あけましておめでとうございます。
ということで、Truffleで言語実装したい気分なので、まずはJyukutyoの数式処理から始めることにしました。
オレオレJVM言語を作ろう! How to create a new JVM language #Graal #Truffle - Fight the Future
けど、APIがだいぶ変わってるようでそのままではできず、あとAntlr使っているのでTruffleのみの部分を切り離して試してみました。
足し算だけの式言語をつくります。
https://github.com/kishida/simplest_truffle_expr
簡易Truffle言語に変数を実装する - きしだのHatena
Truffle言語で関数呼び出しを実装する - きしだのHatena
Truffle言語をGraalVMで動かす - きしだのHatena
依存関係
truffle-apiとtruffle-dsl-processorが必要です。truffle-dsl-processorはアノテーション処理をするだけなので、実行時には不要なはず。
GraalVMで動かす場合はどちらも不要だと思うけど、OpenJDKなどで動かすにはtruffle-apiが必要です。
<dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-api</artifactId> <version>1.0.0-rc9</version> </dependency> <dependency> <groupId>org.graalvm.truffle</groupId> <artifactId>truffle-dsl-processor</artifactId> <version>1.0.0-rc9</version> <scope>provided</scope> </dependency>
GraalVMで動かす場合、実行時にtruffle-api.jarを含めるとGraalVMが持ってるクラスと競合してClassCastExceptionになります。しかし含めないとClassNotFound...動かし方がわからない・・・
※2019/1/4 追記。やっとわかった。
Truffle言語をGraalVMで動かす - きしだのはてな
準備
まずは言語で使う型を@TypeSystemを使って登録します。空のクラスにアノテーションを指定します。
@TypeSystem(long.class) static abstract class MathTypes { }
言語のASTのベースとなるクラスを作ります。これはTruffleのNodeクラスを継承します。このとき@TypeSystemReferenceで先ほど型を登録したクラスを指定しておきます。
メソッドとしてVirtualFrameを受け取るexecuteGenericというメソッドを登録しておきます。executeXxxで型ごとの処理を書けるっぽい。すべての型を処理する場合はexecuteGeneric
@TypeSystemReference(MathTypes.class) static abstract class MathNode extends Node { abstract Object executeGeneric(VirtualFrame frame); }
式のASTを作る
今回はlongだけ使うので、longリテラル用のノードを作ります。longを処理するためのexecuteLongメソッドを用意しておきます。
@NodeInfoはノード情報を持たせるアノテーションです。
@NodeInfo(shortName = "value") static class LongNode extends MathNode { private long value; private LongNode(long value) { this.value = value; } static LongNode of(String v) { return new LongNode(Long.parseLong(v.trim())); } long executeLong(VirtualFrame frame) { return value; } @Override Object executeGeneric(VirtualFrame frame) { return value; } }
そして足し算ノード。左項と右項を保持するフィールドを、@NodeChildとして持たせてます。また、実際の足し算はaddメソッドで定義しますが、型を限定する処理には@Specializationをつけるっぽい。
@NodeInfo(shortName = "+") @NodeChild("leftNode") @NodeChild("rightNoode") public static abstract class AddNode extends MathNode { @Specialization public long add(long left, long right) { return left + right; } public Object add(Object left, Object right) { return null; } }
アノテーションプロセッサによって、AddNodeGenというクラスが生成されて、実際にaddを呼び出すexecuteGenericなどのメソッドや、createというファクトリメソッドが生成されます。
@GeneratedBy(AddNode.class) public static final class AddNodeGen extends AddNode { @Child private MathNode leftNode_; @Child private MathNode rightNoode_; @CompilationFinal private int state_; private AddNodeGen(MathNode leftNode, MathNode rightNoode) { this.leftNode_ = leftNode; this.rightNoode_ = rightNoode; } @Override Object executeGeneric(VirtualFrame frameValue) { int state = state_; Object leftNodeValue_ = this.leftNode_.executeGeneric(frameValue); Object rightNoodeValue_ = this.rightNoode_.executeGeneric(frameValue); if (state != 0 /* is-active add(long, long) */ && leftNodeValue_ instanceof Long) { long leftNodeValue__ = (long) leftNodeValue_; if (rightNoodeValue_ instanceof Long) { long rightNoodeValue__ = (long) rightNoodeValue_; return add(leftNodeValue__, rightNoodeValue__); } } CompilerDirectives.transferToInterpreterAndInvalidate(); return executeAndSpecialize(leftNodeValue_, rightNoodeValue_); } private long executeAndSpecialize(Object leftNodeValue, Object rightNoodeValue) { .... } ... public static AddNode create(MathNode leftNode, MathNode rightNoode) { return new AddNodeGen(leftNode, rightNoode); } }
実行準備
実行にはRootNodeを継承したクラスも必要っぽい。このexecuteメソッドが呼び出されます。
static class MathRootNode extends RootNode { private MathNode body; public MathRootNode( TruffleLanguage<?> language, FrameDescriptor frameDescriptor, MathNode body) { super(language, frameDescriptor); this.body = body; } @Override public Object execute(VirtualFrame frame) { return body.executeGeneric(frame); } }
コンテキストを保持することになってるクラスも作ります。今回は空のクラス
public static class MathLangContext { }
そして言語を登録するクラスを作ります。
@TruffleLanguage.Registrationアノテーションを指定しますが、Jyukutyoのときに比べるとidが必要になってmimeTypeがなくなり、代わりにdefaultMimeTypeとcharacterMimeTypesを指定しています。
parseメソッドで足し算のパースを行ってNodeツリーを作りつつ、RootNodeに持たせて、そこからCallTargetを返すという感じ。
@TruffleLanguage.Registration(name = "MathLang", id = "mathlang", defaultMimeType = MathLang.MIME_TYPE, characterMimeTypes = MathLang.MIME_TYPE) @ProvidedTags({StandardTags.CallTag.class, StandardTags.StatementTag.class, StandardTags.RootTag.class, DebuggerTags.AlwaysHalt.class}) public class MathLang extends TruffleLanguage<MathLangContext>{ public static final String MIME_TYPE = "application/x-mathlang"; @Override protected CallTarget parse(ParsingRequest request) throws Exception { String source = request.getSource().getCharacters().toString(); String[] nums = source.split("\\+"); MathNode node = LongNode.of(nums[nums.length - 1]); for (int i = nums.length - 2; i >= 0; --i) { node = MathNodesFactory.AddNodeGen.create(LongNode.of(nums[i]), node); } MathRootNode root = new MathRootNode(this, new FrameDescriptor(), node); return Truffle.getRuntime().createCallTarget(root); } @Override protected MathLangContext createContext(Env env) { return new MathLangContext(); } @Override protected boolean isObjectOfLanguage(Object object) { return false; } }
実行
そして実行です。
一番苦労したところで、Jyukutyoの書いてるPolyglotEngineが見当たらず。
@TruffleLanguage.Registrationに指定したidを渡してContextを作り、evalすると計算してくれます。
public class MathMain { public static void main(String[] args) { String exp = "12+34+56"; Context cont = Context.create("mathlang"); System.out.println(cont.eval("mathlang", exp)); } }
いやー、なにがなんだかわかりませんね。
ネイティブコンパイル
GraalVMをJVMとして実行することには失敗してますが、ネイティブコンパイルはできました。--tool:truffleをつけるとよさげ。
$ native-image --tool:truffle -cp Mathexpr-1.0-SNAPSHOT.jar mathexpr.MathMain me [me:53002] classlist: 263.17 ms [me:53002] (cap): 1,308.09 ms [me:53002] setup: 2,527.17 ms [me:53002] (typeflow): 9,000.68 ms [me:53002] (objects): 13,375.22 ms [me:53002] (features): 681.50 ms [me:53002] analysis: 23,762.15 ms 629 method(s) included for runtime compilation [me:53002] universe: 642.53 ms [me:53002] (parse): 1,174.13 ms [me:53002] (inline): 2,004.82 ms [me:53002] (compile): 13,530.32 ms [me:53002] compile: 17,943.66 ms [me:53002] image: 1,967.82 ms [me:53002] write: 701.59 ms [me:53002] [total]: 47,888.13 ms $ ./me 102