Global Assembly
Global Assembly
어셈블리(assembly) 표현식이 Namespace 수준의 comptime 블록 안에 등장하면, 그걸 **글로벌 어셈블리(global assembly)**라고 불러요. 인라인 어셈블리(inline assembly)에 익숙한 분이라면 아마 "그럼 인라인 어셈블리와 뭐가 다르지?"라는 생각이 먼저 들 텐데요, 결론부터 말하면 규칙이 상당히 다릅니다. 이번에는 그 차이를 하나씩 짚어볼게요.
본문
글로벌 어셈블리는 인라인 어셈블리와는 다른 규칙을 따라요. 주의할 점이 몇 가지 있는데, 정리하면 이렇습니다.
- 첫째,
volatile은 쓸 수 없어요. 인라인 어셈블리에서volatile은 "이 어셈블리를 생략하지 마라"는 뜻이지만, 글로벌 어셈블리는 무조건 포함되기 때문에 그런 키워드가 필요 없거든요. - 둘째, 인풋(input)·아웃풋(output)·클로버(clobber)가 없어요. 인라인 어셈블리처럼 값을 주고받거나 레지스터를 알려주는 장치가 존재하지 않습니다.
- 셋째, 모든 글로벌 어셈블리는 그대로(verbatim) 하나의 긴 문자열로 이어붙여 한꺼번에 어셈블됩니다. 그래서 인라인 어셈블리 표현식에 있는 것 같은
%를 둘러싼 템플릿 치환 규칙도 적용되지 않아요.
실제로 쓰는 모습을 볼까요. 다음 코드는 x86-64 리눅스에서 어셈블리로 직접 함수 하나를 정의해 쓰는 예시예요.
const std = @import("std");
const expectEqual = std.testing.expectEqual;
comptime {
asm (
\\.global my_func;
\\.type my_func, @function;
\\my_func:
\\ lea (%rdi,%rsi,1),%eax
\\ retq
);
}
extern fn my_func(a: i32, b: i32) i32;
test "global assembly" {
try expectEqual(46, my_func(12, 34));
}
$ zig test test_global_assembly.zig -target x86_64-linux -fllvm
1/1 test_global_assembly.test.global assembly...OK
All 1 tests passed.
comptime 블록 안의 asm(...)이 바로 글로벌 어셈블리예요. 여기서 .global my_func로 심볼을 밖에 노출시키고, my_func: 라벨 아래에서 %rdi(첫 번째 인자)와 %rsi(두 번째 인자)를 더해 %eax로 반환하는 어셈블리를 짜두었죠. 그리고 extern fn my_func(a: i32, b: i32) i32;로 이 함수가 외부(즉 어셈블리)에 정의돼 있다고 선언한 다음에요.
이런 식으로 글로벌 어셈블리는 소스에 등장하는 순서 그대로 결과물에 이어붙어 컴파일 타임에 하나로 합쳐진다는 점이 특징이에요. 인라인 어셈블리처럼 레지스터 할당이나 클로버를 계산해 줄 필요가 없는, "그냥 붙이면 되는" 더 낮은 수준의 어셈블리라고 생각하면 이해가 쉬워요.
더 알아보기
Inline Assembly— 인풋·아웃풋·클로버·템플릿 치환 규칙이 있는 인라인 어셈블리comptime— 컴파일 타임에 실행되는 블록으로, 글로벌 어셈블리가 등장하는 위치Namespace— 선언이 모이는 네임스페이스 레벨