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); } } 

リポゞトリぞのリンク 。



Source: https://habr.com/ru/post/J358700/


All Articles