이전 편 / LLVM - 0. 들어가기에 앞서에서 표현했듯 LLVM은 컴파일러 툴체인입니다.
컴파일러(Compiler), 최적화기(Optimizer), JIT(Just In-time Compilation) 생성기 등 다양한 도구들의 집합입니다.

장점은 다양한 아키텍처를 지원하는 뛰어난 이식성과, 철저한 모듈화 되어 있어 재사용성이 뛰어납니다.
때문에 기존 컴파일러의 단점을 보완하는 만큼, 사용범위가 점점 넓어지고 있습니다.

흐름

Clang

간단히 LLVM의 흐름에 대해 알아보겠습니다.
먼저 LLVMClang 클랭이라고 읽는 프론트 엔드가 있습니다.

프론트엔드 (Clang)

Clanggcc를 대체하기 위하여 개발되었습니다.
C, C++, Objective C 등등 다양한 언어를 파싱할 수 있습니다.

프론트 엔드가 하는일은 프로그래밍 언어를 LLVM의 백 엔드가 이해할 수 있도록 파싱하고,
중간 표현(Intermediate Representation)을 생성하고, 최적화를 위하여 AST(Abstract Syntax Tree)를 생성합니다.

이후 생성 된 AST(Abstract Syntax Tree)를 기반으로 LLVM Sema의 최적화 단계가 진행 됩니다.
LLVM Sema에 의하여 분석 및 최적화가 진행 된 ASTAST'라고 보았을 때, AST'를 기반으로 LLVM CodeGen이 최적화 된 중간 표현(Intermediate Representation)을 생성하게 됩니다. 프론트엔드에 의하여 최적화 된 중간 표현(Intermediate Representation)은 백엔드에 전달합니다.

백엔드

중간 표현(Intermediate Representation)은 어셈블리와 유사한 저급 언어이며, LLVM의 핵심입니다.
백엔드로 전달 된 최적화 된 중간 표현(Intermediate Representation)이 LLVM에 의하여 오브젝트 파일로 컴파일 되게 됩니다.

With Swift

Swift

LLVM을 이야기하면서 빼놓을 수 없는 언어가 한가지 있습니다. 바로 Swift 입니다. Swift는 LLVM의 저자인 크리스 래트너가 만든 언어로 컴파일러 툴체인으로 LLVM을 사용하고 있습니다.
Swift와 함께하는 LLVM은 어떨까요?

독특하게도 Swift는 LLVM의 파싱 과정 이전에 SIL(Swift Intermediate Language)이라는 중간 언어가 존재합니다.
SIL 파서가 Swift를 SIL로 파싱하고, SILLLVM의 프론트엔드가 LLVM IR로 파싱하는 형태로,
총 2번 번역과 최적화 과정을 거치게 됩니다.

LLVM IR은 저급 언어이지만, 비교적 읽을 수 있는 형태인 반면에 SIL은 전혀 그렇지 않습니다!
읽기가 굉장히 까다롭습니다.

예시를 보여드리겠습니다.

LLVM IR

LLVM IR의 hello world

@.str = private unnamed_addr constant [13 x i8] c"hello world\0A\00"

declare i32 @puts(i8* nocapture) nounwind

define i32 @main() { ; i32()*
    %cast210 = getelementptr [13 x i8],[13 x i8]* @.str, i64 0, i64 0

    call i32 @puts(i8* %cast210)
    ret i32 0
}

!0 = !{i32 42, null, !"string"}
!foo = !{!0}

SIL(Swift Intermediate Language)

SIL의 hello world

sil_stage canonical

import Builtin
import Swift
import SwiftShims

func main()

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = integer_literal $Builtin.Int32, 0          // user: %3
  %3 = struct $Int32 (%2 : $Builtin.Int32)        // user: %4
  return %3 : $Int32                              // id: %4
} // end sil function 'main'

// main()
sil hidden @$s4mainAAyyF : $@convention(thin) () -> () {
bb0:
  %0 = integer_literal $Builtin.Word, 1           // user: %2
  // function_ref _allocateUninitializedArray<A>(_:)
  %1 = function_ref @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // user: %2
  %2 = apply %1<Any>(%0) : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer) // users: %4, %3
  %3 = tuple_extract %2 : $(Array<Any>, Builtin.RawPointer), 0 // user: %15
  %4 = tuple_extract %2 : $(Array<Any>, Builtin.RawPointer), 1 // user: %5
  %5 = pointer_to_address %4 : $Builtin.RawPointer to [strict] $*Any // user: %12
  %6 = string_literal utf8 "hello world"          // user: %11
  %7 = integer_literal $Builtin.Word, 11          // user: %11
  %8 = integer_literal $Builtin.Int1, -1          // user: %11
  %9 = metatype $@thin String.Type                // user: %11
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %10 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %11
  %11 = apply %10(%6, %7, %8, %9) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %13
  %12 = init_existential_addr %5 : $*Any, $String // user: %13
  store %11 to %12 : $*String                     // id: %13
  // function_ref _finalizeUninitializedArray<A>(_:)
  %14 = function_ref @$ss27_finalizeUninitializedArrayySayxGABnlF : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // user: %15
  %15 = apply %14<Any>(%3) : $@convention(thin) <τ_0_0> (@owned Array<τ_0_0>) -> @owned Array<τ_0_0> // users: %24, %21
  // function_ref default argument 1 of print(_:separator:terminator:)
  %16 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String // user: %17
  %17 = apply %16() : $@convention(thin) () -> @owned String // users: %23, %21
  // function_ref default argument 2 of print(_:separator:terminator:)
  %18 = function_ref @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String // user: %19
  %19 = apply %18() : $@convention(thin) () -> @owned String // users: %22, %21
  // function_ref print(_:separator:terminator:)
  %20 = function_ref @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> () // user: %21
  %21 = apply %20(%15, %17, %19) : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()
  release_value %19 : $String                     // id: %22
  release_value %17 : $String                     // id: %23
  release_value %15 : $Array<Any>                 // id: %24
  %25 = tuple ()                                  // user: %26
  return %25 : $()                                // id: %26
} // end sil function '$s4mainAAyyF'

// _allocateUninitializedArray<A>(_:)
sil [serialized] [always_inline] [_semantics "array.uninitialized_intrinsic"] @$ss27_allocateUninitializedArrayySayxG_BptBwlF : $@convention(thin) <τ_0_0> (Builtin.Word) -> (@owned Array<τ_0_0>, Builtin.RawPointer)

// String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
sil [serialized] [always_inline] [readonly] [_semantics "string.makeUTF8"] @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String

// _finalizeUninitializedArray<A>(_:)
sil shared_external [serialized] [readnone] [_semantics "array.finalize_intrinsic"] @$ss27_finalizeUninitializedArrayySayxGABnlF : $@convention(thin) <Element> (@owned Array<Element>) -> @owned Array<Element> {
// %0                                             // user: %2
bb0(%0 : $Array<Element>):
  %1 = alloc_stack $Array<Element>                // users: %6, %5, %4, %2
  store %0 to %1 : $*Array<Element>               // id: %2
  // function_ref Array._endMutation()
  %3 = function_ref @$sSa12_endMutationyyF : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> () // user: %4
  %4 = apply %3<Element>(%1) : $@convention(method) <τ_0_0> (@inout Array<τ_0_0>) -> ()
  %5 = load %1 : $*Array<Element>                 // user: %7
  dealloc_stack %1 : $*Array<Element>             // id: %6
  return %5 : $Array<Element>                     // id: %7
} // end sil function '$ss27_finalizeUninitializedArrayySayxGABnlF'

// default argument 1 of print(_:separator:terminator:)
sil shared_external [serialized] @$ss5print_9separator10terminatoryypd_S2StFfA0_ : $@convention(thin) () -> @owned String {
bb0:
  %0 = string_literal utf8 " "                    // user: %5
  %1 = integer_literal $Builtin.Word, 1           // user: %5
  %2 = integer_literal $Builtin.Int1, -1          // user: %5
  %3 = metatype $@thin String.Type                // user: %5
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
  %5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
  return %5 : $String                             // id: %6
} // end sil function '$ss5print_9separator10terminatoryypd_S2StFfA0_'

// default argument 2 of print(_:separator:terminator:)
sil shared_external [serialized] @$ss5print_9separator10terminatoryypd_S2StFfA1_ : $@convention(thin) () -> @owned String {
bb0:
  %0 = string_literal utf8 "\n"                   // user: %5
  %1 = integer_literal $Builtin.Word, 1           // user: %5
  %2 = integer_literal $Builtin.Int1, -1          // user: %5
  %3 = metatype $@thin String.Type                // user: %5
  // function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
  %4 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %5
  %5 = apply %4(%0, %1, %2, %3) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %6
  return %5 : $String                             // id: %6
} // end sil function '$ss5print_9separator10terminatoryypd_S2StFfA1_'

// print(_:separator:terminator:)
sil @$ss5print_9separator10terminatoryypd_S2StF : $@convention(thin) (@guaranteed Array<Any>, @guaranteed String, @guaranteed String) -> ()

// Array._endMutation()
sil shared_external [serialized] [_semantics "array.end_mutation"] @$sSa12_endMutationyyF : $@convention(method) <Element> (@inout Array<Element>) -> () {
// %0                                             // users: %9, %1
bb0(%0 : $*Array<Element>):
  %1 = struct_element_addr %0 : $*Array<Element>, #Array._buffer // user: %2
  %2 = struct_element_addr %1 : $*_ArrayBuffer<Element>, #_ArrayBuffer._storage // user: %3
  %3 = struct_element_addr %2 : $*_BridgeStorage<__ContiguousArrayStorageBase>, #_BridgeStorage.rawValue // user: %4
  %4 = load %3 : $*Builtin.BridgeObject           // user: %5
  %5 = end_cow_mutation %4 : $Builtin.BridgeObject // user: %6
  %6 = struct $_BridgeStorage<__ContiguousArrayStorageBase> (%5 : $Builtin.BridgeObject) // user: %7
  %7 = struct $_ArrayBuffer<Element> (%6 : $_BridgeStorage<__ContiguousArrayStorageBase>) // user: %8
  %8 = struct $Array<Element> (%7 : $_ArrayBuffer<Element>) // user: %9
  store %8 to %0 : $*Array<Element>               // id: %9
  %10 = tuple ()                                  // user: %11
  return %10 : $()                                // id: %11
} // end sil function '$sSa12_endMutationyyF'



// Mappings from '#fileID' to '#filePath':
//   'main/main.swift' => 'main.swift'

같은 내용을 GitHub에서도 보실 수 있습니다.

LLVM IR에 비하여 SIL은 정말로 읽기 힘듭니다!
단순한 hello world에도 굉장히 많은 코드가 인상 깊습니다.

마무리

LLVM을 흐름을 훑어 보면서, 굉장히 많은 키워드가 등장했습니다.
Clang, LLVM IR, LLVM Sema, LLVM CodeGen에서부터 SIL까지 입니다.
제가 목표로 하는것은 최종적으로 “Swift에는 왜 SIL이 필요했는가?“입니다.
목표에 도달하기 위해서는 Clang, LLVM IR, LLVM Sema, LLVM CodeGen을 알고 있어야 합니다.

다음 시간에는 Clang에 대해 조금 더 자세히 알아보는 시간을 갖도록 하겠습니다.

혹시라도 글의 내용이 잘못 된 경우 가감없이 의견 주시면 즉시 반영하도록 하겠습니다.
읽어주셔서 감사합니다.

Reference