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コンパイラ開発計画(4) 実行ファイル出力【MLIR】

Last updated at Posted at 2024-06-03

シリーズ一覧

TL; DR

  • FilskaをMLIR製コンパイラへ移植
  • MLIRとLLVMのCLIオプションが干渉して上手く動作しなかった
  • 代わりにllcを使用しLLVM IRをバイナリに変換

はじめに

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

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

前回ソースコードをLLVM IRに変換するところまで実装したので、今回はLLVM IRを実行ファイルへコンパイルします。

バイナリへのコンパイル処理を追加→失敗

作成中のFilskaコンパイラ(以下「filskalang」)は書籍『Learn LLVM 17』のtinylangをもとに作成しています。

tinylangではソースコードをLLVM IR経由でアセンブリ等へコンパイル可能です。その際に使用されるオプションが --filetype で、指定した形式は llvm::codegen::getFileType() から取得可能です。

ところが、filskalangで同様の実装を組み込んでも --filetype オプションが使えるようになりませんでした。原因は分からないのですが、MLIR用のCLIオプションと干渉してしまったのかもしれません1

また、仮に上記の問題を解決したとしても、出力されたアセンブリを別途 clang で実行ファイルへコンパイルする必要があります。

代案: llcによるコンパイル

そこで、3つのコマンドでコンパイルを分担することにしました。Unix哲学

  • filskalang: FilskaのソースコードをLLVM IRへコンパイル
  • llc: LLVM IRをアセンブリへコンパイル(LLVMの標準ツール)
  • clang: アセンブリを実行ファイルへコンパイル
$ ./bin/filskalang sample.filska -emit llvm > sample.filska.llir
$ llc sample.filska.llir -o sample.filska.llir.s
$ clang -no-pie sample.filska.llir.s sample

注意点として、clang のオプション -no-pie が無いと以下のエラーが発生してしまいます。

/usr/bin/ld: /tmp/foo-208b45.o: relocation R_X86_64_32 against `.rodata' can not be used when making a PIE object; recompile with -fPIE
clang: error: linker command failed with exit code 1 (use -v to see invocation)

対処にはこちらの記事を参考にしました。

処理をスクリプト化

上記コマンドを毎回打ち込むのは辛いので、スクリプトにまとめています。

$ ./filskac sample.filska
# or
$ ./filskac -o sample sample.filska

で実行ファイルが得られます。ソースコードは以下の通りです。

filskac
#!/bin/bash

set -e

function usage() {
    echo "Filska compiler"
    echo "filskac [-o sample] sample.filska"
} 

while getopts "o:h" OPT
do
    case $OPT in
        o) OUTPUT=true;OUTPUT_FILE_OVERRIDE=$OPTARG;;
        h) HELP=true;;
    esac
done
shift $(($OPTIND - 1))


SRC_FILE_NAME=$1

if [ -n "$HELP" ]; then
    usage
    exit 0
fi

if [ -z "$SRC_FILE_NAME" ]; then
    echo "error: source file must be specified"
    usage
    exit 1
fi

OUTPUT_FILE_NAME=${SRC_FILE_NAME%.filska}
if [ -n "$OUTPUT_FILE_OVERRIDE" ]; then
    OUTPUT_FILE_NAME=$OUTPUT_FILE_OVERRIDE
fi

if [ "$OUTPUT_FILE_NAME" = "$SRC_FILE_NAME" ]; then
    echo "error: stop compiling because the output file name '$OUTPUT_FILE_NAME' is same as the src file name"
    echo "make sure that src file name is Filska source code (*.filska)"
    exit 1
fi

LLIR_FILE_NAME=${SRC_FILE_NAME}.llir
ASSEMBLY_FILE_NAME=${LLIR_FILE_NAME}.s

./bin/filskalang $SRC_FILE_NAME -emit llvm > $LLIR_FILE_NAME
llc $LLIR_FILE_NAME -o $ASSEMBLY_FILE_NAME
clang -no-pie $ASSEMBLY_FILE_NAME -o $OUTPUT_FILE_NAME

本題とは関係ありませんが、バイナリとソースコードが同名の場合にエラーになるようにしました。
学生時代、C言語の授業中にソースコードをバイナリで上書きし課題を吹き飛ばしてしまったトラウマからまだ立ち直れません

$ ./filskac -o tests/src/compile/dummy tests/src/compile/dummy
error: stop compiling because the output file name 'tests/src/compile/dummy' is same as the src file name
make sure that src file name is Filska source code (*.filska)

コンパイルしてみる

example/simple.filska
{ main
    set,10
    prt
    hlt
}
$ ./filskac example/simple.filska
$ ./example/simple
10.000000

対応しているのは3命令だけとはいえ、ついにFilskaをネイティブコードまでコンパイルすることができました!

バイナリのサイズは17kBと思っていたより大きかったです。

$ ls -lh example/simple
-rwxr-xr-x 1 syuparn syuparn 17K May  6 21:48 example/simple

中間生成物のアセンブリは以下の通りです。
Filskaのソースコードとの対応箇所もコメントされています。

アセンブリ(長いので折りたたみ)
	.text
	.file	"LLVMDialectModule"
	.section	.rodata.cst8,"aM",@progbits,8
	.p2align	3, 0x0                          # -- Begin function main
.LCPI0_0:
	.quad	0x4024000000000000              # double 10
	.text
	.globl	main
	.p2align	4, 0x90
	.type	main,@function
main:                                   # @main
.Lfunc_begin0:
	.file	1 "example" "simple.filska"
	.loc	1 7 0                           # simple.filska:7:0
	.cfi_startproc
# %bb.0:
	pushq	%rax
	.cfi_def_cfa_offset 16
	movabsq	$4621819117588971520, %rax      # imm = 0x4024000000000000
.Ltmp0:
	.loc	1 2 8 prologue_end              # simple.filska:2:8
	movq	%rax, (%rsp)
	movsd	.LCPI0_0(%rip), %xmm0           # xmm0 = mem[0],zero
	.loc	1 3 8                           # simple.filska:3:8
	movl	$frmt_spec, %edi
	movb	$1, %al
	callq	printf@PLT
	.loc	1 1 7 epilogue_begin            # simple.filska:1:7
	popq	%rax
	.cfi_def_cfa_offset 8
	retq
.Ltmp1:
.Lfunc_end0:
	.size	main, .Lfunc_end0-main
	.cfi_endproc
                                        # -- End function
	.type	frmt_spec,@object               # @frmt_spec
	.section	.rodata,"a",@progbits
frmt_spec:
	.asciz	"%f"
	.size	frmt_spec, 3

	.section	.debug_abbrev,"",@progbits
	.byte	1                               # Abbreviation Code
	.byte	17                              # DW_TAG_compile_unit
	.byte	0                               # DW_CHILDREN_no
	.byte	37                              # DW_AT_producer
	.byte	14                              # DW_FORM_strp
	.byte	19                              # DW_AT_language
	.byte	5                               # DW_FORM_data2
	.byte	3                               # DW_AT_name
	.byte	14                              # DW_FORM_strp
	.byte	16                              # DW_AT_stmt_list
	.byte	23                              # DW_FORM_sec_offset
	.byte	17                              # DW_AT_low_pc
	.byte	1                               # DW_FORM_addr
	.byte	18                              # DW_AT_high_pc
	.byte	6                               # DW_FORM_data4
	.byte	0                               # EOM(1)
	.byte	0                               # EOM(2)
	.byte	0                               # EOM(3)
	.section	.debug_info,"",@progbits
.Lcu_begin0:
	.long	.Ldebug_info_end0-.Ldebug_info_start0 # Length of Unit
.Ldebug_info_start0:
	.short	4                               # DWARF version number
	.long	.debug_abbrev                   # Offset Into Abbrev. Section
	.byte	8                               # Address Size (in bytes)
	.byte	1                               # Abbrev [1] 0xb:0x1b DW_TAG_compile_unit
	.long	.Linfo_string0                  # DW_AT_producer
	.short	2                               # DW_AT_language
	.long	.Linfo_string1                  # DW_AT_name
	.long	.Lline_table_start0             # DW_AT_stmt_list
	.quad	.Lfunc_begin0                   # DW_AT_low_pc
	.long	.Lfunc_end0-.Lfunc_begin0       # DW_AT_high_pc
.Ldebug_info_end0:
	.section	.debug_str,"MS",@progbits,1
.Linfo_string0:
	.asciz	"MLIR"                          # string offset=0
.Linfo_string1:
	.asciz	"<unknown>"                     # string offset=5
	.section	".note.GNU-stack","",@progbits
	.section	.debug_line,"",@progbits
.Lline_table_start0:

おわりに

以上、Filskaコンパイラでバイナリを生成した方法の紹介でした。コンパイルの全工程ができたので、次回以降はサブプログラムの無限ループ、ジャンプを実装していく予定です。

  1. tinylangではMLIRは扱っていません。

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?