0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ストレンジコード】Filskaコンパイラ開発計画(3) LLVM IRを出力する【MLIR】

Last updated at Posted at 2024-05-30

シリーズ一覧:

TL; DR

  • FilskaをMLIR製コンパイラへ移植
  • 最低限の命令のみ実装し、処理系の骨組みを作成
    • Lexer: 字句解析
    • Parser: 構文解析
    • Sema: 意味解析
    • CodeGen: MLIR生成
    • Lowering: MLIRをLLVM IRへ変換

はじめに

本シリーズでは、『ストレンジコード』に登場する難解プログラミング言語「Filska」をMLIRへ移植します。本家がPython製インタプリタなので、コンパイラにすることで高速化できるのでは?という淡い期待も寄せています。

(ストレンジコードにはFilska以外の難解プログラミング言語もたくさん登場します!言語好きは必見です (ダイマ)

今回はASTからMLIRを生成し、LLVM IRへ変換するための導線を準備します。

思い立った背景や開発の概要については初回の記事をご覧ください。

記事投稿者のスペック

MLIRもC++も初心者のため、誤った記述があればコメントで指摘いただけますと幸いです :bow:

  • C++: 未経験
  • LLVM: 未経験
  • MLIR: 未経験
  • 言語処理系: インタプリタのみ自作経験あり

処理系の骨組みをつくる

まずは処理系の設計指針を立てるため、ソースコードを読み込んでからLLVM IRを出力するまでに必要な処理を一通り実装します。
(ただし、命令は動作確認できる最低限のみ対応)

Semaまでについては前回の記事をご覧ください。

  • Lexer: 字句解析
    • ソースコード→トークン列
  • Parser: 構文解析
    • トークン列→AST
  • Sema: 意味解析
    • 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 命令

include/filskalang/CodeGen/Ops.td
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(長いので折りたたみ)
include/filskalang/CodeGen/Ops.td
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" のボディに命令が格納されているのが確認できます。

sample.filska
{ main
    set,10
    prt
    hlt
}
生成されたMLIR
"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 関数が複数作成されてしまう
サブプログラムの変換
lib/CodeGen/LowerToLLVM.cpp
  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, 静的単一代入)形式のため、同じ変数へ再代入することは できません
一方、メモリオブジェクトについてこの制約は無いため、再代入可能な変数を allocastore 命令で表すことが可能です。
詳細はLLVMのチュートリアル「Kaleidoscope」言語の解説で詳しく紹介されています。

再代入を実現するLLVM IR(上記サイトより引用)
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命令の実装
lib/CodeGen/LowerToLLVM.cpp
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の変換
lib/CodeGen/LowerToLLVM.cpp
  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
生成されたLLVM IR
; 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::SMLocmlir::Location に変換できない

Lexer, Parserではエラーメッセージに llvm::SourceMgr#PrintMessage を使用しているため、エラー発生個所のソースコード上の位置情報は llvm::SMLoc から取得しています。

一方、filskalang dialectのコード生成時には位置情報として mlir::Location が要求されます。両者はどうやら相互変換ができないよう4なので、両方の形式に対応した位置情報クラスを定義しています。

位置情報クラスの実装
include/filskalang/Basic/Location.h
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のこの設定のように記述できるはずですが...

mlir/examples/toy/Ch7/include/toy/CMakeLists.txt
# 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 に合致していない場合に発生します。オーバーロードごとにエラーが発生するためエラーログが大量発生しますが、その中のどれかに当てはまるように修正すればコンパイルが通ります。

lib/CodeGen/MLIRGen.cpp
    // 正しいシグネチャに修正する
    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

lib/CodeGen/Dialect.cpp
#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() を使用することで、想定通りボディ内に入れることができました。

lib/CodeGen/LowerToLLVM.cpp
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

正しくは以下のように指定する必要がありました。

lib/CodeGen/LowerToLLVM.cpp
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のまま

第二引数にはポインタ型ではなく格納している値の型を指定する必要がありました。

lib/CodeGen/LowerToLLVM.cpp
// 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をバイナリへコンパイルする処理を取り上げる予定です。

  1. ビルド時にTableGenファイルからC++ソースコードが生成されます。

  2. まだ使っていないだけで、今後数学関連の命令は math, arith dialectへloweringする予定です。

  3. この例だと、さらに最適化されて引数に直接定数が入る形になっています。

  4. コード生成時に llvm::SMLoc から行番号、列番号を抽出できないか試しましたがSegmentation Faultが発生してしまいました。lexer稼働時にリアルタイムで抽出する必要があるようです。

  5. filskalang/CodeGen/Ops.cpp.inc を2回includeしていますが、defineで定義されている定数によって内容が変わるためいずれも必要です。(この仕組みはどこかで記事にしたい)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?