世界を動かす技術を、日本語で。

jankはC++です

概要

  • jank によるC++とのシームレスなインターフェースの進展
  • メモリ管理や型システムの強化、複雑な型への対応
  • 実用例 としてHello WorldやJSONパーサ、FTXUIとの連携を紹介
  • Clang/LLVM の協力と課題、今後の展望
  • パッケージングや配布、コミュニティ参加の呼びかけ

jankによるC++シームレスインターフェースの進展

  • 2024年4月時点 ではjankからC++へのアクセスが不可能だった状況
  • 個人開発者 による四半期の成果報告
  • GithubスポンサーClojurists Together からの支援への感謝
  • Clang/LLVM のVassil Vassilev氏、Lang Hames氏への謝意
  • C++との「魔法のような」連携を実現した技術基盤

メモリ管理の強化

  • cpp/newcpp/delete による手動メモリ管理の実装
  • jankのGCアロケータ(bdwgc) を活用し、mallocの代替を実現
  • cpp/delete 利用時は積極的・決定的なメモリ回収が可能
  • デストラクタ に対するbdwgc完全対応
  • 手動削除と自動回収の両方で非自明なデストラクタ呼び出しに対応

真偽値とリテラル・型表現の拡張

  • cpp/truecpp/false でC++のbool値を直接扱う
  • #cppリーダーマクロ によるC++リテラル導入予定
  • 複雑な型表現 (例:cpp/intやcpp/type "std::map<std::string, int>")
  • .サフィックス の省略でコンストラクタ呼び出しを簡略化

Opaque Boxによるポインタの安全な取り扱い

  • void *型のような生ポインタをjankオブジェクトに cpp/box でラップ
  • jankのデータ構造内で 型安全 に扱う仕組み
  • cpp/unbox で型情報を明示しつつC++ポインタに復元

プリコンパイル済みヘッダー(PCH)対応

  • Clang によるjank用C++ヘッダーのJIT処理を高速化
  • 初回起動時 やアップデート時にPCH自動再生成
  • 起動時間短縮・パフォーマンス向上

安定性と静的型付け

  • 数百件のinteropテスト でクラッシュやバグを発見・修正
  • 静的型付け (C++の型システム)をそのまま利用
  • リフレクションや型推測なし でコンパイルエラーによる安全性確保

実用例

  • Hello World :cpp/std.coutとcpp/castでC++ストリーム出力
  • JSON Pretty Printer :nlohmann/jsonヘッダーとstd::ifstreamを活用
  • FTXUI連携 :hiccup記法による純粋データでターミナルUI構築
    • Opaque BoxでC++オブジェクトをjank関数間で受け渡し
    • std::vectorやClojureの機能を活用した柔軟な実装

Claspとの比較とインスピレーション

  • Clasp (Common Lispベース、MPS GC、LLVM連携)との相違点
  • Christian Schafmeister博士 への敬意と影響

今後の課題と展望

  • 自動デストラクタ呼び出し (スタック割当オブジェクト)未対応
  • Clang/LLVM のバグや未実装機能への依存と課題
  • 資金提供 (Github Sponsor等)による開発加速の呼びかけ
  • 次四半期は パッケージング・配布の簡易化 が主眼
    • バグ修正、ツーリング、ドキュメント整備後に アルファ版リリース を予定

コミュニティ参加の案内

  • Slack でのコミュニティ参加
  • GitHub での設計議論やチケット対応
  • スポンサーシップ や企業協賛の呼びかけ

Hackerたちの意見

Redditにコメントしたらすぐにダウンボートされちゃったけど、Jankの作者がここにいると思うから(建設的な批判を受け入れてくれるといいな)、CppInterOpのC++相互運用のアプローチは完全にジャンクだと思う(ダジャレじゃないよ)。このアプローチは、C++を文字列として扱って、それを解析・解釈してABI準拠の呼び出しを生成するんだ。今のところlibclangが他の方法をサポートしてないから、こうする理由はそれだけ。これはJankのせいじゃないけど、libclangで「修正」できるかもしれない。最低でも、https://github.com/llvm/llvm-project/blob/main/clang/lib/Cod...を使ってclangのASTに基づいてコードを生成することができるし、最大限の方法としては、https://github.com/Mr-Anyone/abiみたいなものや、これが実現したらhttps://discourse.llvm.org/t/llvm-introduce-an-abi-lowering-...を使ってC++ライブラリのABI準拠の呼び出しを生成することができる。ちなみに、これはC++の相互運用が一級品になることを心から願って言ってるんだ。もしC++の相互運用がしっかりしてたら、すぐにJankの一番の支持者/ユーザーになるよ。EDIT: 証拠が欲しい人は、https://github.com/compiler-research/CppInterOp/blob/main/li...をざっと見てみて。

CppInterOpのC++相互運用のアプローチは完全にジャンクだと思う(ダジャレじゃないよ)。このアプローチは、C++を文字列として扱って、それを解析・解釈してABI準拠の呼び出しを生成するんだ。だから、これは本当にジャンクに聞こえるね。私の質問は、ジャンクに聞こえるだけじゃなくて、何か問題があるの?遅いとか信頼性がないとか?

こんにちは!ここにいるよ、受け入れる準備もできてる。Clangが実際に私のユースケースをサポートできればいいんだけど、残念ながらClangはスタンドアロンのAOTコンパイル用に設計されていて、他のIR生成メカニズムと絡むことは考慮されてないんだ。それに、Clangは一部のエラーを優雅に処理できず、悪い状態に陥ることがある。最近の四半期で、jankのCppInterOpのフォークをかなり成長させたよ。変更リストはここにあるよ: https://gist.github.com/jeaye/f6517e52f1b2331d294caed70119f1... これを全てアップストリームに持っていきたいけど、今は優先度が高くないから結構大変なんだ。CppInterOpの内部を見てきた経験から言うと、一番大きな問題はC++のコード生成じゃないと思う。基本的に、どんなコード生成も文字列を構築する形になるんだ。君がリンクしたCppInterOpの部分はC++関数を構築してるけど、ロバスト性の観点から言うと、実際に何が問題なの?文字列は任意のユーザー入力に基づいて生成されるんじゃなくて、ClangのQualTypesとDeclsに基づいて生成されるんだ。つまり、実際にそこにたどり着くには有効なClangの値が必要なんだよ。ABIの状況が完全に混乱していて、jankがすでにClangのJIT C++コンパイラを使っていることを考えると、これは非常に実行可能な解決策だと思う。ただ、ロバスト性の観点では、Clangのエラーハンドリングや優雅さの欠如、こういうユースケースに対するツールの貧弱さに戻るんだ。私の経験から言うと、それがロバスト性の問題を引き起こす原因になると思う。私の返答を受け入れられないとか防御的だと思わないでほしい。ディスカッションを本当に感謝してるし、もし私が何か間違ったことを言ってるなら、もっと説明してくれたら嬉しい。代替案については、君がリンクした https://github.com/Mr-Anyone/abi は3ヶ月前のもので、星が0(つまりユーザーも0、戦闘テストの年数も0だと思う)だね。それに、君がリンクした https://discourse.llvm.org/t/llvm-introduce-an-abi-lowering-... も素晴らしいと思うけど、_もし/いつそれが利用可能になったら_って感じだね。だから、すべての選択肢の中で、率直に聞くけど、今日存在する本当に_より良い_選択肢はあるの?CppInterOpはjankの実装の詳細なんだ。もしC++の文字列生成をもっとIR生成とポータブルなABIメカニズムに置き換えられ、Clangが私のテンプレート特殊化が正しいインスタンス化を得るためにC++の文字列に依存しなくて済むような十分なライブラリを提供できるなら、私は間違いなくCppInterOpを置き換えることにオープンだよ。見た限りでは、まだそこには至っていないね。

この再帰的な略語のPL名、手に負えなくなってきたね /s

これについてずっと考えてたんだけど、jankが再帰的な頭字語だってどういうことなのか全くわからない。君が見えてるものは何なの?

Jankがこの問題に対してLLVMをランタイムに埋め込む解決策を見つけたのは驚きじゃないよ。もっと良い方法があればいいのに。C++には好きじゃないところがたくさんあって、その中でも名前のマングリングの標準化がないことが一番嫌だ。コンパイル時に名前をマングルしたりデマングルする方法もないから、ダイナミックFFIをターゲットにするのが本当に面倒なんだ。シンボル名や呼び出しのセマンティクスをconstexpr const char*として取得できる方法があればいいのに、ボイラープレートやextern "C"のブロックをたくさん生成(または書く)しなくて済むのに。これは絶対に可能だけど、簡単なことじゃないから、標準化委員会は絶対に取り入れないだろうね。alloca/VLAの標準化された同等物も追加されないだろうし。基本的で役に立つものを持つことは許されてないんだ。型推論を乱用する方法だけが増えていく。C++26ではついにconstexprの動的割り当てが実現するのかな?コンパイラは実際に3つのコンパイル時リフレクションの標準のうちの一つを実装するのかな?続報をお楽しみに!

ランタイムにLLVMを埋め込んでるわけじゃない - clangを埋め込んでるんだ。私の下のコメントを見れば、LLVMは現在十分じゃないってわかるよ。 > [C++]は名前のマングリングのせいでダイナミックFFIをターゲットにするのが本当に面倒なんだ。名前のマングリングはC++ FFIの中で一番簡単な部分で、難しいのはABIの残りの部分なんだ。興味がある人はここから始めてみてね https://github.com/rust-lang/rust-bindgen/issues/778

カーマックはトリニティ/クエイク3エンジンでほぼ同じことをやったよね。確かLCCかtccだったかな、個人でも完全に理解できるCコンパイラの一つだよ。彼はCをsyscall用のいくつかのビルトインを使ってコンパイルして、自分のスタックマシンに変換したんだ。でも、ネイティブDLL用のターゲットもあったから、安全なsyscallインターフェースは同じだけど、セグメンテーションフォルトが起きる可能性もあるから、信頼しなきゃいけない。こんな一つのプログラムの中で(エリートなレジェンドたちの高概念FAANG C++よりもまだ読みやすい、本当にユニークなもの)、これが一番劇的な革新じゃなかったなんて、すごいことだよ。実際には、これがそのプログラムの中で三番目に劇的な革命だったんだ。こういうのに興味があるなら、病欠して一日中プランファイルを読んでみて。ゾクゾクするよ。

名前のマングリングに関する標準化がない、またはコンパイル時に名前をマングルしたりデマングルする方法がない。多くのことと同じように、これはC++の問題じゃない。標準はあって、ほとんどのターゲットがそれを使ってる…で、マイクロソフトがやってることがある。後者に対処しなきゃならないときだけ問題が出てくる。今、標準は進化するし、これによって異なるシステムライブラリやツールが何が受け入れられるか、正しいかについて異なる見解を持つ余地ができる(I...EJ...Eのエラーを解決しようとしたときの悪夢は今でも忘れられない)…でも、すべての機能は存在していて、最前線にいなければうまく動くよ(幸いなことに、C++11は本当に必要な部分を提供してくれたし、それ以降はただの「あったらいいな」って感じ)。

名前のマングリングに関する標準化がない 名前のマングリングを標準化する意味がわからない。標準があると仮定しよう、そうしたら今度は標準ライブラリにあるすべてのクラスのメモリレイアウトを標準化しなきゃならない。それがなければ、リンク時に失敗する代わりに、仮想のプログラムは実行中に醜い形で壊れることになる。例えば、互いに呼び出し合う二つの関数がstd::stringの長さがメモリのどこにあるかについて異なる意見を持っていたら。

C++のポータビリティやABI、標準については理解してるよ。だけど、jankがLLVM以外の何を使うと思ってるのかはわからないな。ClojureはJVMを使ってるし、jankはLLVMを使ってる。JITランタイムやjankのコンパイラバックエンド(IR最適化やターゲットコード生成のため)を処理するために_何か_が必要になると思う。LLVMじゃなければ、jankは別の何かを組み込むことになる。これら二つを自分で構築しなきゃならないとなると、すでに巨大なプロジェクトが手に負えないものになってしまうよ。

コンパイル時に名前をデマングルするのは、標準化されてるわけじゃないけど、今はGCCやClangでできるよ。__PRETTY_FUNCTION__をうまく使えばね。

2日前の話だよ: https://news.ycombinator.com/item?id=44482273

最近D言語を試してみたら、C++との相互運用がすごく良くて驚いた(言語全体的に結構いい感じ)。Carbonは全然見当たらなくて、Swiftもまだ試してない。これは良いものになるといいな。

Shedskin言語はC++との統合が素晴らしいよ。

もちろんjankはすでにC++のサポートを得てるけど、もしD言語のサポートがあったとしたら、もっと簡単だったり実現可能だったりしたのかな?

そうそう、jankはClojureだけど、JVMじゃなくてC++/LLVMランタイムを使ってるんだ。だから、すでにその型はC++の型になってて、かなり楽になるはず。基本的にはlibclang / CppInterOpを使って対応するLLVMの型を取得して、関数呼び出しを出力するって感じだね。 https://github.com/jank-lang/jank/blob/interop/compiler%2Bru...

すごいプロジェクトだね、こんな狂ったことをこなす能力には驚かされるよ。でも、もっと高レベル言語でC++との相互運用性が良くなればいいな。役立つC++のコードが結構あるからさ。Claspの話がちょっと出てきたのも嬉しい。読んでるときにすぐ思い浮かんだし。

昔Clojureを使ってたけど、今は仕事でNimを使ってる。NimでCにリンクするのはめちゃくちゃ簡単だよ。jankがこれで動いてるのを見るのは嬉しいけど、C++は...ほんとに厄介なターゲットだよね。jankが最終的に参照カウントに落ち着く可能性はあるかな?私的にはシンプルで予測可能、エッジケースも少なくて速いから、すごく良いと思う。結局、jankプログラムがどれだけメモリを叩くかにかかってるんだろうな。Clojureはバックグラウンドで結構動いてたのを覚えてる。

参照カウントから始めたけど、Clojureのプログラムが出すゴミの量が多すぎて、GCを使わないと全体が遅くなっちゃうんだ。jankのGCは今後変わる予定で、オプショナルなアファイン型をサポートしてほしいけど、Clojureのベースは多分ずっとガベージコレクションされるだろうね。

ずっと前、ClojureConj 2014でリッチ・ヒッキーにcppベースのClojureが可能か聞いたことがあるんだけど、彼の答えは「まあ、主な障害はガベージコレクタがないことだね」だった。たくさんの会話が同時にあったから、深入りする機会はなかったけど、1. その反論は意味があるの? 2. jankはそのハードルにどうアプローチしてるの?

記事の最初の部分だよ - 「cpp/newとcpp/deleteを使って手動メモリ管理を実装した。これはmallocではなく、jankのGCアロケータ(現在はbdwgc)を使っているから、cpp/deleteを使う必要は基本的にない。ただし、cpp/deleteを使うと、メモリの収集が早くてより決定的になることがある。実装にはデストラクタのためのbdwgcの完全サポートもあるから、手動削除と自動収集の両方が非自明なデストラクタをトリガーするよ。」

GCはこれの中で一番難しい部分じゃないよ。2014年にはC++のJITコンパイルに使える技術がなかったし、ネイティブコードのJITコンパイルに関してもほとんど技術がなかったからね。

記事によると、ガベージコレクションは常に行われるけど、deleteを呼び出すと、ガベージコレクターがもっと積極的に掃除をするようになるんだって。

それは素晴らしい!C++との相互運用はめっちゃ複雑な作業だよね。お疲れ様!絶対簡単なことじゃないよ。C++のテンプレートインスタンス化とパフォーマンスを保ちながらどうやってやるのがベストなのか、ずっと疑問だった。静的言語の場合、コンパイル時に型をC++に翻訳して、生成されたC++ファイルをClang/GCC/MSVCにコンパイルさせて、最終的な結果をリンクする必要があるだろうね。最後に、名前のマングリングが正しく行われたことをコンピュータの神々に祈るしかないね。