シリーズ一覧:
- 【ストレンジコード】Filskaコンパイラ開発計画(1) 言語仕様を整理する【MLIR】
- 【ストレンジコード】Filskaコンパイラ開発計画(2) ソースコードをパースする【MLIR】
- 【ストレンジコード】Filskaコンパイラ開発計画(3) LLVM IRを出力する【MLIR】(本記事)
- 【ストレンジコード】Filskaコンパイラ開発計画(4) 実行ファイル出力【MLIR】
- 【ストレンジコード】Filskaコンパイラ開発計画(5) サブプログラムを複数作成可能にする【MLIR】
- 【ストレンジコード】Filskaコンパイラ開発計画(6) llvmir以外のdialectを経由したloweringを可能にする【MLIR】
TL; DR
- FilskaをMLIR製コンパイラへ移植
- 最低限の命令のみ実装し、処理系の骨組みを作成
- Lexer: 字句解析
- Parser: 構文解析
- Sema: 意味解析
- CodeGen: MLIR生成
- Lowering: MLIRをLLVM IRへ変換
はじめに
本シリーズでは、『ストレンジコード』に登場する難解プログラミング言語「Filska」をMLIRへ移植します。本家がPython製インタプリタなので、コンパイラにすることで高速化できるのでは?という淡い期待も寄せています。
(ストレンジコードにはFilska以外の難解プログラミング言語もたくさん登場します!言語好きは必見です (ダイマ))
今回はASTからMLIRを生成し、LLVM IRへ変換するための導線を準備します。
思い立った背景や開発の概要については初回の記事をご覧ください。
記事投稿者のスペック
MLIRもC++も初心者のため、誤った記述があればコメントで指摘いただけますと幸いです
- C++: 未経験
- LLVM: 未経験
- MLIR: 未経験
- 言語処理系: インタプリタのみ自作経験あり
処理系の骨組みをつくる
まずは処理系の設計指針を立てるため、ソースコードを読み込んでからLLVM IRを出力するまでに必要な処理を一通り実装します。
(ただし、命令は動作確認できる最低限のみ対応)
Semaまでについては前回の記事をご覧ください。
- Lexer: 字句解析
- ソースコード→トークン列
- Parser: 構文解析
- トークン列→AST
- Sema: 意味解析
- ASTがFilskaの制約を満たしているかチェック
- 例:同名のサブプログラムが存在した場合エラー
- ASTがFilskaの制約を満たしているかチェック
- CodeGen: MLIR生成
- AST→MLIR (filskalang dialect)
- Lowering: MLIRをLLVM IRへ変換
- MLIR→LLVM IR
設計
以下の2つの言語処理系を参考にしています。
-
Toy
- MLIRの公式チュートリアル
- toyソースコードをMLIRを経由しLLVM IRへ変換、その後JITコンパイラで実行
-
tinylang
- 書籍『Learn LLVM 17』の題材として取り上げられる言語
- Modula-2のサブセット
- tinylangソースコードをLLVM IRへ変換したのちバイナリ生成
開発環境
- 使用バージョン
- LLVM: 18.1.4
- MLIR: 18.1.4
- clang: 17.0.1
- 開発環境
- WSL2 Ubuntu 20.04
環境構築のあれこれに詰まると辛いので (2敗) 、LLVMとMLIRはaptでインストールしています。
また、こうすることで将来的にFilskaコンパイラをDockerイメージで配布できるようになります。
aptでの環境構築とコンパイラのイメージ化について詳細は以下の記事をご覧ください。
実装する命令
動作確認をするための最小構成として、今回は以下の要素のみ実装します。
- プログラム
- サブプログラム: Filskaの命令をまとめた単位(他言語の「関数」に相当)
-
set
命令: サブプログラムのレジスタm
に指定した数値リテラルを格納 -
prt
命令: サブプログラムのレジスタm
の内容を数値として標準出力へ表示 -
hlt
命令: プログラムを終了する(サブプログラムはデフォルトで無限ループするため)
下記のソースコードが動作するのがゴールです。
{ main
set,10
prt
hlt
}
$ filska example/simple.filska
10
CodeGen
MLIRチュートリアルのtoyを参考にしています。
ASTからMLIRを生成します。MLIRの命令はdialect(直訳すると「方言」?)の単位で管理することができるため、FilskaのASTをそのまま表したfilskalang dialectを作成します。
dialectの定義は
- TableGen
- C++
の2通りの方法で記述可能です1。今回は記述が簡潔なTableGenを採用しました。
TableGenの記法についてはtoyチュートリアルと公式リファレンスを参考にしました。
基本的にはFilskaの命令をdialectの命令と1対1対応させています。
例: set
命令
def SetOp : Filskalang_Op<"set"> {
let summary = "set operation";
let description = [{
The "filskalang.set" builtin operation represents the "set" instruction
}];
# NOTE: レジスタを参照するためサブプログラム名を追加で渡している(理由は後述)
let arguments = (ins F64Attr:$value, SymbolNameAttr:$subprogramName);
}
サブプログラムは複数命令を内部に持つ必要があるため、FunctionOpInterface
を継承して作成しています。
SubprogramOp(長いので折りたたみ)
def SubprogramOp : Filskalang_Op<"subprogram", [
FunctionOpInterface,
]> {
let summary = "subprogram operation";
let description = [{
The "filskalang.subprogram" operation represents a subprogram.
This must have no parameters and return values.
}];
// NOTE: dummy parameters and return values are required for FunctionOpInterface
let arguments = (ins
SymbolNameAttr:$sym_name,
TypeAttrOf<FunctionType>:$function_type,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs
);
let regions = (region AnyRegion:$body);
let builders = [
OpBuilder<(ins
"StringRef":$name,
"FunctionType":$type,
CArg<"ArrayRef<NamedAttribute>", "{}">:$attrs
)>
];
let extraClassDeclaration = [{
//===------------------------------------------------------------------===//
// SubprogramOpInterface Methods
//===------------------------------------------------------------------===//
/// Returns the argument types of this function.
ArrayRef<Type> getArgumentTypes() { return getFunctionType().getInputs(); }
/// Returns the result types of this function.
ArrayRef<Type> getResultTypes() { return getFunctionType().getResults(); }
Region *getCallableRegion() { return &getBody(); }
}];
# デフォルトのビルドメソッド生成をスキップ(自分で別途実装する必要がある)
let skipDefaultBuilders = 1;
}
ソースコードをfilskalang dialectに変換するとこのようになります。"filskalang.subprogram"
のボディに命令が格納されているのが確認できます。
{ main
set,10
prt
hlt
}
"builtin.module"() ({
"filskalang.subprogram"() <{function_type = () -> (), sym_name = "main"}> ({
"filskalang.set"() <{subprogramName = "main", value = 1.000000e+01 : f64}> : () -> ()
"filskalang.prt"() <{subprogramName = "main"}> : () -> ()
"filskalang.hlt"() : () -> ()
}) : () -> ()
}) : () -> ()
lowering
filskalang dialectのままでは実行できないため、LLVM IRへ変換します。MLIRでは複数の中間表現(dialect)を経由して段階的に別の中間表現へ変換(lowering)することができます。
loweringについてはこちらの記事で詳しく解説されています。
今回実装する
- サブプログラム
-
set
命令 -
prt
命令 -
hlt
命令
はいずれも既存のdialectで上手く表現できるものがなかったため、直接LLVM IRへ変換しています2。
サブプログラム
MLIRには関数を表す func
dialectが存在しますが、今回は使用しませんでした。
理由はサブプログラムには returnの概念が存在しない ためです。
サブプログラムは関数と異なり、ジャンプ先からジャンプ元へ戻ることはありません。そのため、サブプログラムのジャンプを関数呼び出しとして実装するとコールスタックが溜まり続けてしまいます。
サブプログラムをmain関数内にブロックとして展開し、ジャンプをbr命令によって表すことでこの問題を回避することにしました。
(現状ではmain関数内のインライン展開のみ実装済み)
実装に以下の課題がありますが、今回は無視してひとまずLLVM IRの生成を目指します。
- サブプログラムの無限ループが実装できていない
- サブプログラムは本来最後の命令を実行し終えると先頭の命令から再び実行する
- 複数のサブプログラムが存在する場合に正しく処理できない
- LLVM IRに
main
関数が複数作成されてしまう
- LLVM IRに
サブプログラムの変換
mlir::LogicalResult
matchAndRewrite(mlir::Operation *Op, mlir::ArrayRef<mlir::Value> Operands,
mlir::ConversionPatternRewriter &Rewriter) const override {
auto Loc = Op->getLoc();
auto *Context = Op->getContext();
mlir::filskalang::SubprogramOp Subprogram =
mlir::cast<mlir::filskalang::SubprogramOp>(Op);
Rewriter.setInsertionPointToEnd(Subprogram->getBlock());
// `main` 関数 (LLVM IRのエントリポイント)にサブプログラムの命令一覧をインライン展開
auto DummyType = mlir::LLVM::LLVMFunctionType::get(
mlir::LLVM::LLVMVoidType::get(Context), mlir::ArrayRef<mlir::Type>{});
auto Func = Rewriter.create<mlir::LLVM::LLVMFuncOp>(Loc, "main", DummyType);
Rewriter.inlineRegionBefore(Subprogram.getBody(), Func.getBody(),
Func.end());
mlir::Block *EntryBlock = &Func.getBody().front();
// ...
// LLVM IRのブロックはterminatorで終了しないといけないため、ダミーのreturnを挿入
// https://llvm.org/docs/LangRef.html#terminator-instructions
// TODO: 無限ループへ置き換え
Rewriter.setInsertionPointToEnd(EntryBlock);
Rewriter.create<mlir::LLVM::ReturnOp>(Loc, mlir::ArrayRef<mlir::Value>());
Rewriter.setInsertionPointToEnd(Subprogram->getBlock());
// 不要になった変換前の命令(filskalang.subprogram)を削除
Rewriter.eraseOp(Op);
return mlir::success();
}
};
set命令
set
はサブプログラムのレジスタ m
への代入を行います。そのため以下全てを満たす必要があります。
-
m
に対し再代入が行える - どのサブプログラムの
m
かを識別できる
m
に対し再代入が行える
LLVM IRはSSA(Static Single Assignment, 静的単一代入)形式のため、同じ変数へ再代入することは できません。
一方、メモリオブジェクトについてこの制約は無いため、再代入可能な変数を alloca
と store
命令で表すことが可能です。
詳細はLLVMのチュートリアル「Kaleidoscope」言語の解説で詳しく紹介されています。
define i32 @example() {
entry:
%X = alloca i32 ; %X用のメモリ割り当て
...
%tmp = load i32, i32* %X ; スタックから %X の値を読み込み
%tmp2 = add i32 %tmp, 1
store i32 %tmp2, i32* %X ; %Xへ書き込み
ご覧の通り、メモリを使用するためパフォーマンスが下がってしまいます。
幸いLLVMの最適化パス mem2reg
によってレジスタ割り当てに置換されるため、実際には効率を損なわない処理に変換されます。
実際、filskalangが生成したLLVM IRも最適化によってalloca, store命令が削除されています3。
define void @main() !dbg !6 {
%1 = alloca double, i64 0, align 8, !dbg !8
store double 1.000000e+01, ptr %1, align 8, !dbg !9
%2 = load double, ptr %1, align 8, !dbg !10
%3 = call double (ptr, ...) @printf(ptr @frmt_spec, double %2), !dbg !10
ret void, !dbg !8
}
define void @main() local_unnamed_addr !dbg !6 {
%1 = tail call double (ptr, ...) @printf(ptr nonnull @frmt_spec, double 1.000000e+01), !dbg !8
ret void, !dbg !9
}
どのサブプログラムの m
かを識別できる
サブプログラムごとに別々の m
を用意し適切なものを使用する必要があります。しかし、 prt
命令から直接対応するサブプログラムを取得することはできません。
そのため、サブプログラム名と対応する alloca
命令をmapで管理し、対応するものにstore
するようにしています。
(このためにfilskalang dialectの set
命令の引数にサブプログラム名を持たせていました)
set命令の実装
std::unordered_map<std::string, mlir::TypedValue<::mlir::LLVM::LLVMPointerType>>
SubprogramMemory;
class SetOpLowering : public mlir::ConversionPattern {
public:
mlir::LogicalResult
matchAndRewrite(mlir::Operation *Op, mlir::ArrayRef<mlir::Value> Operands,
mlir::ConversionPatternRewriter &Rewriter) const override {
auto Loc = Op->getLoc();
auto SetOp = mlir::cast<mlir::filskalang::SetOp>(Op);
mlir::Value ConstantOp = Rewriter.create<mlir::LLVM::ConstantOp>(
Loc, Rewriter.getF64Type(),
Rewriter.getF64FloatAttr(SetOp.getValue().convertToDouble()));
// サブプログラム名から対応するalloca命令を取得
auto MemoryPointer = SubprogramMemory.at(SetOp.getSubprogramName().str());
auto Value = llvm::APFloat(SetOp.getValue());
// 指定した命令へstore
Rewriter.create<mlir::LLVM::StoreOp>(Loc, ConstantOp, MemoryPointer);
// Notify the rewriter that this operation has been removed.
Rewriter.eraseOp(Op);
return mlir::success();
}
};
class SubprogramOpLowering : public mlir::ConversionPattern {
public:
mlir::LogicalResult
matchAndRewrite(mlir::Operation *Op, mlir::ArrayRef<mlir::Value> Operands,
mlir::ConversionPatternRewriter &Rewriter) const override {
// ...
// レジスタ `m` を定義するためにalloca命令を挿入
Rewriter.setInsertionPointToStart(EntryBlock);
mlir::Value Cst0 = Rewriter.create<mlir::LLVM::ConstantOp>(
Loc, Rewriter.getI64Type(), Rewriter.getIndexAttr(0));
auto Alloca = Rewriter.create<mlir::LLVM::AllocaOp>(
Loc, /*resultType*/ mlir::LLVM::LLVMPointerType::get(Context),
/*elementType*/ Rewriter.getF64Type(), Cst0);
// mapへ格納
SubprogramMemory[Subprogram.getName().str()] = Alloca.getRes();
// ...
}
};
MLIR (LLVM dialect)上でも、alloca
命令のポインタへ store
できていることが確認できます。
%1 = llvm.alloca %0 x f64 : (i64) -> !llvm.ptr
%2 = llvm.mlir.constant(1.000000e+01 : f64) : f64
llvm.store %2, %1 : f64, !llvm.ptr
prt命令
printf
を呼び出しています。数値をそのまま出力するため書式 %f
を使用しています。
出力対象はレジスタ m
なので、上記と同様 load
命令でレジスタの中身を取得して printf
の引数にしています。
実際には m
が整数の場合のみ %d
で出力する必要がありますが、これは今後の課題とします。
(処理は長いので割愛)
hlt命令
サブプログラムの無限ループを止めるために必要ですが、無限ループ機構自体未実装のため現状何もしていません。
hltの変換
mlir::LogicalResult
matchAndRewrite(mlir::Operation *Op, mlir::ArrayRef<mlir::Value> Operands,
mlir::ConversionPatternRewriter &Rewriter) const override {
// TODO: 処理を追加
// 不要になった変換前の命令を削除
Rewriter.eraseOp(Op);
return mlir::success();
}
動作確認
LLVM IRまで生成できるようになったため、実際に動かしてみます。
{ main
set,10
prt
hlt
}
$ ./bin/filskalang example/simple.filska -emit llvm
; ModuleID = 'LLVMDialectModule'
source_filename = "LLVMDialectModule"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-linux-gnu"
@frmt_spec = internal constant [3 x i8] c"%f\00"
declare !dbg !3 double @printf(ptr, ...)
define void @main() !dbg !6 {
%1 = alloca double, i64 0, align 8, !dbg !8
store double 1.000000e+01, ptr %1, align 8, !dbg !9
%2 = load double, ptr %1, align 8, !dbg !10
%3 = call double (ptr, ...) @printf(ptr @frmt_spec, double %2), !dbg !10
ret void, !dbg !8
}
!llvm.module.flags = !{!0}
!llvm.dbg.cu = !{!1}
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = distinct !DICompileUnit(language: DW_LANG_C, file: !2, producer: "MLIR", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly)
!2 = !DIFile(filename: "<unknown>", directory: "")
!3 = !DISubprogram(name: "printf", linkageName: "printf", scope: !2, file: !2, line: 1, type: !4, scopeLine: 1, spFlags: DISPFlagOptimized)
!4 = !DISubroutineType(cc: DW_CC_normal, types: !5)
!5 = !{}
!6 = distinct !DISubprogram(name: "main", linkageName: "main", scope: !7, file: !7, line: 1, type: !4, scopeLine: 7, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !1)
!7 = !DIFile(filename: "simple.filska", directory: "example")
!8 = !DILocation(line: 1, column: 7, scope: !6)
!9 = !DILocation(line: 2, column: 8, scope: !6)
!10 = !DILocation(line: 3, column: 8, scope: !6)
LLVM IRのインタプリタ lli
に渡して実行したところ、無事想定通りの結果が表示されました。
$ ./bin/filskalang example/simple.filska -emit llvm | lli
10.000000
本家では 10
と表示されるため、書式については今後修正が必要そうです。
はまったところ
ここまでさらっと書きましたが、実際にはここに至るまで七転八倒していました。
以下はまった点と解消方法についてです。
(備忘録なので適当に読み飛ばしてください)
CodeGen
llvm::SMLoc
を mlir::Location
に変換できない
Lexer, Parserではエラーメッセージに llvm::SourceMgr#PrintMessage
を使用しているため、エラー発生個所のソースコード上の位置情報は llvm::SMLoc
から取得しています。
一方、filskalang dialectのコード生成時には位置情報として mlir::Location
が要求されます。両者はどうやら相互変換ができないよう4なので、両方の形式に対応した位置情報クラスを定義しています。
位置情報クラスの実装
class Location {
mlir::SMLoc Loc;
int Line;
int Column;
const char *InputFileName;
public:
Location(mlir::SMLoc Loc, int Line, int Column, const char *InputFileName)
: Loc(Loc), Line(Line), Column(Column), InputFileName(InputFileName) {}
mlir::SMLoc getLoc() { return Loc; }
mlir::Location getLocation(mlir::OpBuilder Builder) {
return mlir::FileLineColLoc::get(Builder.getStringAttr(InputFileName), Line,
Column);
}
};
2つの処理系の実装をそのまま引っ張ってきたのが原因とはいえ少し煩雑なので、将来別の言語をMLIRで作る際には mlir::Location
にまとめられないか検討したいです。
TableGenからC++ソースコードが生成できない
tbファイルと同じディレクトリの CMakeLists.txt
に設定を入れてもソースコードが生成されませんでした(原因不明)。
本来はtoyのこの設定のように記述できるはずですが...
# toyより引用
set(LLVM_TARGET_DEFINITIONS ShapeInferenceInterface.td)
mlir_tablegen(ShapeInferenceOpInterfaces.h.inc -gen-op-interface-decls)
mlir_tablegen(ShapeInferenceOpInterfaces.cpp.inc -gen-op-interface-defs)
add_public_tablegen_target(ToyCh7ShapeInferenceInterfaceIncGen)
解決方法が分からないので、汚いですがプロジェクト直下の CMakeLists.txt
に設定を書きました。
add_mlir_dialect(FilskalangOps filskalang)
set(LLVM_TARGET_DEFINITIONS ${CMAKE_CURRENT_SOURCE_DIR}/include/filskalang/CodeGen/Ops.td)
mlir_tablegen(include/filskalang/CodeGen/Ops.h.inc -gen-op-decls)
mlir_tablegen(include/filskalang/CodeGen/Ops.cpp.inc -gen-op-defs)
mlir_tablegen(include/filskalang/CodeGen/Dialect.h.inc -gen-dialect-decls)
mlir_tablegen(include/filskalang/CodeGen/Dialect.cpp.inc -gen-dialect-defs)
add_public_tablegen_target(FilskalangOpsIncGen)
dialectのbuildメソッドでコンパイルエラーが発生する
undefined reference to `mlir::filskalang::SubprogramOp::build(mlir::OpBuilder&, mlir::OperationState&, llvm::StringRef, mlir::FunctionType, llvm::ArrayRef<mlir::NamedAttribute>)'
Builder.create<作成したい命令の型>
の引数のシグネチャが、作成したい命令の型::build
に合致していない場合に発生します。オーバーロードごとにエラーが発生するためエラーログが大量発生しますが、その中のどれかに当てはまるように修正すればコンパイルが通ります。
// 正しいシグネチャに修正する
mlir::filskalang::SubprogramOp Sub =
Builder.create<mlir::filskalang::SubprogramOp>(
Loc, Subprogram.getName(), funcType);
Lowering
lowering失敗時に何が起きているか分からない
--mlir-print-ir-after-all
オプションを付けることで、変換パスごとにMLIRの途中経過を確認できました。
$ ./bin/filskalang --mlir-print-ir-after-all example/simple.filska -emit llvm
dialectの実装が見つからない
ビルド時に以下のエラーが発生しました。
undefined reference to `filskalang::PrtOp::print(mlir::OpAsmPrinter&)'
undefined reference to `filskalang::PrtOp::verifyInvariantsImpl()'
TableGenから生成された実装 (.cpp.inc
)がincludeできていないのが原因だったため、以下を追加する必要がありました5。
#include "filskalang/CodeGen/Dialect.h"
using namespace mlir::filskalang;
#include "filskalang/CodeGen/Dialect.cpp.inc"
+#define GET_OP_CLASSES
+#include "filskalang/CodeGen/Ops.cpp.inc"
void FilskalangDialect::initialize() {
addOperations<
#define GET_OP_LIST
#include "filskalang/CodeGen/Ops.cpp.inc"
>();
}
関数のボディに命令を追加できない
サブプログラムを funcOp
にloweringした際、alloca
命令を追加する必要がありましたが、命令が関数の外側に挿入されてしまいました。
命令の挿入ポイントに Func.getBody().front()
を使用することで、想定通りボディ内に入れることができました。
auto Func = Rewriter.create<mlir::LLVM::LLVMFuncOp>(Loc, "main", DummyType);
mlir::Block *EntryBlock = &Func.getBody().front();
Rewriter.setInsertionPointToStart(EntryBlock);
// 以降はmain関数の先頭に挿入される
mlir::Value Cst0 = Rewriter.create<mlir::LLVM::ConstantOp>(
Loc, Rewriter.getI64Type(), Rewriter.getIndexAttr(0));
こちらの実装を参考にしました。
load命令でコンパイルエラー
alloca
命令を load
命令の引数に指定したところ、以下のエラーが発生しました。
filskalang: /usr/lib/llvm-18/include/llvm/Support/Casting.h:566: decltype(auto) llvm::cast(const From &) [To = mlir::detail::TypedValue<mlir::LLVM::LLVMPointerType>, From = mlir::OpResult]: Assertion `isa<To>(Val) && "cast<Ty>() argument of incompatible type!"' failed.
Aborted
正しくは以下のように指定する必要がありました。
auto Alloca = Rewriter.create<mlir::LLVM::AllocaOp>(
Loc, /*resultType*/ mlir::LLVM::LLVMPointerType::get(Context),
/*elementType*/ Rewriter.getF64Type(), Cst0);
auto Load = Rewriter.create<mlir::LLVM::LoadOp>(
- Loc, mlir::Float64Type::get(Context), Alloca);
+ Loc, mlir::Float64Type::get(Context), Alloca.getRes());
loadした値が0のまま
第二引数にはポインタ型ではなく格納している値の型を指定する必要がありました。
// Float64の指定が必要
auto Load = Rewriter.create<mlir::LLVM::LoadOp>(
Loc, mlir::Float64Type::get(Context), MemoryPointer);
C++初心者シリーズ
値が0になってしまう
あやまって参照を持たせていたため解放されてしまいました...
private:
UnaryOperator Operator;
+ NumberLiteral *Operand;
- NumberLiteral *&Operand;
mapが更新されない
キーの型を const char*
ではなく string
にする必要がありました。
おわりに
以上、Filskaコンパイラの設計の骨組みの紹介でした。次回はLLVM IRをバイナリへコンパイルする処理を取り上げる予定です。
-
ビルド時にTableGenファイルからC++ソースコードが生成されます。 ↩
-
まだ使っていないだけで、今後数学関連の命令は
math
,arith
dialectへloweringする予定です。 ↩ -
この例だと、さらに最適化されて引数に直接定数が入る形になっています。 ↩
-
コード生成時に
llvm::SMLoc
から行番号、列番号を抽出できないか試しましたがSegmentation Faultが発生してしまいました。lexer稼働時にリアルタイムで抽出する必要があるようです。 ↩ -
filskalang/CodeGen/Ops.cpp.inc
を2回includeしていますが、defineで定義されている定数によって内容が変わるためいずれも必要です。(この仕組みはどこかで記事にしたい) ↩