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

org.pkl.thirdparty.truffle.api.strings.TStringInternalNodes Maven / Gradle / Ivy

Go to download

Fat Jar containing pkl-cli, pkl-codegen-java, pkl-codegen-kotlin, pkl-config-java, pkl-core, pkl-doc, and their shaded third-party dependencies.

There is a newer version: 0.27.1
Show newest version
/*
 * Copyright (c) 2021, 2022, 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 org.pkl.thirdparty.truffle.api.strings;

import static org.pkl.thirdparty.truffle.api.strings.AbstractTruffleString.checkArrayRange;
import static org.pkl.thirdparty.truffle.api.strings.AbstractTruffleString.checkByteLengthUTF16;
import static org.pkl.thirdparty.truffle.api.strings.AbstractTruffleString.checkByteLengthUTF32;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.indexOfCannotMatch;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.is16Bit;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.is7Bit;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.is7Or8Bit;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.is8Bit;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isAscii;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isAsciiBytesOrLatin1;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isBrokenFixedWidth;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isBrokenMultiByte;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isBytes;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isFixedWidth;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isStride0;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isStride1;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isStride2;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isSupportedEncoding;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUTF16;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUTF16Or32;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUTF32;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUTF8;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isUnsupportedEncoding;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isValid;
import static org.pkl.thirdparty.truffle.api.strings.TStringGuards.isValidFixedWidth;
import static org.pkl.thirdparty.truffle.api.strings.TStringOps.readS0;
import static org.pkl.thirdparty.truffle.api.strings.TStringOps.writeToByteArray;

import java.util.Arrays;

import org.pkl.thirdparty.truffle.api.CompilerAsserts;
import org.pkl.thirdparty.truffle.api.CompilerDirectives.TruffleBoundary;
import org.pkl.thirdparty.truffle.api.dsl.Bind;
import org.pkl.thirdparty.truffle.api.dsl.Cached;
import org.pkl.thirdparty.truffle.api.dsl.Cached.Exclusive;
import org.pkl.thirdparty.truffle.api.dsl.Cached.Shared;
import org.pkl.thirdparty.truffle.api.dsl.ImportStatic;
import org.pkl.thirdparty.truffle.api.dsl.Specialization;
import org.pkl.thirdparty.truffle.api.nodes.ExplodeLoop;
import org.pkl.thirdparty.truffle.api.nodes.Node;
import org.pkl.thirdparty.truffle.api.profiles.InlinedBranchProfile;
import org.pkl.thirdparty.truffle.api.profiles.InlinedConditionProfile;
import org.pkl.thirdparty.truffle.api.strings.AbstractTruffleString.NativePointer;
import org.pkl.thirdparty.truffle.api.strings.TStringOpsNodes.RawIndexOfStringNode;
import org.pkl.thirdparty.truffle.api.strings.TStringOpsNodes.RawLastIndexOfStringNode;
import org.pkl.thirdparty.truffle.api.strings.TruffleString.CompactionLevel;
import org.pkl.thirdparty.truffle.api.strings.TruffleString.Encoding;
import org.pkl.thirdparty.truffle.api.strings.TruffleString.ErrorHandling;

final class TStringInternalNodes {

    abstract static class GetCodeRangeForIndexCalculationNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Encoding encoding);

        @Specialization
        static int get(Node node, AbstractTruffleString a, Encoding encoding,
                        @Cached InlinedConditionProfile impreciseProfile,
                        @Cached TruffleString.ToIndexableNode toIndexableNode,
                        @Cached CalcStringAttributesNode calcStringAttributesNode) {
            int codeRange = a.codeRange();
            if (impreciseProfile.profile(node, !TSCodeRange.isPrecise(codeRange) && !isFixedWidth(codeRange))) {
                return StringAttributes.getCodeRange(updateAttributes(node, a, encoding, codeRange, toIndexableNode, calcStringAttributesNode));
            }
            return codeRange;
        }
    }

    abstract static class GetPreciseCodeRangeNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Encoding encoding);

        @Specialization
        static int get(Node node, AbstractTruffleString a, Encoding encoding,
                        @Cached InlinedConditionProfile impreciseProfile,
                        @Cached TruffleString.ToIndexableNode toIndexableNode,
                        @Cached CalcStringAttributesNode calcStringAttributesNode) {
            int codeRange = a.codeRange();
            if (impreciseProfile.profile(node, !TSCodeRange.isPrecise(codeRange))) {
                return StringAttributes.getCodeRange(updateAttributes(node, a, encoding, codeRange, toIndexableNode, calcStringAttributesNode));
            }
            return codeRange;
        }
    }

    abstract static class GetCodePointLengthNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Encoding encoding);

        @Specialization
        static int get(Node node, AbstractTruffleString a, Encoding encoding,
                        @Cached InlinedConditionProfile cacheMissProfile,
                        @Cached TruffleString.ToIndexableNode toIndexableNode,
                        @Cached CalcStringAttributesNode calcStringAttributesNode) {
            int codePointLength = a.codePointLength();
            if (cacheMissProfile.profile(node, codePointLength < 0)) {
                return StringAttributes.getCodePointLength(updateAttributes(node, a, encoding, a.codeRange(), toIndexableNode, calcStringAttributesNode));
            }
            return codePointLength;
        }

        static GetCodePointLengthNode getUncached() {
            return TStringInternalNodesFactory.GetCodePointLengthNodeGen.getUncached();
        }
    }

    private static long updateAttributes(Node node, AbstractTruffleString a, Encoding encoding, int impreciseCodeRange,
                    TruffleString.ToIndexableNode toIndexableNode,
                    CalcStringAttributesNode calcStringAttributesNode) {
        assert !TSCodeRange.isPrecise(impreciseCodeRange);
        long attrs = calcStringAttributesNode.execute(node, a, toIndexableNode.execute(node, a, a.data()), a.offset(), a.length(), a.stride(), encoding, 0, impreciseCodeRange);
        a.updateAttributes(StringAttributes.getCodePointLength(attrs), StringAttributes.getCodeRange(attrs));
        return attrs;
    }

    abstract static class FromBufferWithStringCompactionNode extends AbstractInternalNode {

        abstract TruffleString execute(Node node, Object arrayA, int offsetA, int byteLength, Encoding encoding, boolean copy, boolean isCacheHead);

        @Specialization
        static TruffleString fromBufferWithStringCompaction(Node node, Object arrayA, int offsetA, int byteLength, Encoding encoding, boolean copy, boolean isCacheHead,
                        @Cached InlinedConditionProfile asciiLatinBytesProfile,
                        @Cached InlinedConditionProfile utf8Profile,
                        @Cached InlinedConditionProfile utf8BrokenProfile,
                        @Cached InlinedConditionProfile utf16Profile,
                        @Cached InlinedConditionProfile utf16CompactProfile,
                        @Cached InlinedConditionProfile utf32Profile,
                        @Cached InlinedConditionProfile utf32Compact0Profile,
                        @Cached InlinedConditionProfile utf32Compact1Profile,
                        @Cached InlinedConditionProfile exoticValidProfile,
                        @Cached InlinedConditionProfile exoticFixedWidthProfile) {
            final int offset;
            final int length;
            final int stride;
            final int codePointLength;
            final int codeRange;
            final Object array;
            if (utf16Profile.profile(node, isUTF16(encoding))) {
                checkByteLengthUTF16(byteLength);
                length = byteLength >> 1;
                long attrs = TStringOps.calcStringAttributesUTF16(node, arrayA, offsetA, length, false);
                codePointLength = StringAttributes.getCodePointLength(attrs);
                codeRange = StringAttributes.getCodeRange(attrs);
                stride = Stride.fromCodeRangeUTF16(codeRange);
                if (copy || stride == 0) {
                    offset = 0;
                    array = new byte[length << stride];
                    if (utf16CompactProfile.profile(node, stride == 0)) {
                        TStringOps.arraycopyWithStride(node, arrayA, offsetA, 1, 0, array, offset, 0, 0, length);
                    } else {
                        TStringOps.arraycopyWithStride(node, arrayA, offsetA, 1, 0, array, offset, 1, 0, length);
                    }
                } else {
                    offset = offsetA;
                    array = arrayA;
                }
            } else if (utf32Profile.profile(node, isUTF32(encoding))) {
                checkByteLengthUTF32(byteLength);
                length = byteLength >> 2;
                codeRange = TStringOps.calcStringAttributesUTF32(node, arrayA, offsetA, length);
                codePointLength = length;
                stride = Stride.fromCodeRangeUTF32(codeRange);
                if (copy || stride < 2) {
                    offset = 0;
                    array = new byte[length << stride];
                    if (utf32Compact0Profile.profile(node, stride == 0)) {
                        TStringOps.arraycopyWithStride(node, arrayA, offsetA, 2, 0, array, offset, 0, 0, length);
                    } else if (utf32Compact1Profile.profile(node, stride == 1)) {
                        TStringOps.arraycopyWithStride(node, arrayA, offsetA, 2, 0, array, offset, 1, 0, length);
                    } else {
                        TStringOps.arraycopyWithStride(node, arrayA, offsetA, 2, 0, array, offset, 2, 0, length);
                    }
                } else {
                    offset = offsetA;
                    array = arrayA;
                }
            } else {
                length = byteLength;
                stride = 0;
                if (utf8Profile.profile(node, isUTF8(encoding))) {
                    long attrs = TStringOps.calcStringAttributesUTF8(node, arrayA, offsetA, length, false, false, utf8BrokenProfile);
                    codeRange = StringAttributes.getCodeRange(attrs);
                    codePointLength = StringAttributes.getCodePointLength(attrs);
                } else if (asciiLatinBytesProfile.profile(node, isAsciiBytesOrLatin1(encoding))) {
                    int cr = TStringOps.calcStringAttributesLatin1(node, arrayA, offsetA, length);
                    codeRange = is8Bit(cr) ? TSCodeRange.asciiLatinBytesNonAsciiCodeRange(encoding) : cr;
                    codePointLength = length;
                } else {
                    if (arrayA instanceof NativePointer) {
                        ((NativePointer) arrayA).materializeByteArray(node, offsetA, length << stride, InlinedConditionProfile.getUncached());
                    }
                    long attrs = JCodings.getInstance().calcStringAttributes(node, arrayA, offsetA, length, encoding, 0, exoticValidProfile, exoticFixedWidthProfile);
                    codeRange = StringAttributes.getCodeRange(attrs);
                    codePointLength = StringAttributes.getCodePointLength(attrs);
                }
                if (copy) {
                    offset = 0;
                    array = TStringOps.arraycopyOfWithStride(node, arrayA, offsetA, length, 0, length, 0);
                } else {
                    offset = offsetA;
                    array = arrayA;
                }
            }
            return TruffleString.createFromArray(array, offset, length, stride, encoding, codePointLength, codeRange, isCacheHead);
        }
    }

    abstract static class FromBufferWithStringCompactionKnownAttributesNode extends AbstractInternalNode {

        final TruffleString execute(Node node, AbstractTruffleString a, Encoding encoding) {
            return execute(node, a, true, encoding);
        }

        abstract TruffleString execute(Node node, AbstractTruffleString a, boolean isCacheHead, Encoding encoding);

        @Specialization
        static TruffleString fromBufferWithStringCompaction(Node node, AbstractTruffleString a, boolean isCacheHead, Encoding encoding,
                        @Cached TStringInternalNodes.GetCodePointLengthNode getCodePointLengthNode,
                        @Cached GetPreciseCodeRangeNode getPreciseCodeRangeNode,
                        @Cached InlinedConditionProfile utf16Profile,
                        @Cached InlinedConditionProfile utf16CompactProfile,
                        @Cached InlinedConditionProfile utf32Profile,
                        @Cached InlinedConditionProfile utf32Compact0Profile,
                        @Cached InlinedConditionProfile utf32Compact1Profile) {
            final int codeRange = getPreciseCodeRangeNode.execute(node, a, encoding);
            a.looseCheckEncoding(encoding, codeRange);
            final int length = a.length();
            if (length == 0) {
                return encoding.getEmpty();
            }
            final Object arrayA = a.data();
            assert arrayA instanceof byte[] || arrayA instanceof NativePointer;
            final int offsetA = a.offset();
            final int strideA = a.stride();
            final int offset = 0;
            final int stride;
            final Object array;
            if (utf16Profile.profile(node, isUTF16(encoding))) {
                stride = Stride.fromCodeRangeUTF16(codeRange);
                array = new byte[length << stride];
                if (utf16CompactProfile.profile(node, strideA == 1 && stride == 0)) {
                    TStringOps.arraycopyWithStride(node, arrayA, offsetA, 1, 0, array, offset, 0, 0, length);
                } else {
                    assert stride == strideA;
                    TStringOps.arraycopyWithStride(node, arrayA, offsetA, 0, 0, array, offset, 0, 0, length << stride);
                }
            } else if (utf32Profile.profile(node, isUTF32(encoding))) {
                stride = Stride.fromCodeRangeUTF32(codeRange);
                array = new byte[length << stride];
                if (utf32Compact0Profile.profile(node, strideA == 2 && stride == 0)) {
                    TStringOps.arraycopyWithStride(node, arrayA, offsetA, 2, 0, array, offset, 0, 0, length);
                } else if (utf32Compact1Profile.profile(node, strideA == 2 && stride == 1)) {
                    TStringOps.arraycopyWithStride(node, arrayA, offsetA, 2, 0, array, offset, 1, 0, length);
                } else {
                    assert stride == strideA;
                    TStringOps.arraycopyWithStride(node, arrayA, offsetA, 0, 0, array, offset, 0, 0, length << stride);
                }
            } else {
                stride = 0;
                array = TStringOps.arraycopyOfWithStride(node, arrayA, offsetA, length, 0, length, 0);
            }
            int codePointLength = getCodePointLengthNode.execute(node, a, encoding);
            return TruffleString.createFromArray(array, offset, length, stride, encoding, codePointLength, codeRange, isCacheHead);
        }

        static FromBufferWithStringCompactionKnownAttributesNode getUncached() {
            return TStringInternalNodesFactory.FromBufferWithStringCompactionKnownAttributesNodeGen.getUncached();
        }
    }

    abstract static class FromNativePointerNode extends AbstractInternalNode {

        abstract TruffleString execute(Node node, NativePointer pointer, int byteOffset, int byteLength, Encoding encoding, boolean isCacheHead);

        @Specialization
        static TruffleString fromNativePointerInternal(Node node, NativePointer pointer, int byteOffset, int byteLength, Encoding encoding, boolean isCacheHead,
                        @Cached InlinedConditionProfile asciiLatinBytesProfile,
                        @Cached InlinedConditionProfile utf8Profile,
                        @Cached InlinedConditionProfile utf8BrokenProfile,
                        @Cached InlinedConditionProfile utf16Profile,
                        @Cached InlinedConditionProfile utf32Profile,
                        @Cached InlinedConditionProfile exoticValidProfile,
                        @Cached InlinedConditionProfile exoticFixedWidthProfile) {
            final int length;
            final int stride;
            final int codePointLength;
            final int codeRange;
            if (utf16Profile.profile(node, isUTF16(encoding))) {
                checkByteLengthUTF16(byteLength);
                length = byteLength >> 1;
                long attrs = TStringOps.calcStringAttributesUTF16(node, pointer, byteOffset, length, false);
                codePointLength = StringAttributes.getCodePointLength(attrs);
                codeRange = StringAttributes.getCodeRange(attrs);
                stride = 1;
            } else if (utf32Profile.profile(node, isUTF32(encoding))) {
                checkByteLengthUTF32(byteLength);
                length = byteLength >> 2;
                codeRange = TStringOps.calcStringAttributesUTF32(node, pointer, byteOffset, length);
                codePointLength = length;
                stride = 2;
            } else {
                length = byteLength;
                stride = 0;
                if (utf8Profile.profile(node, isUTF8(encoding))) {
                    long attrs = TStringOps.calcStringAttributesUTF8(node, pointer, byteOffset, length, false, false, utf8BrokenProfile);
                    codeRange = StringAttributes.getCodeRange(attrs);
                    codePointLength = StringAttributes.getCodePointLength(attrs);
                } else if (asciiLatinBytesProfile.profile(node, isAsciiBytesOrLatin1(encoding))) {
                    int cr = TStringOps.calcStringAttributesLatin1(node, pointer, byteOffset, length);
                    codeRange = is8Bit(cr) ? TSCodeRange.asciiLatinBytesNonAsciiCodeRange(encoding) : cr;
                    codePointLength = length;
                } else {
                    pointer.materializeByteArray(node, byteOffset, byteLength, InlinedConditionProfile.getUncached());
                    long attrs = JCodings.getInstance().calcStringAttributes(node, pointer, byteOffset, length, encoding, 0, exoticValidProfile, exoticFixedWidthProfile);
                    codeRange = StringAttributes.getCodeRange(attrs);
                    codePointLength = StringAttributes.getCodePointLength(attrs);
                }
            }
            return TruffleString.createFromArray(pointer, byteOffset, length, stride, encoding, codePointLength, codeRange, isCacheHead);
        }
    }

    abstract static class ByteLengthOfCodePointNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling);

        @SuppressWarnings("unused")
        @Specialization(guards = {"isFixedWidth(codeRangeA)", "isBestEffort(errorHandling)"})
        int doFixed(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            return 1 << encoding.naturalStride;
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isUpToValidFixedWidth(codeRangeA)", "isReturnNegative(errorHandling)"})
        int doFixedValidReturnNegative(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            return 1 << encoding.naturalStride;
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isAscii(encoding)", "isBroken(codeRangeA)", "isReturnNegative(errorHandling)"})
        int doASCIIBrokenReturnNegative(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            assert isStride0(a);
            return readS0(a, arrayA, index) < 0x80 ? 1 : -1;
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isUTF32(encoding)", "isBroken(codeRangeA)", "isReturnNegative(errorHandling)"})
        static int doUTF32BrokenReturnNegative(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling,
                        @Cached CodePointAtRawNode codePointAtRawNode) {
            return codePointAtRawNode.execute(node, a, arrayA, codeRangeA, encoding, index, ErrorHandling.RETURN_NEGATIVE) < 0 ? -1 : 4;
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isUTF8(encoding)", "isValid(codeRangeA)"})
        int utf8Valid(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            assert isStride0(a);
            int firstByte = readS0(a, arrayA, index);
            return firstByte <= 0x7f ? 1 : Encodings.utf8CodePointLength(firstByte);
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isUTF8(encoding)", "isBroken(codeRangeA)"})
        int utf8Broken(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            assert isStride0(a);
            return Encodings.utf8GetCodePointLength(a, arrayA, index, errorHandling);
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isUTF16(encoding)", "isValid(codeRangeA)"})
        int utf16Valid(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            assert isStride1(a);
            return Encodings.isUTF16HighSurrogate(TStringOps.readS1(a, arrayA, index)) ? 4 : 2;
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isUTF16(encoding)", "isBroken(codeRangeA)"})
        int utf16Broken(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            assert isStride1(a);
            return Encodings.utf16BrokenGetCodePointByteLength(a, arrayA, index, errorHandling);
        }

        @SuppressWarnings("unused")
        @TruffleBoundary
        @Specialization(guards = "isUnsupportedEncoding(encoding)")
        int unsupported(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int index, ErrorHandling errorHandling) {
            assert isStride0(a);
            JCodings.Encoding jCoding = JCodings.getInstance().get(encoding);
            int cpLength = JCodings.getInstance().getCodePointLength(jCoding, JCodings.asByteArray(arrayA), a.byteArrayOffset() + index, a.byteArrayOffset() + a.length());
            int regionLength = a.length() - index;
            if (errorHandling == ErrorHandling.BEST_EFFORT) {
                if (cpLength > 0 && cpLength <= regionLength) {
                    return cpLength;
                } else {
                    return Math.min(JCodings.getInstance().minLength(jCoding), regionLength);
                }
            } else {
                assert errorHandling == ErrorHandling.RETURN_NEGATIVE;
                if (cpLength <= regionLength) {
                    return cpLength;
                } else {
                    return -1 - (cpLength - regionLength);
                }
            }
        }
    }

    abstract static class RawIndexToCodePointIndexNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int byteOffset, int index);

        @SuppressWarnings("unused")
        @Specialization(guards = "isFixedWidth(codeRangeA)")
        int doFixed(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int byteOffset, int index) {
            return index;
        }

        @Specialization(guards = {"isUTF8(encoding)", "isValid(codeRangeA)"})
        static int utf8Valid(Node node, AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int byteOffset, int index,
                        @Shared("broken") @Cached InlinedConditionProfile brokenProfile) {
            return StringAttributes.getCodePointLength(TStringOps.calcStringAttributesUTF8(node, arrayA, a.offset() + byteOffset, index, true, byteOffset + index == a.length(), brokenProfile));
        }

        @Specialization(guards = {"isUTF8(encoding)", "isBroken(codeRangeA)"})
        static int utf8Broken(Node node, @SuppressWarnings("unused") AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding,
                        int byteOffset,
                        int index,
                        @Shared("broken") @Cached InlinedConditionProfile brokenProfile) {
            return StringAttributes.getCodePointLength(TStringOps.calcStringAttributesUTF8(node, arrayA, a.offset() + byteOffset, index, false, false, brokenProfile));
        }

        @Specialization(guards = {"isUTF16(encoding)", "isValid(codeRangeA)"})
        int utf16Valid(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int byteOffset, int index) {
            assert isStride1(a);
            return StringAttributes.getCodePointLength(TStringOps.calcStringAttributesUTF16(this, arrayA, a.offset() + byteOffset, index, true));
        }

        @Specialization(guards = {"isUTF16(encoding)", "isBroken(codeRangeA)"})
        int utf16Broken(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int byteOffset, int index) {
            assert isStride1(a);
            return StringAttributes.getCodePointLength(TStringOps.calcStringAttributesUTF16(this, arrayA, a.offset() + byteOffset, index, false));
        }

        @TruffleBoundary
        @Specialization(guards = "isUnsupportedEncoding(encoding)")
        static int unsupported(Node node, @SuppressWarnings("unused") AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, Encoding encoding, int byteOffset, int index,
                        @Exclusive @Cached InlinedConditionProfile validProfile,
                        @Shared("broken") @Cached InlinedConditionProfile fixedWidthProfile) {
            return StringAttributes.getCodePointLength(JCodings.getInstance().calcStringAttributes(node, arrayA, a.offset(), index, encoding, byteOffset, validProfile, fixedWidthProfile));
        }
    }

    abstract static class CodePointIndexToRawNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int extraOffsetRaw, int index, boolean isLength);

        @SuppressWarnings("unused")
        @Specialization(guards = "isFixedWidth(codeRangeA)")
        int doFixed(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int extraOffsetRaw, int index, boolean isLength) {
            return index;
        }

        @Specialization(guards = {"isUTF8(encoding)", "isValid(codeRangeA)"})
        int utf8Valid(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int extraOffsetRaw, int index,
                        boolean isLength) {
            assert isStride0(a);
            int regionOffset = a.offset() + extraOffsetRaw;
            int regionLength = a.length() - extraOffsetRaw;
            int byteIndex = TStringOps.codePointIndexToByteIndexUTF8Valid(this, arrayA, regionOffset, regionLength, index);
            if (byteIndex < 0 || !isLength && byteIndex == regionLength) {
                throw InternalErrors.indexOutOfBounds();
            }
            return byteIndex;
        }

        @Specialization(guards = {"isUTF8(encoding)", "isBroken(codeRangeA)"})
        int utf8Broken(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int extraOffsetRaw, int index,
                        boolean isLength) {
            assert isStride0(a);
            int cpi = 0;
            for (int i = extraOffsetRaw; i < a.length(); i += Encodings.utf8GetCodePointLength(a, arrayA, i, ErrorHandling.BEST_EFFORT)) {
                if (cpi == index) {
                    return i - extraOffsetRaw;
                }
                cpi++;
                TStringConstants.truffleSafePointPoll(this, cpi);
            }
            return atEnd(a, extraOffsetRaw, index, isLength, cpi);
        }

        @Specialization(guards = {"isUTF16(encoding)", "isValid(codeRangeA)"})
        int utf16Valid(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int extraOffsetRaw, int index,
                        boolean isLength) {
            assert isStride1(a);
            int regionOffset = a.offset() + (extraOffsetRaw << 1);
            int regionLength = a.length() - extraOffsetRaw;
            int result = TStringOps.codePointIndexToByteIndexUTF16Valid(this, arrayA, regionOffset, regionLength, index);
            if (result < 0 || !isLength && result == regionLength) {
                throw InternalErrors.indexOutOfBounds();
            }
            return result;
        }

        @Specialization(guards = {"isUTF16(encoding)", "isBroken(codeRangeA)"})
        int utf16Broken(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int extraOffsetRaw, int index,
                        boolean isLength) {
            assert isStride1(a);
            int cpi = 0;
            for (int i = extraOffsetRaw; i < a.length(); i++) {
                if (!(i > extraOffsetRaw && Encodings.isUTF16LowSurrogate(TStringOps.readS1(a, arrayA, i)) && Encodings.isUTF16HighSurrogate(TStringOps.readS1(a, arrayA, i - 1)))) {
                    if (cpi == index) {
                        return i - extraOffsetRaw;
                    }
                    cpi++;
                }
                TStringConstants.truffleSafePointPoll(this, i + 1);
            }
            return atEnd(a, extraOffsetRaw, index, isLength, cpi);
        }

        @TruffleBoundary
        @Specialization(guards = "isUnsupportedEncoding(encoding)")
        int unsupported(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, Encoding encoding, int extraOffsetRaw, int index, boolean isLength) {
            JCodings.Encoding jCoding = JCodings.getInstance().get(encoding);
            return JCodings.getInstance().codePointIndexToRaw(this, a, JCodings.asByteArray(arrayA), extraOffsetRaw, index, isLength, jCoding);
        }

        static int atEnd(AbstractTruffleString a, int extraOffsetRaw, int index, boolean isLength, int cpi) {
            if (isLength && cpi == index) {
                return a.length() - extraOffsetRaw;
            }
            throw InternalErrors.indexOutOfBounds();
        }
    }

    abstract static class ReadByteNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int i, Encoding encoding);

        @Specialization(guards = "isUTF16(encoding)")
        static int doUTF16(Node node, AbstractTruffleString a, Object arrayA, int i, @SuppressWarnings("unused") Encoding encoding,
                        @Shared("stride0") @Cached InlinedConditionProfile stride0Profile) {
            a.boundsCheckByteIndexUTF16(i);
            final int index;
            if (stride0Profile.profile(node, isStride0(a))) {
                // simplified from:
                // (TStringGuards.bigEndian() ? (i & 1) == 0 : (i & 1) != 0)
                if ((TStringGuards.bigEndian()) == ((i & 1) == 0)) {
                    return 0;
                } else {
                    index = i >> 1;
                }
            } else {
                assert isStride1(a);
                index = i;
            }
            return TStringOps.readS0(arrayA, a.offset(), a.length() << a.stride(), index);
        }

        @Specialization(guards = "isUTF32(encoding)")
        static int doUTF32(Node node, AbstractTruffleString a, Object arrayA, int i, @SuppressWarnings("unused") Encoding encoding,
                        @Shared("stride0") @Cached InlinedConditionProfile stride0Profile,
                        @Exclusive @Cached InlinedConditionProfile stride1Profile) {
            a.boundsCheckByteIndexUTF32(i);
            final int index;
            if (stride0Profile.profile(node, isStride0(a))) {
                if ((i & 3) != (TStringGuards.bigEndian() ? 3 : 0)) {
                    return 0;
                } else {
                    index = i >> 2;
                }
            } else if (stride1Profile.profile(node, isStride1(a))) {
                // simplified from:
                // (TStringGuards.bigEndian() ? (i & 2) == 0 : (i & 2) != 0)
                if ((TStringGuards.bigEndian()) == ((i & 2) == 0)) {
                    return 0;
                } else {
                    if (TStringGuards.bigEndian()) {
                        index = (i >> 1) | (i & 1);
                    } else {
                        index = ((i >> 1) & ~1) | (i & 1);
                    }
                }
            } else {
                assert isStride2(a);
                index = i;
            }
            return TStringOps.readS0(arrayA, a.offset(), a.length() << a.stride(), index);
        }

        @Specialization(guards = "!isUTF16Or32(encoding)")
        static int doRest(AbstractTruffleString a, Object arrayA, int i, @SuppressWarnings("unused") Encoding encoding) {
            a.boundsCheckByteIndexS0(i);
            return TStringOps.readS0(a, arrayA, i);
        }
    }

    abstract static class CodePointAtNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int i, ErrorHandling errorHandling);

        @Specialization(guards = "isUTF16(encoding)")
        static int utf16(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i, ErrorHandling errorHandling,
                        @Cached InlinedConditionProfile fixedWidthProfile,
                        @Shared("stride0") @Cached InlinedConditionProfile stride0Profile,
                        @Shared("valid") @Cached InlinedConditionProfile validProfile) {
            if (fixedWidthProfile.profile(node, isFixedWidth(codeRangeA))) {
                if (stride0Profile.profile(node, isStride0(a))) {
                    return TStringOps.readS0(a, arrayA, i);
                } else {
                    assert isStride1(a);
                    return TStringOps.readS1(a, arrayA, i);
                }
            } else if (validProfile.profile(node, isValid(codeRangeA))) {
                assert isStride1(a);
                return Encodings.utf16DecodeValid(a, arrayA, Encodings.utf16ValidCodePointToCharIndex(node, a, arrayA, i));
            } else {
                assert isStride1(a);
                assert TStringGuards.isBroken(codeRangeA);
                return Encodings.utf16DecodeBroken(a, arrayA, Encodings.utf16BrokenCodePointToCharIndex(node, a, arrayA, i), errorHandling);
            }
        }

        @Specialization(guards = "isUTF32(encoding)")
        static int utf32(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int i, ErrorHandling errorHandling,
                        @Shared("stride0") @Cached InlinedConditionProfile stride0Profile,
                        @Exclusive @Cached InlinedConditionProfile stride1Profile) {
            return CodePointAtRawNode.utf32(node, a, arrayA, codeRangeA, encoding, i, errorHandling, stride0Profile, stride1Profile);
        }

        @Specialization(guards = "isUTF8(encoding)")
        static int utf8(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i, ErrorHandling errorHandling,
                        @Exclusive @Cached InlinedConditionProfile fixedWidthProfile,
                        @Shared("valid") @Cached InlinedConditionProfile validProfile) {
            if (fixedWidthProfile.profile(node, is7Bit(codeRangeA))) {
                return TStringOps.readS0(a, arrayA, i);
            } else {
                int byteIndex = Encodings.utf8CodePointToByteIndex(node, a, arrayA, i);
                if (validProfile.profile(node, isValid(codeRangeA))) {
                    return Encodings.utf8DecodeValid(a, arrayA, byteIndex);
                } else {
                    assert TStringGuards.isBroken(codeRangeA);
                    return Encodings.utf8DecodeBroken(a, arrayA, byteIndex, errorHandling);
                }
            }
        }

        @Specialization(guards = {"!isUTF16Or32(encoding)", "!isUTF8(encoding)", "isBytes(encoding) || is7Or8Bit(codeRangeA)"})
        static int doFixed(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i,
                        @SuppressWarnings("unused") ErrorHandling errorHandling) {
            return CodePointAtRawNode.doFixed(a, arrayA, codeRangeA, encoding, i, errorHandling);
        }

        @Specialization(guards = {"isAscii(encoding)", "!is7Or8Bit(codeRangeA)"})
        static int doAsciiBroken(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i,
                        ErrorHandling errorHandling) {
            return CodePointAtRawNode.doAsciiBroken(a, arrayA, codeRangeA, encoding, i, errorHandling);
        }

        @Specialization(guards = {"isUnsupportedEncoding(encoding)", "!is7Or8Bit(codeRangeA)"})
        int unsupported(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, Encoding encoding, int i, ErrorHandling errorHandling) {
            assert isStride0(a);
            JCodings.Encoding jCoding = JCodings.getInstance().get(encoding);
            byte[] bytes = JCodings.asByteArray(arrayA);
            return JCodings.getInstance().decode(a, bytes, JCodings.getInstance().codePointIndexToRaw(this, a, bytes, 0, i, false, jCoding), jCoding, errorHandling);
        }
    }

    abstract static class CodePointAtRawNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int i, ErrorHandling errorHandling);

        @Specialization(guards = "isUTF16(encoding)")
        static int utf16(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i, ErrorHandling errorHandling,
                        @Cached InlinedConditionProfile fixedWidthProfile,
                        @Cached InlinedConditionProfile validProfile,
                        @Shared("stride0") @Cached InlinedConditionProfile stride0Profile) {
            if (fixedWidthProfile.profile(node, isFixedWidth(codeRangeA))) {
                if (stride0Profile.profile(node, isStride0(a))) {
                    return TStringOps.readS0(a, arrayA, i);
                } else {
                    assert isStride1(a);
                    return TStringOps.readS1(a, arrayA, i);
                }
            } else if (validProfile.profile(node, isValid(codeRangeA))) {
                return Encodings.utf16DecodeValid(a, arrayA, i);
            } else {
                assert TStringGuards.isBroken(codeRangeA);
                return Encodings.utf16DecodeBroken(a, arrayA, i, errorHandling);
            }
        }

        @Specialization(guards = "isUTF32(encoding)")
        static int utf32(Node node, AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i,
                        ErrorHandling errorHandling,
                        @Shared("stride0") @Cached InlinedConditionProfile stride0Profile,
                        @Exclusive @Cached InlinedConditionProfile stride1Profile) {
            if (stride0Profile.profile(node, isStride0(a))) {
                return TStringOps.readS0(a, arrayA, i);
            } else if (stride1Profile.profile(node, isStride1(a))) {
                char c = TStringOps.readS1(a, arrayA, i);
                if (errorHandling == ErrorHandling.RETURN_NEGATIVE && Encodings.isUTF16Surrogate(c)) {
                    return -1;
                }
                return c;
            } else {
                assert isStride2(a);
                int c = TStringOps.readS2(a, arrayA, i);
                if (errorHandling == ErrorHandling.RETURN_NEGATIVE && !Encodings.isValidUnicodeCodepoint(c)) {
                    return -1;
                }
                return c;
            }
        }

        @Specialization(guards = "isUTF8(encoding)")
        static int utf8(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i, ErrorHandling errorHandling,
                        @Exclusive @Cached InlinedConditionProfile fixedWidthProfile,
                        @Exclusive @Cached InlinedConditionProfile validProfile) {
            if (fixedWidthProfile.profile(node, is7Bit(codeRangeA))) {
                return TStringOps.readS0(a, arrayA, i);
            } else if (validProfile.profile(node, isValid(codeRangeA))) {
                return Encodings.utf8DecodeValid(a, arrayA, i);
            } else {
                assert TStringGuards.isBroken(codeRangeA);
                return Encodings.utf8DecodeBroken(a, arrayA, i, errorHandling);
            }
        }

        @Specialization(guards = {"!isUTF16Or32(encoding)", "!isUTF8(encoding)", "isBytes(encoding) || is7Or8Bit(codeRangeA)"})
        static int doFixed(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i,
                        @SuppressWarnings("unused") ErrorHandling errorHandling) {
            assert isStride0(a);
            return TStringOps.readS0(a, arrayA, i);
        }

        @Specialization(guards = {"isAscii(encoding)", "!is7Or8Bit(codeRangeA)"})
        static int doAsciiBroken(AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i,
                        ErrorHandling errorHandling) {
            assert isStride0(a);
            int c = readS0(a, arrayA, i);
            if (errorHandling == ErrorHandling.RETURN_NEGATIVE && c > 0x7f) {
                return -1;
            }
            assert errorHandling == ErrorHandling.BEST_EFFORT || !TSCodeRange.isPrecise(codeRangeA);
            return c;
        }

        @Specialization(guards = {"isUnsupportedEncoding(encoding)", "!is7Or8Bit(codeRangeA)"})
        static int unsupported(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int i, ErrorHandling errorHandling) {
            return JCodings.getInstance().decode(a, JCodings.asByteArray(arrayA), i, JCodings.getInstance().get(encoding), errorHandling);
        }
    }

    static int indexOfFixedWidth(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, int codepoint, int fromIndex, int toIndex,
                    TStringOpsNodes.RawIndexOfCodePointNode indexOfNode) {
        if (fromIndex == toIndex || !TSCodeRange.isInCodeRange(codepoint, codeRangeA)) {
            return -1;
        }
        return indexOfNode.execute(node, a, arrayA, codepoint, fromIndex, toIndex);
    }

    static int lastIndexOfFixedWidth(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, int codepoint, int fromIndex, int toIndex,
                    TStringOpsNodes.RawLastIndexOfCodePointNode indexOfNode) {
        if (fromIndex == toIndex || !TSCodeRange.isInCodeRange(codepoint, codeRangeA)) {
            return -1;
        }
        return indexOfNode.execute(node, a, arrayA, codepoint, fromIndex, toIndex);
    }

    abstract static class IndexOfCodePointNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex);

        @Specialization(guards = "isFixedWidth(codeRangeA)")
        static int doFixedWidth(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached TStringOpsNodes.RawIndexOfCodePointNode indexOfNode) {
            return indexOfFixedWidth(node, a, arrayA, codeRangeA, codepoint, fromIndex, toIndex, indexOfNode);
        }

        @Specialization(guards = "!isFixedWidth(codeRangeA)")
        static int decode(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached TruffleStringIterator.InternalNextNode nextNode) {
            return TruffleStringIterator.indexOf(node, AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding), codepoint, fromIndex, toIndex, nextNode);
        }
    }

    abstract static class IndexOfCodePointRawNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex);

        @Specialization(guards = {"isFixedWidth(codeRangeA)"})
        static int utf8Fixed(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached TStringOpsNodes.RawIndexOfCodePointNode indexOfNode) {
            return indexOfFixedWidth(node, a, arrayA, codeRangeA, codepoint, fromIndex, toIndex, indexOfNode);
        }

        @Specialization(guards = {"isUTF8(encoding)", "!isFixedWidth(codeRangeA)"})
        int utf8Variable(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex) {
            assert isStride0(a);
            int encodedSize = Encodings.utf8EncodedSize(codepoint);
            if (encodedSize > toIndex - fromIndex) {
                return -1;
            }
            if (encodedSize == 1) {
                return TStringOps.indexOfCodePointWithStride(this, a, arrayA, 0, fromIndex, toIndex, codepoint);
            }
            byte[] encoded = Encodings.utf8EncodeNonAscii(codepoint, encodedSize);
            TruffleString b = TruffleString.createFromByteArray(encoded, encoded.length, 0, Encoding.UTF_8, 1, TSCodeRange.getValidMultiByte());
            return TStringOps.indexOfStringWithOrMaskWithStride(this, a, arrayA, 0, b, encoded, 0, fromIndex, toIndex, null);
        }

        @Specialization(guards = {"isUTF16(encoding)", "!isFixedWidth(codeRangeA)"})
        int utf16Variable(AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex,
                        int toIndex) {
            assert isStride1(a);
            int encodedSize = Encodings.utf16EncodedSize(codepoint);
            if (encodedSize > toIndex - fromIndex) {
                return -1;
            }
            if (encodedSize == 1) {
                return TStringOps.indexOfCodePointWithStride(this, a, arrayA, 1, fromIndex, toIndex, codepoint);
            }
            return TStringOps.indexOf2ConsecutiveWithStride(
                            this, a, arrayA, 1, fromIndex, toIndex, Character.highSurrogate(codepoint), Character.lowSurrogate(codepoint));
        }

        @Specialization(guards = {"isUnsupportedEncoding(encoding)", "!isFixedWidth(codeRangeA)"})
        static int unsupported(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached TruffleStringIterator.InternalNextNode nextNode) {
            final TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding);
            it.setRawIndex(fromIndex);
            int loopCount = 0;
            while (it.hasNext() && it.getRawIndex() < toIndex) {
                int ret = it.getRawIndex();
                if (nextNode.execute(node, it) == codepoint) {
                    return ret;
                }
                TStringConstants.truffleSafePointPoll(node, ++loopCount);
            }
            return -1;
        }
    }

    abstract static class LastIndexOfCodePointNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex);

        @Specialization(guards = "isFixedWidth(codeRangeA)")
        static int doFixedWidth(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached TStringOpsNodes.RawLastIndexOfCodePointNode lastIndexOfNode) {
            return lastIndexOfFixedWidth(node, a, arrayA, codeRangeA, codepoint, fromIndex, toIndex, lastIndexOfNode);
        }

        @Specialization(guards = "!isFixedWidth(codeRangeA)")
        static int decode(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached TruffleStringIterator.InternalNextNode nextNode) {
            return TruffleStringIterator.lastIndexOf(node, AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding), codepoint, fromIndex, toIndex, nextNode);
        }
    }

    abstract static class LastIndexOfCodePointRawNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int codepoint, int fromIndex, int toIndex);

        @Specialization(guards = {"isFixedWidth(codeRangeA)"})
        static int utf8Fixed(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached @Shared("lastIndexOfNode") TStringOpsNodes.RawLastIndexOfCodePointNode lastIndexOfNode) {
            return lastIndexOfFixedWidth(node, a, arrayA, codeRangeA, codepoint, fromIndex, toIndex, lastIndexOfNode);
        }

        @Specialization(guards = {"isUTF8(encoding)", "!isFixedWidth(codeRangeA)"})
        static int utf8Variable(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached @Shared("lastIndexOfNode") TStringOpsNodes.RawLastIndexOfCodePointNode lastIndexOfNode) {
            int encodedSize = Encodings.utf8EncodedSize(codepoint);
            if (encodedSize > fromIndex - toIndex) {
                return -1;
            }
            if (encodedSize == 1) {
                return lastIndexOfFixedWidth(node, a, arrayA, codeRangeA, codepoint, fromIndex, toIndex, lastIndexOfNode);
            }
            byte[] encoded = Encodings.utf8EncodeNonAscii(codepoint, encodedSize);
            TruffleString b = TruffleString.createFromByteArray(encoded, encoded.length, 0, Encoding.UTF_8, 1, TSCodeRange.getValidMultiByte());
            return TStringOps.lastIndexOfStringWithOrMaskWithStride(node, a, arrayA, 0, b, encoded, 0, fromIndex, toIndex, null);
        }

        @Specialization(guards = {"isUTF16(encoding)", "!isFixedWidth(codeRangeA)"})
        static int utf16Variable(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint, int fromIndex, int toIndex,
                        @Cached @Shared("lastIndexOfNode") TStringOpsNodes.RawLastIndexOfCodePointNode lastIndexOfNode) {
            assert isStride1(a);
            int encodedSize = Encodings.utf16EncodedSize(codepoint);
            if (encodedSize > fromIndex - toIndex) {
                return -1;
            }
            if (encodedSize == 1) {
                return lastIndexOfFixedWidth(node, a, arrayA, codeRangeA, codepoint, fromIndex, toIndex, lastIndexOfNode);
            }
            return TStringOps.lastIndexOf2ConsecutiveWithOrMaskWithStride(
                            node, a, arrayA, 1, fromIndex, toIndex, Character.highSurrogate(codepoint), Character.lowSurrogate(codepoint), 0, 0);
        }

        @Specialization(guards = {"isUnsupportedEncoding(encoding)", "!isFixedWidth(codeRangeA)"})
        static int unsupported(Node node, AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int codepoint,
                        int fromIndex, int toIndex,
                        @Cached TruffleStringIterator.InternalPreviousNode prevNode) {
            final TruffleStringIterator it = TruffleString.backwardIterator(a, arrayA, codeRangeA, encoding);
            it.setRawIndex(fromIndex);
            int loopCount = 0;
            while (it.hasPrevious() && it.getRawIndex() >= toIndex) {
                if (prevNode.execute(node, it) == codepoint) {
                    return it.getRawIndex();
                }
                TStringConstants.truffleSafePointPoll(node, ++loopCount);
            }
            return -1;
        }
    }

    @ImportStatic({TStringGuards.class, Encoding.class})
    abstract static class IndexOfCodePointSetNode extends Node {

        static final int POSSIBLE_STRIDE_VALUES = 3;

        @Children IndexOfCodePointSet.IndexOfNode[] indexOfNodes;
        final Encoding encoding;
        final boolean isUTF16Or32;

        IndexOfCodePointSetNode(IndexOfCodePointSet.IndexOfNode[] indexOfNodes, Encoding encoding) {
            this.indexOfNodes = insert(indexOfNodes);
            this.encoding = encoding;
            this.isUTF16Or32 = isUTF16Or32(encoding);
        }

        abstract int execute(Object arrayA, int offsetA, int lengthA, int strideA, int codeRangeA, int fromIndex, int toIndex);

        @Specialization(guards = "!isUTF16Or32")
        int stride0(Object arrayA, int offsetA, int lengthA, @SuppressWarnings("unused") int strideA, int codeRangeA, int fromIndex, int toIndex) {
            return doIndexOf(arrayA, offsetA, lengthA, 0, codeRangeA, fromIndex, toIndex);
        }

        @Specialization(guards = {"isUTF16Or32", "strideA == cachedStride"}, limit = "POSSIBLE_STRIDE_VALUES")
        int dynamicStride(Object arrayA, int offsetA, int lengthA, @SuppressWarnings("unused") int strideA, int codeRangeA, int fromIndex, int toIndex,
                        @Cached(value = "strideA", allowUncached = true) int cachedStride) {
            return doIndexOf(arrayA, offsetA, lengthA, cachedStride, codeRangeA, fromIndex, toIndex);
        }

        @ExplodeLoop
        private int doIndexOf(Object arrayA, int offsetA, int lengthA, int strideA, int codeRangeA, int fromIndex, int toIndex) {
            CompilerAsserts.partialEvaluationConstant(indexOfNodes);
            // generate an if-else cascade checking increasing code range values
            for (int i = 0; i < indexOfNodes.length - 1; i++) {
                CompilerAsserts.partialEvaluationConstant(i);
                IndexOfCodePointSet.IndexOfNode node = indexOfNodes[i];
                CompilerAsserts.partialEvaluationConstant(node);
                if (TSCodeRange.isMoreRestrictiveOrEqual(codeRangeA, Byte.toUnsignedInt(node.maxCodeRange))) {
                    return node.execute(this, arrayA, offsetA, lengthA, strideA, codeRangeA, fromIndex, toIndex, encoding);
                }
            }
            // last node is always the else-branch
            return indexOfNodes[indexOfNodes.length - 1].execute(this, arrayA, offsetA, lengthA, strideA, codeRangeA, fromIndex, toIndex, encoding);
        }
    }

    abstract static class SubstringNode extends AbstractInternalNode {

        abstract TruffleString execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int fromIndex, int length, boolean lazy);

        @SuppressWarnings("unused")
        @Specialization(guards = "length == 0")
        static TruffleString lengthZero(AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int fromIndex, int length, boolean lazy) {
            return encoding.getEmpty();
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"fromIndex == 0", "length == length(a)"})
        static TruffleString sameStr(TruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int fromIndex, int length, boolean lazy) {
            return a;
        }

        @Specialization(guards = {"length > 0", "length != length(a) || a.isMutable()", "!lazy"})
        static TruffleString materializeSubstring(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int fromIndex, int length,
                        @SuppressWarnings("unused") boolean lazy,
                        @Shared("attributes") @Cached CalcStringAttributesNode calcAttributesNode,
                        @Exclusive @Cached InlinedConditionProfile utf16Profile,
                        @Exclusive @Cached InlinedConditionProfile utf32Profile) {
            final long attrs;
            final int codeRange;
            final int stride;
            final int newStride;
            if (utf16Profile.profile(node, isUTF16(encoding))) {
                stride = a.stride();
                attrs = calcAttributesNode.execute(node, a, arrayA, a.offset(), length, stride, Encoding.UTF_16, fromIndex, codeRangeA);
                codeRange = StringAttributes.getCodeRange(attrs);
                newStride = Stride.fromCodeRangeUTF16(codeRange);
            } else if (utf32Profile.profile(node, isUTF32(encoding))) {
                stride = a.stride();
                attrs = calcAttributesNode.execute(node, a, arrayA, a.offset(), length, stride, Encoding.UTF_32, fromIndex, codeRangeA);
                codeRange = StringAttributes.getCodeRange(attrs);
                newStride = Stride.fromCodeRangeUTF32(codeRange);
            } else {
                stride = 0;
                attrs = calcAttributesNode.execute(node, a, arrayA, a.offset(), length, stride, encoding, fromIndex, codeRangeA);
                codeRange = StringAttributes.getCodeRange(attrs);
                newStride = 0;
            }
            byte[] newBytes = TStringOps.arraycopyOfWithStride(node, arrayA, a.offset() + (fromIndex << stride), length, stride, length, newStride);
            return TruffleString.createFromByteArray(newBytes, length, newStride, encoding, StringAttributes.getCodePointLength(attrs), codeRange);
        }

        @Specialization(guards = {"length > 0", "length != length(a)", "lazy"})
        static TruffleString createLazySubstring(Node node, TruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int fromIndex, int length, @SuppressWarnings("unused") boolean lazy,
                        @Shared("attributes") @Cached CalcStringAttributesNode calcAttributesNode,
                        @Exclusive @Cached InlinedConditionProfile stride1MustMaterializeProfile,
                        @Exclusive @Cached InlinedConditionProfile stride2MustMaterializeProfile) {
            int lazyOffset = a.offset() + (fromIndex << a.stride());
            long attrs = calcAttributesNode.execute(node, a, arrayA, a.offset(), length, a.stride(), encoding, fromIndex, codeRangeA);
            int codeRange = StringAttributes.getCodeRange(attrs);
            int codePointLength = StringAttributes.getCodePointLength(attrs);
            final Object array;
            final int offset;
            final int stride;
            if (stride1MustMaterializeProfile.profile(node, a.stride() == 1 && TSCodeRange.isMoreRestrictiveOrEqual(codeRange, TSCodeRange.get8Bit()))) {
                assert isUTF16Or32(encoding);
                stride = 0;
                offset = 0;
                final byte[] newBytes = new byte[length];
                TStringOps.arraycopyWithStride(node,
                                arrayA, lazyOffset, 1, 0,
                                newBytes, offset, 0, 0, length);
                array = newBytes;
            } else if (stride2MustMaterializeProfile.profile(node, a.stride() == 2 && TSCodeRange.isMoreRestrictiveOrEqual(codeRange, TSCodeRange.get16Bit()))) {
                // Always materialize 4-byte UTF-32 strings when they can be compacted. Otherwise,
                // they could get re-interpreted as UTF-16 and break the assumption that all UTF-16
                // strings are stride 0 or 1.
                assert isUTF32(encoding);
                stride = Stride.fromCodeRangeUTF32(StringAttributes.getCodeRange(attrs));
                offset = 0;
                final byte[] newBytes = new byte[length << stride];
                if (stride == 0) {
                    TStringOps.arraycopyWithStride(node,
                                    arrayA, lazyOffset, 2, 0,
                                    newBytes, offset, 0, 0, length);
                } else {
                    assert stride == 1;
                    TStringOps.arraycopyWithStride(node,
                                    arrayA, lazyOffset, 2, 0,
                                    newBytes, offset, 1, 0, length);
                }
                array = newBytes;
            } else {
                if (isUnsupportedEncoding(encoding) && arrayA instanceof NativePointer) {
                    // avoid conflicts in NativePointer#getBytes
                    array = ((NativePointer) arrayA).copy();
                } else {
                    array = arrayA;
                }
                offset = lazyOffset;
                stride = a.stride();
            }
            return TruffleString.createFromArray(array, offset, length, stride, encoding, codePointLength, codeRange);
        }
    }

    abstract static class ConcatEagerNode extends AbstractInternalNode {

        abstract TruffleString execute(Node node, AbstractTruffleString a, AbstractTruffleString b, Encoding encoding, int concatLength, int concatStride, int concatCodeRange);

        @Specialization
        static TruffleString concat(Node node, AbstractTruffleString a, AbstractTruffleString b, Encoding encoding, int concatLength, int concatStride, int concatCodeRange,
                        @Cached TruffleString.ToIndexableNode toIndexableNodeA,
                        @Cached TruffleString.ToIndexableNode toIndexableNodeB,
                        @Cached GetCodePointLengthNode getCodePointLengthANode,
                        @Cached GetCodePointLengthNode getCodePointLengthBNode,
                        @Cached ConcatMaterializeBytesNode materializeBytesNode,
                        @Cached CalcStringAttributesNode calculateAttributesNode,
                        @Cached InlinedConditionProfile brokenProfile) {
            final byte[] bytes = materializeBytesNode.execute(node, a, toIndexableNodeA.execute(node, a, a.data()), b, toIndexableNodeB.execute(node, b, b.data()), encoding, concatLength,
                            concatStride);
            final int codeRange;
            final int codePointLength;
            if (brokenProfile.profile(node, isBrokenMultiByte(concatCodeRange))) {
                final long attrs = calculateAttributesNode.execute(node, null, bytes, 0, concatLength, concatStride, encoding, 0, TSCodeRange.getBrokenMultiByte());
                codePointLength = StringAttributes.getCodePointLength(attrs);
                codeRange = StringAttributes.getCodeRange(attrs);
            } else {
                codePointLength = getCodePointLengthANode.execute(node, a, encoding) + getCodePointLengthBNode.execute(node, b, encoding);
                codeRange = concatCodeRange;
            }
            return TruffleString.createFromByteArray(bytes, concatLength, concatStride, encoding, codePointLength, codeRange);
        }
    }

    abstract static class ConcatMaterializeBytesNode extends AbstractInternalNode {

        abstract byte[] execute(Node node, AbstractTruffleString a, Object arrayA, AbstractTruffleString b, Object arrayB, Encoding encoding, int concatLength, int concatStride);

        @Specialization(guards = "isUTF16(encoding) || isUTF32(encoding)")
        byte[] doWithCompression(AbstractTruffleString a, Object arrayA, AbstractTruffleString b, Object arrayB, @SuppressWarnings("unused") Encoding encoding, int concatLength, int concatStride) {
            final byte[] bytes = new byte[concatLength << concatStride];
            TStringOps.arraycopyWithStride(this,
                            arrayA, a.offset(), a.stride(), 0,
                            bytes, 0, concatStride, 0, a.length());
            TStringOps.arraycopyWithStride(this,
                            arrayB, b.offset(), b.stride(), 0,
                            bytes, 0, concatStride, a.length(), b.length());
            return bytes;
        }

        @Specialization(guards = {"!isUTF16(encoding)", "!isUTF32(encoding)"})
        byte[] doNoCompression(AbstractTruffleString a, Object arrayA, AbstractTruffleString b, Object arrayB, @SuppressWarnings("unused") Encoding encoding, int concatLength, int concatStride) {
            assert isStride0(a);
            assert isStride0(b);
            assert concatStride == 0;
            final byte[] bytes = new byte[concatLength];
            TStringOps.arraycopyWithStride(this,
                            arrayA, a.offset(), 0, 0,
                            bytes, 0, 0, 0, a.length());
            TStringOps.arraycopyWithStride(this,
                            arrayB, b.offset(), 0, 0,
                            bytes, 0, 0, a.length(), b.length());
            return bytes;
        }
    }

    abstract static class RegionEqualsNode extends AbstractInternalNode {

        abstract boolean execute(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA, int fromIndexA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndexB, int length, Encoding encoding);

        @Specialization(guards = {"isFixedWidth(codeRangeA, codeRangeB)"})
        boolean direct(
                        AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, int fromIndexA,
                        AbstractTruffleString b, Object arrayB, @SuppressWarnings("unused") int codeRangeB, int fromIndexB, int length, @SuppressWarnings("unused") Encoding encoding) {
            return TStringOps.regionEqualsWithOrMaskWithStride(this, a, arrayA, a.stride(), fromIndexA, b, arrayB, b.stride(), fromIndexB, null, length);
        }

        @Specialization(guards = {"!isFixedWidth(codeRangeA, codeRangeB)"})
        static boolean decode(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA, int fromIndexA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndexB, int length, Encoding encoding,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeA,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeB) {
            TruffleStringIterator aIt = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding);
            TruffleStringIterator bIt = AbstractTruffleString.forwardIterator(b, arrayB, codeRangeB, encoding);
            for (int i = 0; i < fromIndexA; i++) {
                if (!aIt.hasNext()) {
                    return false;
                }
                nextNodeA.execute(node, aIt);
                TStringConstants.truffleSafePointPoll(node, i + 1);
            }
            for (int i = 0; i < fromIndexB; i++) {
                if (!bIt.hasNext()) {
                    return false;
                }
                nextNodeB.execute(node, bIt);
                TStringConstants.truffleSafePointPoll(node, i + 1);
            }
            for (int i = 0; i < length; i++) {
                if (!(aIt.hasNext() && bIt.hasNext()) || nextNodeA.execute(node, aIt) != nextNodeB.execute(node, bIt)) {
                    return false;
                }
                TStringConstants.truffleSafePointPoll(node, i + 1);
            }
            return true;
        }
    }

    abstract static class InternalIndexOfStringNode extends AbstractInternalNode {

        abstract int execute(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, Encoding encoding);

        @Specialization(guards = {"isFixedWidth(codeRangeA, codeRangeB)"})
        static int direct(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, @SuppressWarnings("unused") Encoding encoding,
                        @Cached RawIndexOfStringNode indexOfStringNode) {
            assert !b.isEmpty() && !indexOfCannotMatch(node, codeRangeA, b, codeRangeB, toIndex - fromIndex, encoding, GetCodePointLengthNode.getUncached());
            return indexOfStringNode.execute(node, a, arrayA, b, arrayB, fromIndex, toIndex, null);
        }

        @Specialization(guards = {"!isFixedWidth(codeRangeA, codeRangeB)"})
        static int decode(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, Encoding encoding,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeA,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeB) {
            assert !b.isEmpty() && !indexOfCannotMatch(node, codeRangeA, b, codeRangeB, toIndex - fromIndex, encoding, GetCodePointLengthNode.getUncached());
            TruffleStringIterator aIt = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding);
            TruffleStringIterator bIt = AbstractTruffleString.forwardIterator(b, arrayB, codeRangeB, encoding);
            return TruffleStringIterator.indexOfString(node, aIt, bIt, fromIndex, toIndex, nextNodeA, nextNodeB);
        }
    }

    abstract static class IndexOfStringRawNode extends AbstractInternalNode {

        abstract int execute(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, byte[] mask, Encoding encoding);

        @Specialization(guards = {"isSupportedEncoding(encoding) || isFixedWidth(codeRangeA)"})
        static int supported(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, byte[] mask, @SuppressWarnings("unused") Encoding encoding,
                        @Cached TStringOpsNodes.RawIndexOfStringNode indexOfStringNode) {
            assert !b.isEmpty() && !indexOfCannotMatch(codeRangeA, b, codeRangeB, mask, toIndex - fromIndex);
            return indexOfStringNode.execute(node, a, arrayA, b, arrayB, fromIndex, toIndex, mask);
        }

        @Specialization(guards = {"isUnsupportedEncoding(encoding)", "!isFixedWidth(codeRangeA)"})
        static int unsupported(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, @SuppressWarnings("unused") byte[] mask, Encoding encoding,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeA,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeB) {
            assert mask == null;
            assert !b.isEmpty() && !indexOfCannotMatch(codeRangeA, b, codeRangeB, mask, toIndex - fromIndex);
            TruffleStringIterator aIt = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding);
            TruffleStringIterator bIt = AbstractTruffleString.forwardIterator(b, arrayB, codeRangeB, encoding);
            return TruffleStringIterator.byteIndexOfString(node, aIt, bIt, fromIndex, toIndex, nextNodeA, nextNodeB);
        }
    }

    abstract static class LastIndexOfStringNode extends AbstractInternalNode {

        abstract int execute(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, Encoding encoding);

        @Specialization(guards = {"isFixedWidth(codeRangeA, codeRangeB)"})
        static int direct(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, @SuppressWarnings("unused") Encoding encoding,
                        @Cached RawLastIndexOfStringNode indexOfStringNode) {
            assert !b.isEmpty() && !indexOfCannotMatch(node, codeRangeA, b, codeRangeB, fromIndex - toIndex, encoding, GetCodePointLengthNode.getUncached());
            return indexOfStringNode.execute(node, a, arrayA, b, arrayB, fromIndex, toIndex, null);
        }

        @Specialization(guards = {"!isFixedWidth(codeRangeA, codeRangeB)"})
        static int decode(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, Encoding encoding,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeA,
                        @Cached TruffleStringIterator.InternalPreviousNode prevNodeA,
                        @Cached TruffleStringIterator.InternalPreviousNode prevNodeB) {
            assert !b.isEmpty() && !indexOfCannotMatch(node, codeRangeA, b, codeRangeB, fromIndex - toIndex, encoding, GetCodePointLengthNode.getUncached());
            TruffleStringIterator aIt = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding);
            TruffleStringIterator bIt = AbstractTruffleString.backwardIterator(b, arrayB, codeRangeB, encoding);
            return TruffleStringIterator.lastIndexOfString(node, aIt, bIt, fromIndex, toIndex, nextNodeA, prevNodeA, prevNodeB);
        }
    }

    abstract static class LastIndexOfStringRawNode extends AbstractInternalNode {

        abstract int execute(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, byte[] mask, Encoding encoding);

        @Specialization(guards = {"isSupportedEncoding(encoding) || isFixedWidth(codeRangeA)"})
        static int lios8SameEncoding(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, byte[] mask, @SuppressWarnings("unused") Encoding encoding,
                        @Cached TStringOpsNodes.RawLastIndexOfStringNode indexOfStringNode) {
            assert !b.isEmpty() && !indexOfCannotMatch(codeRangeA, b, codeRangeB, mask, fromIndex - toIndex);
            return indexOfStringNode.execute(node, a, arrayA, b, arrayB, fromIndex, toIndex, mask);
        }

        @Specialization(guards = {"isUnsupportedEncoding(encoding)", "!isFixedWidth(codeRangeA)"})
        static int unsupported(Node node,
                        AbstractTruffleString a, Object arrayA, int codeRangeA,
                        AbstractTruffleString b, Object arrayB, int codeRangeB, int fromIndex, int toIndex, @SuppressWarnings("unused") byte[] mask, Encoding encoding,
                        @Cached TruffleStringIterator.InternalNextNode nextNodeA,
                        @Cached TruffleStringIterator.InternalPreviousNode prevNodeA,
                        @Cached TruffleStringIterator.InternalPreviousNode prevNodeB) {
            assert mask == null;
            assert !b.isEmpty() && !indexOfCannotMatch(codeRangeA, b, codeRangeB, mask, fromIndex - toIndex);
            TruffleStringIterator aIt = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding);
            TruffleStringIterator bIt = AbstractTruffleString.backwardIterator(b, arrayB, codeRangeB, encoding);
            return TruffleStringIterator.lastByteIndexOfString(node, aIt, bIt, fromIndex, toIndex, nextNodeA, prevNodeA, prevNodeB);
        }
    }

    abstract static class StrideFromCodeRangeNode extends AbstractInternalNode {

        abstract int execute(Node node, int codeRange, Encoding encoding);

        @Specialization(guards = "isUTF16(encoding)")
        int doUTF16(int codeRange, @SuppressWarnings("unused") Encoding encoding) {
            return Stride.fromCodeRangeUTF16(codeRange);
        }

        @Specialization(guards = "isUTF32(encoding)")
        int doUTF32(int codeRange, @SuppressWarnings("unused") Encoding encoding) {
            return Stride.fromCodeRangeUTF32(codeRange);
        }

        @Specialization(guards = {"!isUTF16(encoding)", "!isUTF32(encoding)"})
        int doOther(@SuppressWarnings("unused") int codeRange, @SuppressWarnings("unused") Encoding encoding) {
            return 0;
        }
    }

    abstract static class CalcStringAttributesNode extends AbstractInternalNode {

        abstract long execute(Node node, AbstractTruffleString a, Object array, int offset, int length, int stride, Encoding encoding, int fromIndex, int knownCodeRange);

        @SuppressWarnings("unused")
        @Specialization(guards = "length == 0")
        long empty(AbstractTruffleString a, Object array, int offset, int length, int stride, Encoding encoding, int fromIndex, int knownCodeRange) {
            return StringAttributes.create(length, TSCodeRange.getAsciiCodeRange(encoding));
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"is7Bit(knownCodeRange)", "length > 0"})
        long ascii(AbstractTruffleString a, Object array, int offset, int length, int stride, Encoding encoding, int fromIndex, int knownCodeRange) {
            return StringAttributes.create(length, TSCodeRange.get7Bit());
        }

        @Specialization(guards = {"!is7Bit(knownCodeRange)", "length > 0"})
        static long notAscii(Node node, AbstractTruffleString a, Object array, int offset, int length, int stride, Encoding encoding, int fromIndex, int knownCodeRange,
                        @Cached CalcStringAttributesInnerNode calcNode) {
            return calcNode.execute(node, a, array, offset, length, stride, encoding, fromIndex, knownCodeRange);
        }

    }

    abstract static class CalcStringAttributesInnerNode extends AbstractInternalNode {

        abstract long execute(Node node, AbstractTruffleString a, Object array, int offset, int length, int stride, Encoding encoding, int fromIndex, int knownCodeRange);

        @Specialization(guards = {"is8Bit(knownCodeRange) || isAsciiBytesOrLatin1(encoding)", "stride == 0"})
        long doLatin1(@SuppressWarnings("unused") AbstractTruffleString a, Object array, int offset, int length, @SuppressWarnings("unused") int stride, Encoding encoding, int fromIndex,
                        @SuppressWarnings("unused") int knownCodeRange) {
            int codeRange = TStringOps.calcStringAttributesLatin1(this, array, offset + fromIndex, length);
            return StringAttributes.create(length, is8Bit(codeRange) && isAsciiBytesOrLatin1(encoding) ? TSCodeRange.asciiLatinBytesNonAsciiCodeRange(encoding) : codeRange);
        }

        @Specialization(guards = {"isUpTo16Bit(knownCodeRange)", "stride == 1"})
        long doBMP(@SuppressWarnings("unused") AbstractTruffleString a, Object array, int offset, int length, @SuppressWarnings("unused") int stride, @SuppressWarnings("unused") Encoding encoding,
                        int fromIndex, @SuppressWarnings("unused") int knownCodeRange) {
            return StringAttributes.create(length, TStringOps.calcStringAttributesBMP(this, array, offset + (fromIndex << 1), length));
        }

        @Specialization(guards = {"isUTF8(encoding)", "!isFixedWidth(knownCodeRange)"})
        static long doUTF8(Node node, AbstractTruffleString a, Object array, int offset, int length, int stride, @SuppressWarnings("unused") Encoding encoding, int fromIndex, int knownCodeRange,
                        @Exclusive @Cached InlinedConditionProfile brokenProfile) {
            assert stride == 0;
            int off = offset + fromIndex;
            if (isValid(knownCodeRange) && a != null) {
                return TStringOps.calcStringAttributesUTF8(node, array, off, length, true, off + length == a.offset() + a.length(), brokenProfile);
            } else {
                return TStringOps.calcStringAttributesUTF8(node, array, off, length, false, false, brokenProfile);
            }
        }

        @Specialization(guards = {"isUTF16(encoding)", "isValid(knownCodeRange)"})
        long doUTF16Valid(@SuppressWarnings("unused") AbstractTruffleString a, Object array, int offset, int length, int stride, @SuppressWarnings("unused") Encoding encoding, int fromIndex,
                        @SuppressWarnings("unused") int knownCodeRange) {
            assert stride == 1;
            return TStringOps.calcStringAttributesUTF16(this, array, offset + (fromIndex << 1), length, true);
        }

        @Specialization(guards = {"isUTF16(encoding)", "isBroken(knownCodeRange)"})
        long doUTF16Unknown(@SuppressWarnings("unused") AbstractTruffleString a, Object array, int offset, int length, int stride, @SuppressWarnings("unused") Encoding encoding, int fromIndex,
                        @SuppressWarnings("unused") int knownCodeRange) {
            assert stride == 1;
            return TStringOps.calcStringAttributesUTF16(this, array, offset + (fromIndex << 1), length, false);
        }

        @Specialization(guards = {"stride == 2"})
        long doUTF32(@SuppressWarnings("unused") AbstractTruffleString a, Object array, int offset, int length, @SuppressWarnings("unused") int stride, Encoding encoding, int fromIndex,
                        @SuppressWarnings("unused") int knownCodeRange) {
            assert isUTF32(encoding);
            return StringAttributes.create(length, TStringOps.calcStringAttributesUTF32(this, array, offset + (fromIndex << 2), length));
        }

        @Specialization(guards = "isUnsupportedEncoding(encoding)")
        static long doGeneric(Node node, @SuppressWarnings("unused") AbstractTruffleString a, Object array, int offset, int length, int stride, Encoding encoding, int fromIndex,
                        @SuppressWarnings("unused") int knownCodeRange,
                        @Exclusive @Cached InlinedConditionProfile validCharacterProfile,
                        @Exclusive @Cached InlinedConditionProfile fixedWidthProfile) {
            assert stride == 0;
            return JCodings.getInstance().calcStringAttributes(node, array, offset, length, encoding, fromIndex, validCharacterProfile, fixedWidthProfile);
        }
    }

    abstract static class ParseIntNode extends AbstractInternalNode {

        abstract int execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int radix) throws TruffleString.NumberFormatException;

        @SuppressWarnings("unused")
        @Specialization(guards = {"is7Bit(codeRangeA)", "compaction == cachedCompaction"}, limit = Stride.STRIDE_CACHE_LIMIT, unroll = Stride.STRIDE_UNROLL)
        static int do7Bit(Node node, AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int radix,
                        @Bind("fromStride(a.stride())") CompactionLevel compaction,
                        @Cached("compaction") CompactionLevel cachedCompaction,
                        @Cached InlinedBranchProfile errorProfile) throws TruffleString.NumberFormatException {
            return NumberConversion.parseInt7Bit(node, a, arrayA, cachedCompaction.getStride(), radix, errorProfile);
        }

        @Specialization(guards = "!is7Bit(codeRangeA)")
        static int doGeneric(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int radix,
                        @Cached TruffleStringIterator.InternalNextNode nextNode,
                        @Cached InlinedBranchProfile errorProfile) throws TruffleString.NumberFormatException {
            return NumberConversion.parseInt(node, AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding), radix, errorProfile, nextNode);
        }
    }

    abstract static class ParseLongNode extends AbstractInternalNode {

        abstract long execute(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int radix) throws TruffleString.NumberFormatException;

        @SuppressWarnings("unused")
        @Specialization(guards = {"is7Bit(codeRangeA)", "compaction == cachedCompaction"}, limit = Stride.STRIDE_CACHE_LIMIT, unroll = Stride.STRIDE_UNROLL)
        static long do7Bit(Node node, AbstractTruffleString a, Object arrayA, @SuppressWarnings("unused") int codeRangeA, @SuppressWarnings("unused") Encoding encoding, int radix,
                        @Bind("fromStride(a.stride())") CompactionLevel compaction,
                        @Cached("compaction") CompactionLevel cachedCompaction,
                        @Cached InlinedBranchProfile errorProfile) throws TruffleString.NumberFormatException {
            return NumberConversion.parseLong7Bit(node, a, arrayA, cachedCompaction.getStride(), radix, errorProfile);
        }

        @Specialization(guards = "!is7Bit(codeRangeA)")
        static long parseLong(Node node, AbstractTruffleString a, Object arrayA, int codeRangeA, Encoding encoding, int radix,
                        @Cached TruffleStringIterator.InternalNextNode nextNode,
                        @Cached InlinedBranchProfile errorProfile) throws TruffleString.NumberFormatException {
            return NumberConversion.parseLong(node, AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, encoding), radix, errorProfile, nextNode);
        }
    }

    abstract static class ParseDoubleNode extends AbstractInternalNode {

        abstract double execute(Node node, AbstractTruffleString a, Object arrayA) throws TruffleString.NumberFormatException;

        @SuppressWarnings("unused")
        @Specialization(guards = "compaction == cachedCompaction", limit = Stride.STRIDE_CACHE_LIMIT, unroll = Stride.STRIDE_UNROLL)
        static double doParse(Node node, AbstractTruffleString a, Object arrayA,
                        @Bind("fromStride(a.stride())") CompactionLevel compaction,
                        @Cached("compaction") CompactionLevel cachedCompaction,
                        @Cached InlinedBranchProfile errorProfile) throws TruffleString.NumberFormatException {
            return FastDoubleParser.parseDouble(node, a, arrayA, cachedCompaction.getStride(), 0, a.length(), errorProfile);
        }
    }

    abstract static class FromJavaStringUTF16Node extends AbstractInternalNode {

        FromJavaStringUTF16Node() {
        }

        abstract TruffleString execute(Node node, String value, int charOffset, int length, boolean copy);

        @Specialization
        static TruffleString doNonEmpty(Node node, String javaString, int charOffset, int length, final boolean copy,
                        @Cached InlinedConditionProfile utf16CompactProfile) {
            checkArrayRange(javaString.length(), charOffset, length);
            CompilerAsserts.partialEvaluationConstant(copy);
            if (length == 0) {
                return Encoding.UTF_16.getEmpty();
            }
            final byte[] array;
            final int offset;
            final int stride;
            final int codeRange;
            final int codePointLength;
            int strideJS = TStringUnsafe.getJavaStringStride(javaString);
            int offsetJS = charOffset << 1;
            byte[] arrayJS = TStringUnsafe.getJavaStringArray(javaString);
            if (utf16CompactProfile.profile(node, strideJS == 0)) {
                if (length == 1) {
                    return TStringConstants.getSingleByte(Encoding.UTF_16, Byte.toUnsignedInt(arrayJS[charOffset]));
                }
                codeRange = TSCodeRange.markImprecise(TSCodeRange.get8Bit());
                codePointLength = length;
            } else {
                assert strideJS == 1;
                if (length == 1 && TStringOps.readFromByteArray(arrayJS, 1, charOffset) <= 0xff) {
                    return TStringConstants.getSingleByte(Encoding.UTF_16, TStringOps.readFromByteArray(arrayJS, 1, charOffset));
                }
                if (TStringUnsafe.COMPACT_STRINGS_ENABLED && length == javaString.length()) {
                    codePointLength = -1;
                    codeRange = TSCodeRange.markImprecise(TSCodeRange.getBrokenMultiByte());
                } else {
                    final long attrs = TStringOps.calcStringAttributesUTF16(node, arrayJS, offsetJS, length, false);
                    codePointLength = StringAttributes.getCodePointLength(attrs);
                    codeRange = StringAttributes.getCodeRange(attrs);
                }
            }
            if (TStringUnsafe.COMPACT_STRINGS_ENABLED && (!copy || length == javaString.length())) {
                stride = strideJS;
                offset = offsetJS;
                array = arrayJS;
            } else {
                stride = Stride.fromCodeRangeUTF16AllowImprecise(codeRange);
                array = new byte[length << stride];
                offset = 0;
                if (strideJS == 1 && stride == 0) {
                    TStringOps.arraycopyWithStride(node, arrayJS, offsetJS, 1, 0, array, offset, 0, 0, length);
                } else {
                    assert strideJS == stride;
                    TStringOps.arraycopyWithStride(node, arrayJS, offsetJS, 0, 0, array, offset, 0, 0, length << stride);
                }
            }
            TruffleString ret = TruffleString.createFromArray(array, offset, length, stride, Encoding.UTF_16, codePointLength, codeRange);
            if (length == javaString.length()) {
                assert charOffset == 0;
                TruffleString wrapped = TruffleString.createWrapJavaString(javaString, codePointLength, codeRange);
                ret.cacheInsertFirstBeforePublished(wrapped);
            }
            return ret;
        }
    }

    abstract static class CreateJavaStringNode extends AbstractInternalNode {

        abstract String execute(Node node, AbstractTruffleString a, Object arrayA);

        @Specialization
        static String createJavaString(Node node, AbstractTruffleString a, Object arrayA,
                        @Cached InlinedConditionProfile reuseProfile) {
            assert TSCodeRange.is7Or8Bit(a.codeRange()) || TSCodeRange.isPrecise(a.codeRange());
            assert a.isLooselyCompatibleTo(Encoding.UTF_16);
            final int stride = TStringUnsafe.COMPACT_STRINGS_ENABLED ? Stride.fromCodeRangeUTF16(a.codeRange()) : 1;
            final byte[] bytes;
            if (reuseProfile.profile(node, a instanceof TruffleString && arrayA instanceof byte[] && a.length() << a.stride() == ((byte[]) arrayA).length && a.stride() == stride)) {
                assert a.offset() == 0;
                bytes = (byte[]) arrayA;
            } else {
                bytes = new byte[a.length() << stride];
                TStringOps.arraycopyWithStride(node,
                                arrayA, a.offset(), a.stride(), 0,
                                bytes, 0, stride, 0, a.length());
            }
            return TStringUnsafe.createJavaString(bytes, stride);
        }

    }

    abstract static class TransCodeNode extends AbstractInternalNode {

        abstract TruffleString execute(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding targetEncoding);

        @Specialization
        static TruffleString transcode(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding targetEncoding,
                        @Cached InlinedConditionProfile asciiBytesInvalidProfile,
                        @Cached TransCodeIntlNode transCodeIntlNode) {
            if (AbstractTruffleString.DEBUG_STRICT_ENCODING_CHECKS && a.isImmutable() && TSCodeRange.isMoreRestrictiveThan(codeRangeA, targetEncoding.maxCompatibleCodeRange)) {
                if (a.stride() == 0) {
                    return TruffleString.createFromArray(arrayA, a.offset(), a.length(), 0, targetEncoding, codePointLengthA, codeRangeA, false);
                }
                int targetStride = Stride.fromCodeRange(codeRangeA, targetEncoding);
                Object array = TStringOps.arraycopyOfWithStride(node, arrayA, a.offset(), a.length(), a.stride(), a.length(), targetStride);
                return TruffleString.createFromArray(array, 0, a.length(), targetStride, targetEncoding, codePointLengthA, codeRangeA, false);
            }
            assert a.length() > 0;
            if (asciiBytesInvalidProfile.profile(node, (isAscii(a.encoding()) || isBytes(a.encoding())) && isSupportedEncoding(targetEncoding))) {
                assert (isBrokenFixedWidth(codeRangeA) || isValidFixedWidth(codeRangeA)) && isStride0(a) && codePointLengthA == a.length();
                byte[] buffer = new byte[codePointLengthA];
                for (int i = 0; i < buffer.length; i++) {
                    int c = readS0(a, arrayA, i);
                    buffer[i] = (byte) (c > 0x7f ? '?' : c);
                    TStringConstants.truffleSafePointPoll(node, i + 1);
                }
                return TransCodeIntlNode.create(a, buffer, buffer.length, 0, targetEncoding, codePointLengthA, TSCodeRange.get7Bit(), true);
            } else {
                return transCodeIntlNode.execute(node, a, arrayA, codePointLengthA, codeRangeA, Encoding.get(a.encoding()), targetEncoding);
            }
        }
    }

    abstract static class TransCodeIntlNode extends AbstractInternalNode {

        abstract TruffleString execute(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding, Encoding targetEncoding);

        @SuppressWarnings("unused")
        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "isAscii(targetEncoding) || isBytes(targetEncoding)"})
        static TruffleString targetAscii(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding, Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode) {
            assert !is7Bit(codeRangeA);
            byte[] buffer = new byte[codePointLengthA];
            TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, sourceEncoding);
            int i = 0;
            while (it.hasNext()) {
                int codepoint = iteratorNextNode.execute(node, it);
                buffer[i++] = codepoint > 0x7f ? (byte) '?' : (byte) codepoint;
                TStringConstants.truffleSafePointPoll(node, i);
            }
            return create(a, buffer, buffer.length, 0, targetEncoding, codePointLengthA, TSCodeRange.get7Bit(), true);
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "isLatin1(targetEncoding)"})
        static TruffleString latin1Transcode(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding, Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode) {
            assert !is7Or8Bit(codeRangeA);
            byte[] buffer = new byte[codePointLengthA];
            TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, sourceEncoding);
            int codeRange = TSCodeRange.get7Bit();
            int i = 0;
            while (it.hasNext()) {
                int codepoint = iteratorNextNode.execute(node, it);
                byte latin1 = codepoint > 0xff ? (byte) '?' : (byte) codepoint;
                buffer[i++] = latin1;
                if (latin1 < 0) {
                    codeRange = TSCodeRange.get8Bit();
                }
                TStringConstants.truffleSafePointPoll(node, i);
            }
            return create(a, buffer, codePointLengthA, 0, Encoding.ISO_8859_1, codePointLengthA, codeRange, true);
        }

        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "!isLarge(codePointLengthA)", "isUTF8(targetEncoding)"})
        static TruffleString utf8TranscodeRegular(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode,
                        @Cached @Shared("brokenProfile") InlinedConditionProfile brokenProfile,
                        @Cached @Shared("outOfMemoryProfile") InlinedBranchProfile outOfMemoryProfile) {
            return utf8Transcode(node, a, arrayA, codePointLengthA, codeRangeA, sourceEncoding, iteratorNextNode, false, brokenProfile, outOfMemoryProfile);
        }

        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "isLarge(codePointLengthA)", "isUTF8(targetEncoding)"})
        static TruffleString utf8TranscodeLarge(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode,
                        @Cached @Shared("brokenProfile") InlinedConditionProfile brokenProfile,
                        @Cached @Shared("outOfMemoryProfile") InlinedBranchProfile outOfMemoryProfile) {
            return utf8Transcode(node, a, arrayA, codePointLengthA, codeRangeA, sourceEncoding, iteratorNextNode, true, brokenProfile, outOfMemoryProfile);
        }

        static boolean isLarge(int codePointLengthA) {
            return codePointLengthA > TStringConstants.MAX_ARRAY_SIZE / 4;
        }

        private static TruffleString utf8Transcode(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        TruffleStringIterator.InternalNextNode iteratorNextNode, boolean isLarge, InlinedConditionProfile brokenProfile, InlinedBranchProfile outOfMemoryProfile) {
            assert !is7Bit(codeRangeA);
            TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, sourceEncoding);
            byte[] buffer = new byte[isLarge ? TStringConstants.MAX_ARRAY_SIZE : codePointLengthA * 4];
            int codeRange = TSCodeRange.getValidMultiByte();
            int length = 0;
            int loopCount = 0;
            while (it.hasNext()) {
                int codepoint = iteratorNextNode.execute(node, it);
                if (Encodings.isUTF16Surrogate(codepoint) || Integer.toUnsignedLong(codepoint) > Character.MAX_CODE_POINT) {
                    codeRange = TSCodeRange.getBrokenMultiByte();
                    codepoint = Encodings.invalidCodepoint();
                }
                int n = Encodings.utf8EncodedSize(codepoint);
                assert isLarge || length + n <= buffer.length;
                if (isLarge && length > TStringConstants.MAX_ARRAY_SIZE - n) {
                    outOfMemoryProfile.enter(node);
                    throw InternalErrors.outOfMemory();
                }
                Encodings.utf8Encode(codepoint, buffer, length, n);
                length += n;
                TStringConstants.truffleSafePointPoll(node, ++loopCount);
            }
            final int codePointLength;
            if (isBrokenMultiByte(codeRange)) {
                long attrs = TStringOps.calcStringAttributesUTF8(node, buffer, 0, length, false, false, brokenProfile);
                codePointLength = StringAttributes.getCodePointLength(attrs);
                codeRange = StringAttributes.getCodeRange(attrs);
            } else {
                codePointLength = codePointLengthA;
            }
            return create(a, Arrays.copyOf(buffer, length), length, 0, Encoding.UTF_8, codePointLength, codeRange, isBrokenMultiByte(codeRange));
        }

        @Specialization(guards = {"isUTF32(sourceEncoding)", "isUTF16(targetEncoding)"})
        TruffleString utf16Fixed32Bit(AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, @SuppressWarnings("unused") Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding) {
            assert TStringGuards.isValidFixedWidth(codeRangeA) || TStringGuards.isBrokenFixedWidth(codeRangeA);
            assert isStride2(a);
            byte[] buffer = new byte[codePointLengthA * 4];
            int length = 0;
            int codeRange = TStringGuards.isValidFixedWidth(codeRangeA) ? TSCodeRange.getValidMultiByte() : TSCodeRange.getBrokenMultiByte();
            for (int i = 0; i < a.length(); i++) {
                int codepoint = TStringOps.readS2(a, arrayA, i);
                length += Encodings.utf16Encode(codepoint, buffer, length);
                TStringConstants.truffleSafePointPoll(this, i + 1);
            }
            final int codePointLength;
            if (isBrokenMultiByte(codeRange)) {
                long attrs = TStringOps.calcStringAttributesUTF16(this, buffer, 0, length, false);
                codePointLength = StringAttributes.getCodePointLength(attrs);
                codeRange = StringAttributes.getCodeRange(attrs);
            } else {
                codePointLength = codePointLengthA;
            }
            return create(a, Arrays.copyOf(buffer, length * 2), length, 1, Encoding.UTF_16, codePointLength, codeRange, isBrokenMultiByte(codeRange));
        }

        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "!isFixedWidth(codeRangeA)", "!isLarge(codePointLengthA)", "isUTF16(targetEncoding)"})
        static TruffleString utf16TranscodeRegular(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode,
                        @Cached @Shared("outOfMemoryProfile") InlinedBranchProfile outOfMemoryProfile) {
            return utf16Transcode(node, a, arrayA, codePointLengthA, codeRangeA, sourceEncoding, iteratorNextNode, false, outOfMemoryProfile);
        }

        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "!isFixedWidth(codeRangeA)", "isLarge(codePointLengthA)", "isUTF16(targetEncoding)"})
        static TruffleString utf16TranscodeLarge(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode,
                        @Cached @Shared("outOfMemoryProfile") InlinedBranchProfile outOfMemoryProfile) {
            return utf16Transcode(node, a, arrayA, codePointLengthA, codeRangeA, sourceEncoding, iteratorNextNode, true, outOfMemoryProfile);
        }

        private static TruffleString utf16Transcode(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        TruffleStringIterator.InternalNextNode iteratorNextNode, boolean isLarge, InlinedBranchProfile outOfMemoryProfile) {
            assert TStringGuards.isValidOrBrokenMultiByte(codeRangeA);
            TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, sourceEncoding);
            byte[] buffer = new byte[codePointLengthA];
            int codePointLength = codePointLengthA;
            int length = 0;
            int codeRange = TSCodeRange.get7Bit();
            while (it.hasNext()) {
                int curIndex = it.getRawIndex();
                int codepoint = iteratorNextNode.execute(node, it);
                if (codepoint > 0xff) {
                    buffer = TStringOps.arraycopyOfWithStride(node, buffer, 0, length, 0, codePointLengthA, 1);
                    codeRange = TSCodeRange.get16Bit();
                    it.setRawIndex(curIndex);
                    break;
                }
                if (codepoint > 0x7f) {
                    codeRange = TSCodeRange.get8Bit();
                }
                buffer[length++] = (byte) codepoint;
                TStringConstants.truffleSafePointPoll(node, length);
            }
            if (!it.hasNext()) {
                assert length == codePointLengthA;
                return create(a, buffer, length, 0, Encoding.UTF_16, codePointLengthA, codeRange, false);
            }
            while (it.hasNext()) {
                int curIndex = it.getRawIndex();
                int codepoint = iteratorNextNode.execute(node, it);
                if (codepoint > 0xffff) {
                    buffer = Arrays.copyOf(buffer, isLarge ? TStringConstants.MAX_ARRAY_SIZE : buffer.length * 2);
                    codeRange = TSCodeRange.commonCodeRange(codeRange, TSCodeRange.getValidMultiByte());
                    it.setRawIndex(curIndex);
                    break;
                }
                if (Encodings.isUTF16Surrogate(codepoint)) {
                    codeRange = TSCodeRange.getBrokenMultiByte();
                }
                writeToByteArray(buffer, 1, length++, codepoint);
                TStringConstants.truffleSafePointPoll(node, length);
            }
            if (!it.hasNext()) {
                assert length == codePointLengthA;
                if (isBrokenMultiByte(codeRange)) {
                    long attrs = TStringOps.calcStringAttributesUTF16(node, buffer, 0, length, false);
                    codePointLength = StringAttributes.getCodePointLength(attrs);
                    codeRange = StringAttributes.getCodeRange(attrs);
                }
                return create(a, buffer, length, 1, Encoding.UTF_16, codePointLength, codeRange, isBrokenMultiByte(codeRange));
            }
            int loopCount = 0;
            while (it.hasNext()) {
                int codepoint = iteratorNextNode.execute(node, it);
                if (Encodings.isUTF16Surrogate(codepoint) || Integer.toUnsignedLong(codepoint) > Character.MAX_CODE_POINT) {
                    codeRange = TSCodeRange.getBrokenMultiByte();
                }
                if (isLarge && length + Encodings.utf16EncodedSize(codepoint) > TStringConstants.MAX_ARRAY_SIZE_S1) {
                    outOfMemoryProfile.enter(node);
                    throw InternalErrors.outOfMemory();
                }
                length += Encodings.utf16Encode(codepoint, buffer, length);
                TStringConstants.truffleSafePointPoll(node, ++loopCount);
            }
            if (isBrokenMultiByte(codeRange)) {
                long attrs = TStringOps.calcStringAttributesUTF16(node, buffer, 0, length, false);
                codePointLength = StringAttributes.getCodePointLength(attrs);
                codeRange = StringAttributes.getCodeRange(attrs);
            }
            return create(a, Arrays.copyOf(buffer, length * 2), length, 1, Encoding.UTF_16, codePointLength, codeRange, isBrokenMultiByte(codeRange));
        }

        @Specialization(guards = {"!isUTF16(sourceEncoding)", "isSupportedEncoding(sourceEncoding)", "!isFixedWidth(codeRangeA)", "!isLarge(codePointLengthA)", "isUTF32(targetEncoding)"})
        static TruffleString utf32TranscodeRegular(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode) {
            return utf32Transcode(node, a, arrayA, codePointLengthA, codeRangeA, sourceEncoding, iteratorNextNode);
        }

        @SuppressWarnings("unused")
        @Specialization(guards = {"isSupportedEncoding(sourceEncoding)", "!isFixedWidth(codeRangeA)", "isLarge(codePointLengthA)", "isUTF32(targetEncoding)"})
        static TruffleString utf32TranscodeLarge(AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding, Encoding targetEncoding) {
            throw InternalErrors.outOfMemory();
        }

        @Specialization(guards = {"isUTF16(sourceEncoding)", "!isFixedWidth(codeRangeA)", "!isLarge(codePointLengthA)", "isUTF32(targetEncoding)"})
        static TruffleString utf32TranscodeUTF16(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        @SuppressWarnings("unused") Encoding targetEncoding,
                        @Cached @Shared("iteratorNextNode") TruffleStringIterator.InternalNextNode iteratorNextNode) {
            assert containsSurrogates(a);
            TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, sourceEncoding);
            byte[] buffer = new byte[codePointLengthA << 2];
            int length = 0;
            while (it.hasNext()) {
                writeToByteArray(buffer, 2, length++, iteratorNextNode.execute(node, it));
                TStringConstants.truffleSafePointPoll(node, length);
            }
            assert length == codePointLengthA;
            boolean isBroken = isBrokenMultiByte(codeRangeA);
            return create(a, buffer, length, 2, Encoding.UTF_32, codePointLengthA, isBroken ? TSCodeRange.getBrokenFixedWidth() : TSCodeRange.getValidFixedWidth(), isBroken);
        }

        private static TruffleString utf32Transcode(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, int codeRangeA, Encoding sourceEncoding,
                        TruffleStringIterator.InternalNextNode iteratorNextNode) {
            assert TStringGuards.isValidOrBrokenMultiByte(codeRangeA);
            TruffleStringIterator it = AbstractTruffleString.forwardIterator(a, arrayA, codeRangeA, sourceEncoding);
            byte[] buffer = new byte[codePointLengthA];
            int length = 0;
            int codeRange = TSCodeRange.get7Bit();
            int codepoint = 0;
            while (it.hasNext()) {
                int curIndex = it.getRawIndex();
                codepoint = iteratorNextNode.execute(node, it);
                if (codepoint > 0xff) {
                    if (Encodings.isUTF16Surrogate(codepoint)) {
                        buffer = TStringOps.arraycopyOfWithStride(node, buffer, 0, length, 0, codePointLengthA, 2);
                        codeRange = TSCodeRange.getBrokenFixedWidth();
                    } else {
                        buffer = TStringOps.arraycopyOfWithStride(node, buffer, 0, length, 0, codePointLengthA, 1);
                        codeRange = TSCodeRange.get16Bit();
                    }
                    it.setRawIndex(curIndex);
                    break;
                }
                if (codepoint > 0x7f) {
                    codeRange = TSCodeRange.get8Bit();
                }
                buffer[length++] = (byte) codepoint;
                TStringConstants.truffleSafePointPoll(node, length);
            }
            if (!it.hasNext()) {
                assert length == codePointLengthA;
                return create(a, buffer, length, 0, Encoding.UTF_32, codePointLengthA, codeRange, isBrokenFixedWidth(codeRange));
            }
            if (is16Bit(codeRange)) {
                while (it.hasNext()) {
                    int curIndex = it.getRawIndex();
                    codepoint = iteratorNextNode.execute(node, it);
                    if (codepoint > 0xffff || Encodings.isUTF16Surrogate(codepoint)) {
                        buffer = TStringOps.arraycopyOfWithStride(node, buffer, 0, length, 1, codePointLengthA, 2);
                        codeRange = Encodings.isValidUnicodeCodepoint(codepoint) ? TSCodeRange.getValidFixedWidth() : TSCodeRange.getBrokenFixedWidth();
                        it.setRawIndex(curIndex);
                        break;
                    }
                    writeToByteArray(buffer, 1, length++, codepoint);
                    TStringConstants.truffleSafePointPoll(node, length);
                }
            }
            if (!it.hasNext()) {
                assert length == codePointLengthA;
                return create(a, buffer, length, 1, Encoding.UTF_32, codePointLengthA, codeRange, isBrokenFixedWidth(codeRange));
            }
            while (it.hasNext()) {
                codepoint = iteratorNextNode.execute(node, it);
                if (!Encodings.isValidUnicodeCodepoint(codepoint)) {
                    codeRange = TSCodeRange.getBrokenFixedWidth();
                }
                writeToByteArray(buffer, 2, length++, codepoint);
                TStringConstants.truffleSafePointPoll(node, length);
            }
            return create(a, buffer, length, 2, Encoding.UTF_32, codePointLengthA, codeRange, isBrokenFixedWidth(codeRange));
        }

        @Specialization(guards = {"isUnsupportedEncoding(sourceEncoding) || isUnsupportedEncoding(targetEncoding)"})
        static TruffleString unsupported(Node node, AbstractTruffleString a, Object arrayA, int codePointLengthA, @SuppressWarnings("unused") int codeRangeA,
                        @SuppressWarnings("unused") Encoding sourceEncoding,
                        Encoding targetEncoding,
                        @Shared("outOfMemoryProfile") @Cached InlinedBranchProfile outOfMemoryProfile,
                        @Exclusive @Cached InlinedConditionProfile nativeProfile,
                        @Cached FromBufferWithStringCompactionNode fromBufferWithStringCompactionNode) {
            return JCodings.getInstance().transcode(node, a, arrayA, codePointLengthA, targetEncoding, outOfMemoryProfile, nativeProfile, fromBufferWithStringCompactionNode);
        }

        private static TruffleString create(AbstractTruffleString a, byte[] buffer, int length, int stride, Encoding encoding, int codePointLength, int codeRange, boolean isCacheHead) {
            return TruffleString.createFromByteArray(buffer, length, stride, encoding, codePointLength, codeRange, isCacheHead || a.isMutable());
        }

        @TruffleBoundary
        private static boolean containsSurrogates(AbstractTruffleString a) {
            CompilerAsserts.neverPartOfCompilation();
            for (int i = 0; i < a.length(); i++) {
                if (Encodings.isUTF16Surrogate(a.readCharUTF16Uncached(i))) {
                    return true;
                }
            }
            return false;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy