ITエンジニアのブログ

IT企業でエンジニアやってる人間の日常について

プログラミング言語を作る。第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

グローバル変数はプログラムファイル内に定義され、プログラムファイルから起動されたプロセス内で値を変更したりしているようです。

まずは中間コード生成が目的なので、グローバル変数はグローバルな関数と同様の立ち位置で処理すれば良さそうということがわかりました。