GraalVM:多数のCとScalaが混在

あなたについては知りませんが、最近、新しいJavaテクノロジーに関する記事(Graal、Truffle、all-all-all)に感銘を受けました。 以前に言語を発明し、インタプリタを作成し、どの言語が良いか悲しいか、遅いか、ネイティブコンパイラおよび/またはJITがそれを書いたことを喜んだかのように見えますが、まだデバッガが必要です... LLVMがあり、ありがとうございます。 この記事を読んだ後、特別な種類の通訳を書いた後、その仕事は原則として完了することができるという(ややグロテスクな)印象を受けました。 「Make a Hurt」ボタンがコンパイラプログラマに利用可能になったという感覚。 いいえ、もちろん、JIT言語はゆっくりと起動し、ウォームアップする時間が必要です。 しかし、結局のところ、プログラマーの時間と資格も自由ではありません-アセンブラーですべてを書いたら、どのような情報技術の世界に生きるでしょうか? いいえ、もちろん、おそらくすべてが飛んだでしょう(プログラマーが指示を正しくレイアウトした場合)が、積極的に使用されるプログラムの全体的な複雑さについては、いくつかの疑問があります...







一般に、「プログラマーが費やす時間と結果として得られる製品の理想性(「手作業」)のジレンマでは、境界線を世紀の終わりに移動できることをよく理解しています。そのため、今日ではネイティブコードを最も純粋な形式でロードせずに従来のSQLiteライブラリを使用しようとしています。 Sulongと呼ばれるLLVM IRの既製のトリュフ言語実装を使用します。







免責事項:この記事は、初心者向けのプロ向けのストーリーではなく、テクノロジーに慣れようとしているだけの同じ初心者向けの一種の実験作業と見なすべきです。 そしてもう1つ:LLVM IRは完全にプラットフォームに依存しないと考えることはできません。







したがって、実際には、SQLiteのソースコードを取得し、接続コードを Java Scala(まあ、すみません...)、そしてGraalVMをバインディングとClangで取得します(これを使用してSQLiteをLLVM IRにコンパイルし、Scalaコードにロードします)。







すぐにすべてがUbuntu 18.04 LTS(64ビット)で発生することを予約してください。 Mac OS Xでは、大きな問題は発生しないと信じたいが、GraalとWindowsに必要なすべてのコンポーネントが存在するかどうかはわからない。 ただし、現在ではない場合でも、おそらく後で表示されます。







準備する



  1. 実験用のウサギSQLiteをダウンロードします(実際、記事に添付されているリポジトリにはすべてが揃っています)。
  2. 公式のSQLite In 5 Minutes Or Lessの記事を読んでください。 この場合のSQLiteは例としてのみ使用されるため、これがまさに必要なものです。 SQLiteのコンパイル方法も役立ちます。
  3. ここから GraalVM Community Edition ダウンロードして解凍します。 挑発に屈してPATH



    に追加することはお勧めしません。なぜ、自然と同じnode



    lli



    が必要なのですか?
  4. clangをインストールします-私の場合、通常のUbuntuリポジトリのClang 6です


また、私のテストプロジェクトでは、 sbtビルドシステムを使用します 。 プロジェクトを編集するには、個人的には標準のScalaプラグインを備えたIntelliJ Idea Communityを好みます。







そして、ここで私は個人的に最初のレーキを開始しました。GraalVMのWebサイトでは、これはJDKの単なるディレクトリであると書かれています。 そうだとすれば、それを単純なJDKとしてIdeaに追加します。 「1.8」とIdeaは言った。 うーん...奇妙な。 Grailがあるディレクトリのコンソールに入りbin/javac -version



本当に1.8と言いbin/javac -version



。 まあ、8なので、8は怖くない。 怖いのは、 org.graal



パッケージと、Ideaが見ないすべてのものであり、それらが必要なことです。 File -> Other Settings -> Default Project Structure...



に移動します。JDK設定で、クラスパスにjre/lib



およびjre/lib/ext



jarファイルが含まれていることがわかります。 すべてかどうか-チェックしませんでした。 そして、私たちがおそらく必要とするものは次のとおりです。







非表示のテキスト
 trosinenko@trosinenko-pc:~/tmp/graal/graalvm-1.0.0-rc1/jre/lib$ find . -name '*.jar' ./truffle/truffle-dsl-processor.jar ./truffle/truffle-api.jar ./truffle/truffle-nfi.jar ./truffle/locator.jar ./truffle/truffle-tck.jar ./polyglot/polyglot-native-api.jar ./boot/graaljs-scriptengine.jar ./boot/graal-sdk.jar ./management-agent.jar ./rt.jar ./jsse.jar ./resources.jar ./jvmci/jvmci-hotspot.jar ./jvmci/graal.jar ./jvmci/jvmci-api.jar ./installer/installer.jar ./ext/cldrdata.jar ./ext/sunjce_provider.jar ./ext/nashorn.jar ./ext/sunec.jar ./ext/zipfs.jar ./ext/sunpkcs11.jar ./ext/jaccess.jar ./ext/localedata.jar ./ext/dnsns.jar ./jce.jar ./svm/builder/objectfile.jar ./svm/builder/svm.jar ./svm/builder/pointsto.jar ./svm/library-support.jar ./graalvm/svm-driver.jar ./graalvm/launcher-common.jar ./graalvm/sulong-launcher.jar ./graalvm/graaljs-launcher.jar ./charsets.jar ./jvmci-services.jar ./security/policy/unlimited/US_export_policy.jar ./security/policy/unlimited/local_policy.jar ./security/policy/limited/US_export_policy.jar ./security/policy/limited/local_policy.jar
      
      





さらに、通常のJDKに追加されたものから判断すると、合計リストから多くのサブディレクトリが表示されます./security



は興味がありません。 この場合、「+」メソッド(expanded-directory-shift-click-click、OK)を使用して、サブディレクトリtruffle



polyglot



boot



およびgraalvm



の内容を追加しboot



。 何かが見つからない場合-追加します-それは毎日何かです...







Scalaでプロジェクトを作成する



だから、アイデアは調整されたようです。 sbtプロジェクトを作成してみましょう。 実際、落とし穴はなく、すべてが直感的です。主なことは、新しいJDKを示すことを忘れないことです。







新しいscalaファイルを作成して、 コピーアンドペースト 「ターゲット言語-LLVM」をクリックして、 Start Language Java



セクションのStart Language Java



Polyglotリファレンス」に記述されたコードを創造的にリサイクルします。







ちなみに、JavaScript、R、Ruby、さらにはCのような他の開始言語の豊富さに注意を払うことをお勧めしますが、これはまだ読んでいない完全に異なる話です...







 object SQLiteTest { val polyglot = Context.newBuilder().allowAllAccess(true).build() val file: File = ??? val source = Source.newBuilder("llvm", file).build() val cpart = polyglot.eval(source) ??? }
      
      





App



からobject



を継承したり、フィールドをプライベートにしたりすることはありません。Scalaコンソールからアクセスできます(その構成は既にプロジェクトに追加されています)。







その結果、ほぼ(80%も)5つの意味のある行からサンプルをロールアップしました。スツールに頼って最後に読む時間です。 何を書きましたか さらに、Javadocはmain()



呼び出すだけでなんとなく退屈であり、一般に、モデルの例はSQLiteなので、5行目ではなく、何を書くかを正確に理解する必要があります。 Polyglotリファレンスは素晴らしいですが、APIドキュメントが必要です。 それを見つけるには、リポジトリを歩き回る必要があります。readmeがあり、 Javadocへのリンクが含まれています







それまでの間、私たちに書かれたものの意味はまだ明確ではありません。主な質問への回答をJSに尋ねます。アイデアでScalaコンソール構成を選択し、...







 scala> import org.graalvm.polyglot.Context val polyglot = Context.newBuilder().allowAllAccess(true).build() polyglot.eval("js", "6 * 7") import org.graalvm.polyglot.Context scala> polyglot: org.graalvm.polyglot.Context = org.graalvm.polyglot.Context@68e24e7 scala> res0: org.graalvm.polyglot.Value = 42
      
      





...まあ、すべてがうまくいく、答えは。 そして、質問は読者に練習問題として残されます。







サンプルコードに戻りましょう。 polyglot



変数には、さまざまな言語が存在するコンテキストが含まれます。誰かがオフになり、誰かがオンになり、誰かがすでに遅延初期化されています。 この厳しい世界では、ファイルへのアクセスであっても許可を求める必要があるため、この例ではallowAllAccess(true)



を使用して制限オフにし allowAllAccess(true)









次に、LLVMビットコードを使用してSourceオブジェクトを作成します。 この「ソースコード」をダウンロードする言語とファイルを示します。 また、ソース行を直接使用することもできます(すでに確認済みです)、URL(JARファイルのリソースを含む)、およびjava.io.Reader



インスタンスのみを使用できます。 次に、結果のソースをコンテキストで計算し、 Valueを取得します。 このメソッドのドキュメントによると、 null



取得することはありませんが、 Value



があり、これはNull



です。 しかし、私たちはまだ特定のものをダウンロードする必要があるので...







SQLiteをビルドする



... SQLiteはOracleの代わりではなく、fopen()の代わりと考えてください

-SQLiteについて おわかりのように、SQLiteをGraalVMで実行できるようにすることは、開発者にとってひどい間違いではありませんでした。







SQLiteのドキュメントの前述の部分のヒントとGraalの指示に基づいて、コマンドラインを作成します。 ここにあります:







 clang -g -c -O1 -emit-llvm sqlite3. \ -DSQLITE_OMIT_LOAD_EXTENSION \ -DSQLITE_THREADSAFE=0 \ -o ../../sqlite3.bc
      
      





Sulong内でコードが正しく機能するには、少なくとも-O1



最適化が必要です。 SQLITE_OMIT_LOAD_EXTENSION



は名前-g



保存します(これらの2つのオプションおよびその他のオプションについて 、詳細をドキュメントを参照してください)。そして、pthreadとのリンクは不明であるため、スレッドセーフを無効にします(そうしないと、起動時に失敗します)。 以上です。







プロジェクトを立ち上げます



これで2行目に何か書くことができます。







  val file: File = new File("./sqlite3.bc")
      
      





これで、ライブラリから必要な関数を取得できます。







  val sqliteOpen = cpart.getMember("sqlite3_open") val sqliteExec = cpart.getMember("sqlite3_exec") val sqliteClose = cpart.getMember("sqlite3_close") val sqliteFree = cpart.getMember("sqlite3_free")
      
      





そしてそれは機能します-それらを正しい順序で呼び出すだけです-そしてそれはそれです! たとえば、 sqlite3_open



は、ファイル名と構造体へのポインターへのポインターが必要です(その内部は単語からはまったく興味がありません)。 うーん...と2番目の引数を形成する方法? ポインターを作成する関数が必要です-おそらくSulong固有のものです。 sulong.jar



をクラスパスに追加し、sbtシェル全体を再起動します。 そして何もない。 sbtプロジェクトのルート(アンマネージドjarの標準ディレクトリ)にlib



ディレクトリを作成して実行するのに、どれほど賢くなかったか







 find ../../graalvm-1.0.0-rc1/jre/languages/ -name '*.jar' -exec ln -s {} . \;
      
      





sbtリフレッシュのコンパイルが正常に完了した後。 しかし、何も始まりません...さて、クラスパスをその場所に戻しています。 一般に、5行目を追加すると思いました。 さて、5つのそれぞれについてJavadocを紹介します。これは短い記事であり、誰もが「Twitterはここにありますか?」







おそらく約3時間かかりましたsqlite3_open



番目の引数をsqlite3_open



関数でラップしようとしました...







ある時点でそれは私に気づきました:冗談のようにそれは必要です:「あなたは「戦争と平和」で何を始めますか、「 sqlite3.c



」を読んでください-あなたのレベルのためだけに」...したがって、 sqlite3.c



一時的にtest.c



置き換えられました







 void f(int *x) { *x = 42; }
      
      





さまざまなレベルのプライバシーのあらゆる種類のAPIタイプ変換にもう少しつまずいたので、私はそれを控えめに言っても疲れました。 冗談だけが私の頭に残った。 たとえば、「iOSは直感的なシステムです。理解するには、ロジックは無力です。直感が必要です。」 確かに、GraalVMの主な原則とこれすべて-すべてが透明でリラックスしている必要があります。そのため、FFIのわずかな経験を捨てて、便利なシステムの開発者として考える必要があります。 intのコンテナが必要です。 new java.lang.Integer(0)



-レコードをゼロアドレスに渡します。 しかし、Cの基本で教えられたことは、配列とゼロ要素へのポインターの違いは非常にarbitrary意的なことです。 実際、関数f



単純にintの配列を取り、値をnull要素に書き込みます。 私たちは試します:







 scala> val x = Array(new java.lang.Integer(12)) x: Array[Integer] = Array(12) scala> SQLiteTest.cpart.getMember("f").execute(x) res0: org.graalvm.polyglot.Value = LLVMTruffleObject(null:0) scala> x res1: Array[Integer] = Array(42)
      
      





あります!!!







ここでは、 query



関数をすばやく記述してこれで終了するように見えquery



が、2番目の引数としてそれを渡さないでください: Array(new Object)



Array(Array(new Object))



-LLVM内部のstrlen



を誓って動作を拒否します- O_Oビットコード(ちなみに、LLVM IRは、まあまあの通常のマシンコードとは異なり、非常に典型的です)。







n回後でも、 java.lang.String



Array[Byte]



execute()



最初の引数として渡すだけでexecute()



直感的すぎるという考えを捨てるのをやめ、 void f()



作り直しでこれを確認しました。







その結果、有望な名前__sulong_byte_array_to_native



持つ関数がSulongの組み込みバインダー( SQLiteTest.polyglot.getBindings("llvm")



)で見つかりました。 私たちは試します:







 val str = SQLiteTest.polyglot.getBindings("llvm") .getMember("__sulong_byte_array_to_native") .execute("toc.db".getBytes) val db = new Array[Object](1) SQLiteTest.sqliteOpen.execute(str, db) scala> str: org.graalvm.polyglot.Value = LLVMTruffleObject(null:139990504321152) scala> db: Array[Object] = Array(null) scala> res0: org.graalvm.polyglot.Value = 0 scala> val str = SQLiteTest.polyglot.getBindings("llvm") .getMember("__sulong_byte_array_to_native") .execute("toc123.db".getBytes) SQLiteTest.sqliteOpen.execute(str, db) str: org.graalvm.polyglot.Value = LLVMTruffleObject(null:139990517528064) scala> res1: org.graalvm.polyglot.Value = 0
      
      





動作します!!! ああ、なぜ間違ったファイル名でも機能するのですか?..息を止めて、プロジェクトディレクトリを調べます-そして、新しいtoc123.db



はすでにtoc123.db



ます。 やった!







それで、ScalaのSQLiteドキュメントから例を書き換えましょう:







  def query(dbFile: String, queryString: String): Unit = { val filenameStr = toCString(dbFile) val ptrToDb = new Array[Object](1) val rc = sqliteOpen.execute(filenameStr, ptrToDb) val db = ptrToDb.head if (rc.asInt() != 0) { println(s"Cannot open $dbFile: ${sqliteErrmsg.execute(db)}!") sqliteClose.execute(db) } else { val zErrMsg = new Array[Object](1) val execRc = sqliteExec.execute(db, toCString(queryString), ???, zErrMsg) if (execRc.asInt != 0) { val errorMessage = zErrMsg.head.asInstanceOf[Value] assert(errorMessage.isString) println(s"Cannot execute query: ${errorMessage.asString}") sqliteFree.execute(errorMessage) } sqliteClose.executeVoid(db) } }
      
      





障害は1つだけです-特定のコールバックです。 さて、誰も見ないとき、学生エンジニアはツリーからコアを説明し、JavaScriptでコールバックを記述しようとします。







  val callback = polyglot.eval("js", """function(unused, argc, argv, azColName) { | print("argc = " + argc); | print("argv = " + argv); | print("azColName = " + azColName); | return 0; |} """.stripMargin) // ... val execRc = sqliteExec.execute(db, toCString(queryString), callback, Int.box(0), zErrMsg)
      
      





そして、ここに私たちが得るものがあります:







 io.github.trosinenko.SQLiteTest.query("toc.db", "select * from toc;") argc = 5 argv = foreign {} azColName = foreign {} argc = 5 argv = foreign {} azColName = foreign {} argc = 5 argv = foreign {} azColName = foreign {}
      
      





まあ、魔法は十分ではありません。 さらに、 zErrMsg



エラーが発生した場合、それ自体がストリングに変換できない奇妙なオブジェクトが存在することがzErrMsg



ます。 さて、別のlib.bc



収集してロードし、そのソースlib.c



に以下を記述します。







 #include <polyglot.h> void *fromCString(const char *str) { return polyglot_from_string(str, "UTF-8"); }
      
      





なぜpolyglot_from_string



バインディングを介して直接利用できないのか、理解していなかったので、引き出してバインディングを実行します。







  val lib_fromCString = lib.getMember("fromCString") def fromCString(ptr: Value): String = { if (ptr.isNull) "<null>" else lib_fromCString.execute(ptr).asString() }
      
      





さて、エラーメッセージが返されることがわかりましたが、まだScalaでコールバックしましょう。







  val lib_copyToArray = lib.getMember("copy_to_array_from_pointers") val callback = new ProxyExecutable { override def execute(arguments: Value*): AnyRef = { val argc = arguments(1).asInt() val xargv = new Array[Long](argc) val xazColName = new Array[Long](argc) lib_copyToArray.execute(xargv, arguments(2)) lib_copyToArray.execute(xazColName, arguments(3)) (0 until argc) foreach { i => val name = fromCString(polyglot.asValue(xazColName(i) ^ 1)) val value = fromCString(polyglot.asValue(xargv(i) ^ 1)) println(s"$name = $value") } println("========================") Int.box(0) } }
      
      





同時に、そのような魔法を配列からPolyglot-ovskyにlib.cに追加します。







 void copy_to_array_from_pointers(void *arr, void **ptrs) { int size = polyglot_get_array_size(arr); for(int i = 0; i < size; ++i) { polyglot_set_array_element(arr, i, ((uintptr_t)ptrs[i]) ^ 1); } }
      
      





ポインターに注意してください^ 1-誰かがpolyglot_set_array_element



すぎるので、これが必要です:すなわち、 polyglot_set_array_element



は、プリミティブ型とPolyglot値へのポインターを受け入れる正確に3つの引数を持つ可変長関数です。 その結果、動作します:







 io.github.atrosinenko.SQLiteTest.query("toc.db", "select * from toc;") name = sqlite3 type = object status = 0 title = Database Connection Handle uri = c3ref/sqlite3.html ======================== name = sqlite3_int64 type = object status = 0 title = 64-Bit Integer Types uri = c3ref/int64.html ======================== name = sqlite3_uint64 type = object status = 0 title = 64-Bit Integer Types uri = c3ref/int64.html ======================== ...
      
      





main



メソッドを追加するために残ります:







  def main(args: Array[String]): Unit = { query(args(0), args(1)) polyglot.close() }
      
      





実際には、コンテキストを閉じる必要がありますが、 SQLiteTest



の初期化後もScalaコンソールに必要なため、オブジェクト自体ではこれを行いませんでした。







これで私の話は終わりです。読者に次のことを申し上げます。







  1. SubstrateVMを使用して、Scalaが存在しないかのように、これらすべてをネイティブバイナリに収集してください。
  2. (*)同じことを行いますが、プロファイルガイドによる最適化を使用します


結果のファイル:







SQLiteTest.scala
 package io.github.atrosinenko import java.io.File import org.graalvm.polyglot.proxy.ProxyExecutable import org.graalvm.polyglot.{Context, Source, Value} object SQLiteTest { val polyglot: Context = Context.newBuilder().allowAllAccess(true).build() def loadBcFile(file: File): Value = { val source = Source.newBuilder("llvm", file).build() polyglot.eval(source) } val cpart: Value = loadBcFile(new File("./sqlite3.bc")) val lib: Value = loadBcFile(new File("./lib.bc")) val sqliteOpen: Value = cpart.getMember("sqlite3_open") val sqliteExec: Value = cpart.getMember("sqlite3_exec") val sqliteErrmsg: Value = cpart.getMember("sqlite3_errmsg") val sqliteClose: Value = cpart.getMember("sqlite3_close") val sqliteFree: Value = cpart.getMember("sqlite3_free") val bytesToNative: Value = polyglot.getBindings("llvm").getMember("__sulong_byte_array_to_native") def toCString(str: String): Value = { bytesToNative.execute(str.getBytes()) } val lib_fromCString: Value = lib.getMember("fromCString") def fromCString(ptr: Value): String = { if (ptr.isNull) "<null>" else lib_fromCString.execute(ptr).asString() } val lib_copyToArray: Value = lib.getMember("copy_to_array_from_pointers") val callback: ProxyExecutable = new ProxyExecutable { override def execute(arguments: Value*): AnyRef = { val argc = arguments(1).asInt() val xargv = new Array[Long](argc) val xazColName = new Array[Long](argc) lib_copyToArray.execute(xargv, arguments(2)) lib_copyToArray.execute(xazColName, arguments(3)) (0 until argc) foreach { i => val name = fromCString(polyglot.asValue(xazColName(i) ^ 1)) val value = fromCString(polyglot.asValue(xargv(i) ^ 1)) println(s"$name = $value") } println("========================") Int.box(0) } } def query(dbFile: String, queryString: String): Unit = { val filenameStr = toCString(dbFile) val ptrToDb = new Array[Object](1) val rc = sqliteOpen.execute(filenameStr, ptrToDb) val db = ptrToDb.head if (rc.asInt() != 0) { println(s"Cannot open $dbFile: ${fromCString(sqliteErrmsg.execute(db))}!") sqliteClose.execute(db) } else { val zErrMsg = new Array[Object](1) val execRc = sqliteExec.execute(db, toCString(queryString), callback, Int.box(0), zErrMsg) if (execRc.asInt != 0) { val errorMessage = zErrMsg.head.asInstanceOf[Value] println(s"Cannot execute query: ${fromCString(errorMessage)}") sqliteFree.execute(errorMessage) } sqliteClose.execute(db) } } def main(args: Array[String]): Unit = { query(args(0), args(1)) polyglot.close() } }
      
      





lib.c
 #include <polyglot.h> void *fromCString(const char *str) { return polyglot_from_string(str, "UTF-8"); } void copy_to_array_from_pointers(void *arr, void **ptrs) { int size = polyglot_get_array_size(arr); for(int i = 0; i < size; ++i) { polyglot_set_array_element(arr, i, ((uintptr_t)ptrs[i]) ^ 1); } }
      
      





リポジトリへのリンク








All Articles