あけましておめでとうございます。
ということで、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 && 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