プログラミング言語を作る。第10回:グローバル変数
第8,9回において、C言語と似た文法の言語を用い、型検査や3アドレスコードへの変換などを行ってきました。そろそろ独自言語の文法を考案し、それを主体として細かな設定について考えていきます。
例えば、次のような入力を与えるとします。大体想像つくと思いますが、グローバル変数 a を定義し、それを破壊的変更して文字列に変換し標準出力に起こすという操作を想定しています。グローバル変数というのは普通は使用を避けるものですが、言語仕様上に無いというのは、いざという時に困るかもしれませんので入れておきます。
var a = 1; def main () { a = 2; printLine(a.toString()); }
さて、そもそもグローバル変数というのは既存の言語ではどのように扱われているのでしょうか。よくわからなかったので、C言語を使って調べてみました。
次のプログラムは、グローバル変数 sum = 0 に 1 から 10 までの数値を足しあわせて表示するC言語のプログラムです。
#include <stdio.h> int sum = 0; int main(void){ int i; for(i = 1; i <= 10; i++){ sum += i; } printf("%d\n", sum); return 0; }
LLVM やアセンブリにおけるグローバル変数の扱い方について確認します。まず LLVM から、
@sum = global i32 0, align 4 @.str = private unnamed_addr constant [4 x i8] c"%d\0A\00", align 1 ; Function Attrs: nounwind uwtable define i32 @main() #0 { %1 = alloca i32, align 4 %i = alloca i32, align 4 store i32 0, i32* %1 store i32 1, i32* %i, align 4 br label %2 ; <label>:2 ; preds = %9, %0 %3 = load i32* %i, align 4 %4 = icmp sle i32 %3, 10 br i1 %4, label %5, label %12 ; <label>:5 ; preds = %2 %6 = load i32* %i, align 4 %7 = load i32* @sum, align 4 %8 = add nsw i32 %7, %6 store i32 %8, i32* @sum, align 4 br label %9 ; <label>:9 ; preds = %5 %10 = load i32* %i, align 4 %11 = add nsw i32 %10, 1 store i32 %11, i32* %i, align 4 br label %2 ; <label>:12 ; preds = %2 %13 = load i32* @sum, align 4 %14 = call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i32 0, i32 0), i32 %13) ret i32 0 } declare i32 @printf(i8*, ...) #1
C言語と同様に、グローバルな環境にそのまま定義されています。中間コード生成までは特にグローバル変数への処置を考える必要はなさそうです。続いて x86-64 アセンブリ
.globl sum .bss .align 4 .type sum, @object .size sum, 4 sum: .zero 4 .section .rodata .LC0: .string "%d\n" .text .globl main .type main, @function main: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 subq $16, %rsp movl $1, -4(%rbp) jmp .L2 .L3: movl sum(%rip), %eax addl -4(%rbp), %eax movl %eax, sum(%rip) addl $1, -4(%rbp) .L2: cmpl $10, -4(%rbp) jle .L3 movl sum(%rip), %edx movl $.LC0, %eax movl %edx, %esi movq %rax, %rdi movl $0, %eax call printf movl $0, %eax leave .cfi_def_cfa 7, 8 ret .cfi_endproc
グローバル変数はプログラムファイル内に定義され、プログラムファイルから起動されたプロセス内で値を変更したりしているようです。
まずは中間コード生成が目的なので、グローバル変数はグローバルな関数と同様の立ち位置で処理すれば良さそうということがわかりました。