이전 편 / LLVM - 0. 들어가기에 앞서에서 표현했듯 LLVM
은 컴파일러 툴체인입니다.
컴파일러(Compiler), 최적화기(Optimizer), JIT(Just In-time Compilation) 생성기 등 다양한 도구들의 집합입니다.
장점은 다양한 아키텍처를 지원하는 뛰어난 이식성과, 철저한 모듈화 되어 있어 재사용성이 뛰어납니다.
때문에 기존 컴파일러의 단점을 보완하는 만큼, 사용범위가 점점 넓어지고 있습니다.
흐름
간단히 LLVM
의 흐름에 대해 알아보겠습니다.
먼저 LLVM
은 Clang
클랭이라고 읽는 프론트 엔드가 있습니다.
프론트엔드 (Clang)
Clang
은 gcc
를 대체하기 위하여 개발되었습니다.
C, C++, Objective C 등등 다양한 언어를 파싱할 수 있습니다.
프론트 엔드가 하는일은 프로그래밍 언어를 LLVM의 백 엔드가 이해할 수 있도록 파싱하고,
중간 표현(Intermediate Representation)을 생성하고, 최적화를 위하여 AST(Abstract Syntax Tree)
를 생성합니다.
이후 생성 된 AST(Abstract Syntax Tree)를 기반으로 LLVM Sema
의 최적화 단계가 진행 됩니다.LLVM Sema
에 의하여 분석 및 최적화가 진행 된 AST
를 AST'
라고 보았을 때,
AST'
를 기반으로 LLVM CodeGen
이 최적화 된 중간 표현(Intermediate Representation)
을 생성하게 됩니다.
프론트엔드에 의하여 최적화 된 중간 표현(Intermediate Representation)
은 백엔드에 전달합니다.
백엔드
중간 표현(Intermediate Representation)은 어셈블리와 유사한 저급 언어이며, LLVM의 핵심입니다.
백엔드로 전달 된 최적화 된 중간 표현(Intermediate Representation)이 LLVM
에 의하여 오브젝트 파일로 컴파일 되게 됩니다.
With Swift
LLVM
을 이야기하면서 빼놓을 수 없는 언어가 한가지 있습니다. 바로 Swift 입니다.
Swift는 LLVM
의 저자인 크리스 래트너가 만든 언어로 컴파일러 툴체인으로 LLVM
을 사용하고 있습니다.
Swift와 함께하는 LLVM
은 어떨까요?
독특하게도 Swift는 LLVM의 파싱 과정 이전에 SIL(Swift Intermediate Language)
이라는 중간 언어가 존재합니다.
SIL 파서가 Swift를 SIL
로 파싱하고, SIL
을 LLVM
의 프론트엔드가 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
에 대해 조금 더 자세히 알아보는 시간을 갖도록 하겠습니다.
혹시라도 글의 내용이 잘못 된 경우 가감없이 의견 주시면 즉시 반영하도록 하겠습니다.
읽어주셔서 감사합니다.