inline fn

inline fn

함수를 정의할 때 inline 키워드를 붙이면, 그 함수는 호출 지점(callsite)에서 의미론적으로 인라인(inline) 되어요. 겉보기엔 컴파일러가 최적화할 때 참고할 만한 힌트처럼 보이지만, 사실 이 키워드는 함수 호출에 관여하는 타입과 값에 실제로 영향을 줍니다. 뒤에서 보겠지만 이건 "그냥 힌트"가 아니에요.

일반 함수 호출과 달리, inline 함수의 호출 지점에서 컴파일 타임에 알려진 인자Compile Time Parameters로 취급됩니다. 이 효과는 경우에 따라 함수의 반환값까지 전파될 수 있어요. 직접 확인해 볼게요.

여기서 주의할 점은 코드의 if 조건이 정수 리터럴(120034)로 호출한다는 거예요. inline 덕분에 이 값들이 컴파일 타임 상수로 취급되면서, a + b도 컴파일 시점에 계산될 수 있습니다.

출처: Zig Documentation

본문

inline 키워드를 함수 정의에 추가하면 그 함수는 호출 지점에서 의미론적으로 인라인 됩니다. 이건 최적화 과정에서 지켜볼 수 있는 힌트이기는 하지만(Inline Is Not A Hint), 그보다 함수 호출에 관여하는 타입과 값에 직접적인 영향을 준다는 점이 핵심이에요.

일반 함수 호출과 달리, inline 함수 호출 지점의 인자 중 컴파일 타임에 알려진 것Compile Time Parameters로 취급됩니다. 그리고 이건 잠재적으로 함수의 반환값까지 전파될 수 있죠.

// inline_call.zig
const std = @import("std");

pub fn main() void {
    if (foo(1200, 34) != 1234) {
        @compileError("bad");
    }
}

inline fn foo(a: i32, b: i32) i32 {
    std.debug.print("runtime a = {} b = {}", .{ a, b });
    return a + b;
}
$ zig build-exe inline_call.zig
$ ./inline_call
runtime a = 1200 b = 34

만약 여기서 inline을 지우면, 이 테스트는 통과하지 못하고 @compileError("bad")로 컴파일 에러가 나게 됩니다. 즉 인라인이 아니라면 foo(1200, 34)가 반환하는 값을 컴파일러가 미리 알 수 없어서, 그 비교 결과가 상수가 되지 못하기 때문이에요.

Inline Is Not A Hint

inline을 쓴다는 건, 그만큼 컴파일러가 할 수 있는 일이 제한된다는 뜻이기도 합니다. 바이너리 크기, 컴파일 속도, 심지어 런타임 성능까지 나빠질 수 있어요. 그 대신, 이렇게 쓰고 싶은 경우들을 해결해 줍니다:

  • 특정 스택 프레임이 호출 스택에 존재함을 보장할 수 있어요. 디버깅 목적이거나 Assembly와 연동할 때 유용합니다.
  • 호출 지점에서 마침 comptime인 인자들을 강제로 함수의 반환값까지 전파시킵니다. 위의 예시가 바로 이 경우죠.
  • 컴파일러 백엔드 구현의 한계를 우회할 때도 씁니다.

그냥 "이 함수를 인라인하면 좋겠다"는 힌트만 컴파일러에 전달하고 싶다면, inline 대신 @branchHint를 사용하면 돼요.

더 알아보기(Learn more)

  • Compile Time Parameters — inline 함수 인자의 컴파일 타임 상수 취급과 관련돼요.
  • comptime — 컴파일 타임에 평가되는 값이 어디까지 전파되는지.
  • Assembly — 스택 프레임 보장이 필요한 어셈블리 연동 사례.
  • @branchHint — 인라인 힌트만 필요할 때 쓰는 대안.