Truffle文字列ガイド

Truffle文字列はTruffleのプリミティブ文字列型で、言語間で共有できます。言語の実装者は、相互運用性とパフォーマンスの向上のために、Truffle文字列を言語の文字列型として使用することをお薦めします。

TruffleStringは多数の文字列エンコーディングをサポートしますが、最も一般的に使用される次のものに対して特に最適化されています:

TruffleString API

TruffleStringによって公開されるすべての操作は、内部Nodeおよび静的メソッドまたはインスタンス・メソッドとして提供されています。可能な場合は、指定されたノードを使用する必要があります。静的/インスタンス・メソッドは、それぞれのノードのキャッシュされていないバージョンを実行するための簡易な方法であるためです。すべてのノードの名前は{NameOfOperation}Nodeで、すべての簡易メソッドの名前は{nameOfOperation}Uncachedです。

一部の操作では、特定の文字列プロパティの遅延連結や遅延評価など、遅延評価がサポートされています。これらの操作のほとんどはパラメータboolean lazyを提供しており、これによりユーザーはコールサイトごとに遅延評価を有効または無効にできます。

CodePointAtIndexなどの索引値を扱う操作は、コードポイントベースの索引付けとバイトベースの索引付けの2つのバリアントで使用できます。バイトベースの索引付けは、操作名にByteIndex接尾辞または接頭辞を付けて示されます。それ以外の場合、索引はコードポイントベースです。たとえば、CodePointAtIndexの索引パラメータはコードポイントベースですが、CodePointAtByteIndexはバイトベースの索引を使用します。

現在使用可能な操作のリストを次に示し、カテゴリ別にグループ化しています。

新しいTruffleStringの作成:

問合せ文字列プロパティ:

比較:

変換:

コードポイントおよびバイトへのアクセス:

検索:

組合せ:

インスタンス化

TruffleStringは、コードポイント、数値、プリミティブ配列またはjava.lang.Stringから作成できます。

任意のエンコーディングの文字列はTruffleString.FromByteArrayNodeで作成でき、すでにエンコードされた文字列を含むバイト配列が想定されます。copyパラメータをfalseに設定することで、この操作をコピーできないようにします。

重要: TruffleStringsは、配列の内容が不変であると想定し、この操作のコピーできないバリアントに配列を渡した後にこれを変更しません。

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            @Cached TruffleString.FromByteArrayNode fromByteArrayNode) {
        byte[] array = {'a', 'b', 'c'};
        return fromByteArrayNode.execute(array, 0, array.length, TruffleString.Encoding.UTF_8, false);
    }
}

システムのエンディアンネスに関係なく、UTF-16およびUTF-32文字列を簡単に作成できるように、TruffleStringTruffleString.FromCharArrayUTF16NodeおよびTruffleString.FromIntArrayUTF32Nodeを提供します。

TruffleStringは、TruffleStringBuilderを介して作成することもできます。TruffleStringは、java.lang.StringBuilderと同等です。

TruffleStringBuilderは、次の操作を提供します:

次の例を参照してください:

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            @Cached TruffleStringBuilder.AppendCharUTF16Node appendCharNode,
            @Cached TruffleStringBuilder.AppendJavaStringUTF16Node appendJavaStringNode,
            @Cached TruffleStringBuilder.AppendIntNumberNode appendIntNumberNode,
            @Cached TruffleStringBuilder.AppendStringNode appendStringNode,
            @Cached TruffleString.FromCharArrayUTF16Node fromCharArrayUTF16Node,
            @Cached TruffleStringBuilder.AppendCodePointNode appendCodePointNode,
            @Cached TruffleStringBuilder.ToStringNode toStringNode) {
        TruffleStringBuilder sb = TruffleStringBuilder.create(TruffleString.Encoding.UTF_16);
        sb = appendCharNode.execute(sb, 'a');
        sb = appendJavaStringNode.execute(sb, "abc", /* fromIndex: */ 1, /* length: */ 2);
        sb = appendIntNumberNode.execute(sb, 123);
        TruffleString string = fromCharArrayUTF16Node.execute(new char[]{'x', 'y'}, /* fromIndex: */ 0, /* length: */ 2);
        sb = appendStringNode.execute(sb, string);
        sb = appendCodePointNode.execute(sb, 'z');
        return toStringNode.execute(sb); // string content: "abc123xyz"
    }
}

エンコーディング

すべてのTruffleStringは、インスタンス化時に設定される特定の内部エンコーディングでエンコードされます。

TruffleStringは、次のエンコーディング用に完全に最適化されています:

その他の多くのエンコーディングがサポートされていますが、完全に最適化されているわけではありません。これらを使用するには、Truffle language registrationneedsAllEncodings = trueを設定して有効にする必要があります。

TruffleStringの内部エンコーディングは公開されません。文字列のエンコーディングを問い合せるかわりに、言語は、文字列のエンコーディングが重要なすべてのメソッド(ほとんどすべての操作)にexpectedEncodingパラメータを渡す必要があります。これにより、文字列が両方のエンコーディングでバイト等価である場合に、エンコーディング間の変換時に文字列オブジェクトを再利用できます。次の例に示すように、文字列はSwitchEncodingNodeを使用して別のエンコーディングに変換できます:

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;

abstract static class SomeNode extends Node {

    @Specialization
    static void someSpecialization(
            @Cached TruffleString.FromJavaStringNode fromJavaStringNode,
            @Cached TruffleString.ReadByteNode readByteNode,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNode,
            @Cached TruffleString.ReadByteNode utf8ReadByteNode) {

        // instantiate a new UTF-16 string
        TruffleString utf16String = fromJavaStringNode.execute("foo", TruffleString.Encoding.UTF_16);

        // read a byte with expectedEncoding = UTF-16.
        // if the string is not byte-compatible with UTF-16, this method will throw an IllegalArgumentException
        System.out.printf("%x%n", readByteNode.execute(utf16String, /* byteIndex */ 0, TruffleString.Encoding.UTF_16));

        // convert to UTF-8.
        // note that utf8String may be reference-equal to utf16String!
        TruffleString utf8String = switchEncodingNode.execute(utf16String, TruffleString.Encoding.UTF_8);

        // read a byte with expectedEncoding = UTF-8
        // if the string is not byte-compatible with UTF-8, this method will throw an IllegalArgumentException
        System.out.printf("%x%n", utf8ReadByteNode.execute(utf8String, /* byteIndex */ 0, TruffleString.Encoding.UTF_8));
    }
}

エンコーディング間のバイト等価性は、UTF-16およびUTF-32で文字列圧縮を使用して決定されます。たとえば、圧縮されたUTF-16文字列はISO-8859-1とバイト等価で、すべての文字がASCII範囲(CodeRangeを参照)にある場合は、UTF-8とバイト等価です。

コードが正しくエンコーディングを切り替えているかどうかを確認するには、システム・プロパティtruffle.strings.debug-strict-encoding-checks=trueを使用してユニット・テストを実行します。これにより、エンコーディングを切り替えるときに文字列オブジェクトを再利用できなくなり、エンコーディング・チェックがより厳密になります。1つの文字列で動作するすべての操作では完全一致が強制されますが、2つの文字列で動作する操作ではバイト等価の再解釈が可能になります。

複数の文字列パラメータを持つすべてのTruffleString操作では、文字列が結果エンコーディングと互換性のあるエンコーディングである必要があります。したがって、文字列は同じエンコーディングである必要があります。また、コール元は両方の文字列が結果のエンコーディングと互換性があることを確認する必要があります。これにより、SwitchEncodingNodesが何も行わないことをすでに認識しているコール元は、フットプリントの理由からこれらをスキップできるようになります。

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringBuilder;

abstract static class SomeNode extends Node {

    @Specialization
    static boolean someSpecialization(
            TruffleString a,
            TruffleString b,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNodeA,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNodeB,
            @Cached TruffleString.EqualNode equalNode) {
        TruffleString utf8A = switchEncodingNodeA.execute(a, TruffleString.Encoding.UTF_8);
        TruffleString utf8B = switchEncodingNodeB.execute(b, TruffleString.Encoding.UTF_8);
        return equalNode.execute(utf8A, utf8B, TruffleString.Encoding.UTF_8);
    }
}

文字列のプロパティ

TruffleStringでは、次のプロパティが公開されます:

TruffleStringで公開されているすべてのプロパティを問い合せるには、次の例を参照してください:

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            TruffleString string,
            @Cached TruffleString.CodePointLengthNode codePointLengthNode,
            @Cached TruffleString.IsValidNode isValidNode,
            @Cached TruffleString.GetCodeRangeNode getCodeRangeNode,
            @Cached TruffleString.HashCodeNode hashCodeNode) {
        System.out.println("byte length: " + string.byteLength(TruffleString.Encoding.UTF_8));
        System.out.println("codepoint length: " + codePointLengthNode.execute(string, TruffleString.Encoding.UTF_8));
        System.out.println("is valid: " + isValidNode.execute(string));
        System.out.println("code range: " + getCodeRangeNode.execute(string));
        System.out.println("hash code: " + hashCodeNode.execute(string, TruffleString.Encoding.UTF_8));
    }
}

文字列の等式と比較式

TruffleStringオブジェクトは、EqualNodeを使用して等価性をチェックする必要があります。HashCodeNodeと同様に、等価比較は文字列のエンコーディングに依存するため、比較の前に文字列は常に共通エンコーディングに変換する必要があります。Object#equals(Object)EqualNodeと似ていますが、このメソッドにはexpectedEncodingパラメータがないため、文字列の共通エンコーディングが自動的に決定されます。文字列のエンコーディングが等しくない場合、TruffleStringは、文字列がもう一方の文字列のエンコーディングとバイナリ互換性があるかどうかを確認し、一致する場合はその内容と照合します。それ以外の場合、文字列は等しくないとみなされ、自動変換は適用されません。

TruffleStringhashCodeおよびequalsメソッドは文字列エンコーディングに依存するため、TruffleStringオブジェクトは、HashMapでキーとして使用する前に、常に共通エンコーディングに変換される必要があります。

TruffleStringには、3つの比較ノードCompareBytesNodeCompareCharsUTF16NodeおよびCompareIntsUTF32Nodeも用意されており、それぞれバイト単位、文字単位および整数単位で文字列を比較します。

連結

連結はConcatNodeを介して行われます。この操作では、両方の文字列がexpectedEncodingに存在する必要があります。これは、結果の文字列のエンコーディングでもあります。遅延連結は、lazyパラメータを介してサポートされます。2つの文字列が遅延して連結される場合、新しい文字列の内部配列の割当ておよび初期化は、別の操作がその配列への直接アクセスを必要とするまで遅延されます。このような「遅延連結文字列」のマテリアライズは、MaterializeNodeで明示的にトリガーできます。これは、次の例のように、ループ内の文字列にアクセスする前に便利です:

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static TruffleString someSpecialization(
            TruffleString utf8StringA,
            TruffleString utf8StringB,
            @Cached TruffleString.ConcatNode concatNode,
            @Cached TruffleString.MaterializeNode materializeNode,
            @Cached TruffleString.ReadByteNode readByteNode) {
        // lazy concatenation
        TruffleString lazyConcatenated = concatNode.execute(utf8StringA, utf8StringB, TruffleString.Encoding.UTF_8, /* lazy */ true);

        // explicit materialization
        TruffleString materialized = materializeNode.execute(lazyConcatenated, TruffleString.Encoding.UTF_8);

        int byteLength = materialized.byteLength(TruffleString.Encoding.UTF_8);
        for (int i = 0; i < byteLength; i++) {
            // string is guaranteed to be materialized here, so no slow materialization code can end up in this loop
            System.out.printf("%x%n", readByteNode.execute(materialized, i, TruffleString.Encoding.UTF_8));
        }
    }
}

部分文字列

部分文字列はSubstringNodeおよびSubstringByteIndexNodeを介して作成でき、それぞれコードポイントベースおよびバイトベースの索引を使用します。部分文字列もlazyにできます。つまり、結果の文字列に対して新しい配列は作成されませんが、かわりに親文字列の配列が再使用され、部分文字列ノードに渡されるオフセットと長さでアクセスされます。現在、遅延部分文字列の内部配列は切り捨てられません(文字列の正確な長さの新しい配列に置き換えられます)。この動作では、遅延部分文字列が作成されるたびにメモリー・リークが効率的に作成されます。これが問題となる可能性のある極端な例: サイズが100MBの文字列を指定すると、この文字列から作成された遅延部分文字列は、元の文字列がガベージ・コレクタによって解放された場合でも、100MBの配列を保持します。遅延部分文字列は慎重に使用してください。

java.lang.Stringとの相互運用性

TruffleStringは、java.lang.StringTruffleStringに変換するためのFromJavaStringNodeを提供します。TruffleStringからjava.lang.Stringに変換するには、ToJavaStringNodeを使用します。このノードは、必要に応じて文字列をUTF-16に内部的に変換し、その表現からjava.lang.Stringを作成します。

Object#toString()は、キャッシュされていないバージョンのToJavaStringNodeを使用して実装されるため、高速パスでは回避する必要があります。

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;

abstract static class SomeNode extends Node {

    @Specialization
    static void someSpecialization(
            @Cached TruffleString.FromJavaStringNode fromJavaStringNode,
            @Cached TruffleString.SwitchEncodingNode switchEncodingNode,
            @Cached TruffleString.ToJavaStringNode toJavaStringNode,
            @Cached TruffleString.ReadByteNode readByteNode) {
        TruffleString utf16String = fromJavaStringNode.execute("foo", TruffleString.Encoding.UTF_16);
        TruffleString utf8String = switchEncodingNode.execute(utf16String, TruffleString.Encoding.UTF_8);
        System.out.println(toJavaStringNode.execute(utf8String));
    }
}

TruffleStringは、デバッグのために#toStringDebug()も公開します。戻り値は指定されず、いつでも変更される可能性があるため、デバッグ以外にはこのメソッドを使用しないでください。

java.lang.Stringとの違い

java.lang.StringからTruffleStringに切り替えるときは、次の項目を考慮する必要があります:

コードポイント・イテレータ

TruffleStringは、文字列のコードポイントを反復する手段としてTruffleStringIteratorを提供します。CodePointAtIndexNodeは、特定のコードポイント索引と同等のバイト索引をすべてのコールで再計算する必要がある可能性があるため、特にUTF-8などの可変長幅エンコーディングに対して、ループでCodePointAtIndexNodeを使用する方法よりも、このメソッドを優先する必要があります。

例を参照してください:

import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.strings.TruffleStringIterator;

abstract static class SomeNode extends Node {

    @Specialization
    static void someSpecialization(
            TruffleString string,
            @Cached TruffleString.CreateCodePointIteratorNode createCodePointIteratorNode,
            @Cached TruffleStringIterator.NextNode nextNode,
            @Cached TruffleString.CodePointLengthNode codePointLengthNode,
            @Cached TruffleString.CodePointAtIndexNode codePointAtIndexNode) {

        // iterating over a string's codepoints using TruffleStringIterator
        TruffleStringIterator iterator = createCodePointIteratorNode.execute(string, TruffleString.Encoding.UTF_8);
        while (iterator.hasNext()) {
            System.out.printf("%x%n", nextNode.execute(iterator));
        }

        // suboptimal variant: using CodePointAtIndexNode in a loop
        int codePointLength = codePointLengthNode.execute(string, TruffleString.Encoding.UTF_8);
        for (int i = 0; i < codePointLength; i++) {
            // performance problem: codePointAtIndexNode may have to calculate the byte index corresponding
            // to codepoint index i for every loop iteration
            System.out.printf("%x%n", codePointAtIndexNode.execute(string, i, TruffleString.Encoding.UTF_8));
        }
    }
}

可変文字列

TruffleStringは、MutableTruffleStringと呼ばれる可変文字列バリアントも提供します。これは、TruffleStringのすべてのノードでも受け入れられます。MutableTruffleStringスレッドセーフではないため、WriteByteNodeを介して、内部バイト配列またはネイティブ・ポインタのバイトを上書きできます。内部配列またはネイティブ・ポインタの内容は外部で変更することもできますが、対応するMutableTruffleStringにはnotifyExternalMutation()を介して、これを通知する必要があります。MutableTruffleStringは、Truffle相互運用性の型ではなく、言語境界を渡す前に、TruffleString.AsTruffleStringを介して不変のTruffleStringに変換する必要があります。