IL2CPP 내부 소개

거의 1년 전부터 유니티에서 스크립팅의 미래에 대해 이야기하기 시작했습니다. 새로운 IL2CPP 스크립팅 백엔드는 성능이 뛰어나고 휴대성이 뛰어난 가상 머신을 Unity에 도입할 것을 약속했습니다. 1월에는 IL2CPP를 사용한 첫 번째 플랫폼인 iOS 64비트를 출시했습니다. Unity 5 릴리스에는 또 다른 플랫폼인 WebGL이 추가되었습니다. 엄청난 사용자 커뮤니티의 의견 덕분에 IL2CPP에 대한 많은 패치 릴리스 업데이트를 제공하여 컴파일러와 런타임을 꾸준히 개선해 왔습니다. IL2CPP의 개선을 멈출 계획은 없지만, 한 발짝 물러서서 IL2CPP가 어떻게 작동하는지에 대해 잠시 설명해 드리는 것이 좋겠다고 생각했습니다. 앞으로 몇 달에 걸쳐 다음 주제(및 다른 주제)에 대해 IL2CPP 내부 시리즈 포스팅을 작성할 계획입니다:
1.기본 사항 - 툴체인 및 명령줄 인수(이 게시물)
2. 생성된 코드 둘러보기
4. 메서드 호출 (일반 메서드, 가상 메서드 등)
5. 일반 공유 구현
7. 가비지 컬렉터 통합
8. 테스트 프레임워크 및 사용법
이 시리즈 포스팅에서는 앞으로 반드시 변경될 IL2CPP 구현에 대한 몇 가지 세부 사항을 논의할 예정입니다. 앞으로도 유용하고 흥미로운 정보를 제공할 수 있기를 바랍니다.
IL2CPP라고 부르는 기술은 크게 두 가지로 나뉩니다.
- AOT(미리 보기) 컴파일러
- 가상 머신을 지원하는 런타임 라이브러리
AOT 컴파일러는 .NET 컴파일러의 저수준 출력인 중간 언어(IL)를 C++ 소스 코드로 변환합니다. 런타임 라이브러리는 가비지 컬렉터, 스레드와 파일에 대한 플랫폼 독립적인 액세스, 내부 호출(관리되는 데이터 구조를 직접 수정하는 네이티브 코드) 구현과 같은 서비스 및 추상화를 제공합니다.
IL2CPP AOT 컴파일러의 이름은 il2cpp.exe입니다. Windows의 경우 Editor\Data\il2cpp 디렉터리에서 찾을 수 있습니다. OSX의 경우 Unity 설치의 Contents/Frameworks/il2cpp/build 디렉터리에 있습니다.
il2cpp.exe 유틸리티는 관리형 실행 파일로, 전적으로 C#으로 작성되었습니다. 저희는 IL2CPP를 개발하는 동안 .NET 및 Mono 컴파일러를 모두 사용하여 컴파일했습니다. il2cpp.exe 유틸리티는 Unity와 함께 제공되는 Mono 컴파일러로 컴파일된 관리형 어셈블리를 받아 플랫폼별 C++ 컴파일러에 전달할 C++ 코드를 생성합니다.
IL2CPP 툴체인은 다음과 같이 생각할 수 있습니다:

IL2CPP 기술의 다른 부분은 가상 머신을 지원하는 런타임 라이브러리입니다. 이 라이브러리는 거의 전적으로 C++ 코드를 사용하여 구현했습니다(플랫폼별 어셈블리 코드가 약간 포함되어 있지만, 이 부분은 두 사람만 알도록 하죠). 런타임 라이브러리를 libil2cpp라고 부르며, 플레이어 실행 파일에 링크된 정적 라이브러리로 제공됩니다. IL2CPP 기술의 주요 이점 중 하나는 이 간단하고 휴대 가능한 런타임 라이브러리입니다.
Unity와 함께 제공되는 libil2cpp의 헤더 파일을 살펴보면 libil2cpp 코드가 어떻게 구성되어 있는지에 대한 단서를 찾을 수 있습니다(Windows의 경우 Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include 디렉토리, OSX의 경우 Contents/Frameworks/il2cpp/libil2cpp 디렉토리에서 찾을 수 있습니다). 예를 들어, il2cpp.exe에서 생성된 C++ 코드와 libil2cpp 런타임 간의 인터페이스는 codegen/il2cpp-codegen.h 헤더 파일에 있습니다.
런타임의 핵심 부분 중 하나는 가비지 컬렉터입니다. 유니티는 벰-데머스-바이저 가비지 컬렉터인 libgc와 함께 Unity 5를 출시합니다. 하지만 libil2cpp는 다른 가비지 수집기를 사용할 수 있도록 설계되었습니다. 예를 들어, 저희는 CoreCLR의 일부로 오픈소스로 제공된 Microsoft GC의 통합을 연구하고 있습니다. 이 시리즈의 뒷부분에 있는 가비지 컬렉터 통합에 대한 글에서 이에 대해 자세히 설명하겠습니다.
한 가지 예를 살펴보겠습니다. Windows에서 Unity 5.0.1을 사용하며, 새롭고 빈 프로젝트로 시작하겠습니다. 변환할 사용자 스크립트를 하나 이상 만들 수 있도록 이 간단한 MonoBehaviour 컴포넌트를 메인 카메라 게임 오브젝트에 추가하겠습니다:
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
WebGL 플랫폼용으로 빌드할 때 프로세스 탐색기를 사용하여 Unity가 il2cpp.exe를 실행하는 데 사용한 명령줄을 확인할 수 있습니다:
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
명령줄이 꽤 길고 끔찍하니 일단 풀어보겠습니다. 먼저 Unity가 이 실행 파일을 실행합니다:
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
명령줄의 다음 인수는 il2cpp.exe 유틸리티 자체입니다.
알 수 없는 블록 유형 "codeBlock"인 경우 `serializers.types` 프롭에서 해당 블록에 대한 직렬화기를 지정하세요.
나머지 명령줄 인수는 mono.exe가 아닌 il2cpp.exe로 전달됩니다. 살펴보겠습니다. 먼저 Unity는 5개의 플래그를 il2cpp.exe에 전달합니다:
- --copy-level=None
- il2cpp.exe가 생성된 C++ 코드의 특수 파일 복사를 수행하지 않도록 지정합니다.
- --enable-generic-sharing
- 코드 및 바이너리 크기를 줄이는 기능입니다. IL2CPP는 가능한 경우 일반 메서드의 구현을 공유할 것입니다.
- --enable-unity-event-support
- 리플렉션을 통해 액세스하는 Unity 이벤트의 코드가 올바르게 생성되도록 특별 지원됩니다.
- --output-format=Compact
- 유형 및 메서드 이름에 더 적은 문자가 필요한 형식으로 C++ 코드를 생성합니다. 이 코드는 IL 코드의 이름이 보존되지 않기 때문에 디버깅하기 어렵지만, C++ 컴파일러가 구문 분석할 코드가 적기 때문에 컴파일 속도가 빨라지는 경우가 많습니다.
- --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt"
- 기본(그리고 비어 있는) 추가 유형 파일을 사용합니다. 이 파일을 Unity 프로젝트에 추가하여 런타임에 생성되지만 IL 코드에는 없는 일반 또는 배열 유형을 il2cpp.exe에 알려줄 수 있습니다.
이러한 명령줄 인수는 이후 릴리스에서 변경될 수 있으며 변경될 예정이라는 점에 유의하세요. 아직 il2cpp.exe에 대한 안정적이고 지원되는 명령줄 인수 집합을 확보한 단계는 아닙니다. 마지막으로 명령줄에 두 개의 파일과 하나의 디렉터리 목록이 표시됩니다:
- "C:\사용자\조쉬 피터슨\문서\IL2CPP 블로그 예제\Temp\스토리지 영역\데이터\관리형\어셈블리-CSharp.dll"
- "C:\사용자\조쉬 피터슨\문서\IL2CPP 블로그 예제\Temp\스태깅 영역\데이터\관리되는\UnityEngine.UI.dll"
- "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"
il2cpp.exe 유틸리티는 변환해야 하는 모든 IL 어셈블리 목록을 허용합니다. 이 경우 간단한 모노비헤이비어가 포함된 어셈블리인 Assembly-CSharp.dll과 GUI 어셈블리인 UnityEngine.UI.dll이 있습니다. 여기에는 눈에 띄게 누락된 어셈블리가 몇 가지 있습니다. 분명히 제 스크립트는 UnityEngine.dll을 참조하며, 이는 적어도 mscorlib.dll과 다른 어셈블리를 참조합니다. 어디에 있나요? 실제로 il2cpp.exe는 이러한 어셈블리를 내부적으로 해결합니다. 명령줄에 언급할 수 있지만 반드시 필요한 것은 아닙니다. 유니티는 루트 어셈블리(다른 어셈블리에서 참조하지 않는 어셈블리)에 대해서만 명시적으로 언급하면 됩니다.
il2cpp.exe 명령줄의 마지막 인수는 출력 C++ 파일을 생성할 디렉터리입니다. 궁금하다면 해당 디렉토리에서 생성된 파일을 살펴보세요. 이 시리즈의 다음 글의 주제가 될 것입니다. 하지만 그 전에 WebGL 빌드 설정에서 '개발 플레이어' 옵션을 선택하는 것이 좋습니다. 이렇게 하면 --output-format=Compact 명령줄 인수가 제거되고 생성된 C++ 코드에서 더 나은 유형 및 메서드 이름을 사용할 수 있습니다.
WebGL 또는 iOS 플레이어 설정에서 다양한 옵션을 변경해 보세요. 다양한 코드 생성 단계를 활성화하기 위해 il2cpp.exe에 전달되는 다양한 명령줄 옵션을 볼 수 있어야 합니다. 예를 들어 WebGL 플레이어 설정에서 "예외 활성화" 설정을 "전체" 값으로 변경하면 il2cpp.exe 명령줄에 --emit-null-checks, --enable-stacktrace 및 --enable-array-bounds-check 인수가 추가됩니다.
IL2CPP에서 해결하지 못한 과제 중 하나를 지적하고 싶은데, 이를 간과한 것이 아쉽습니다. IL2CPP로 C# 표준 라이브러리를 다시 작성하려고 시도하지 않았습니다. IL2CPP 스크립팅 백엔드를 사용하는 Unity 프로젝트를 빌드할 때 mscorlib.dll, System.dll 등의 모든 C# 표준 라이브러리 코드는 Mono 스크립팅 백엔드에 사용되는 코드와 완전히 동일합니다.
유니티는 이미 사용자들에게 잘 알려져 있고 Unity 프로젝트에서 충분한 테스트를 거친 C# 표준 라이브러리 코드를 사용합니다. 따라서 IL2CPP와 관련된 버그를 조사할 때 버그가 AOT 컴파일러나 런타임 라이브러리에 있다고 확신할 수 있으며, 다른 곳에는 버그가 없다고 확신할 수 있습니다.
지난 1월 IL2CPP가 4.6.1p5 버전으로 처음 공개된 이후 6번의 정식 릴리스와 7번의 패치 릴리스(Unity 4.6 및 5.0 버전에 걸쳐)를 출시했습니다. 릴리즈 노트에 언급된 100개 이상의 버그를 수정했습니다.
이러한 지속적인 개선을 위해 유니티는 내부적으로 알파 및 베타 릴리스를 출시할 때 사용하는 트렁크 브랜치의 최첨단에 있는 IL2CPP 코드의 한 가지 버전만을 기준으로 개발합니다. 각 릴리스 직전에 IL2CPP 변경 사항을 특정 릴리스 브랜치에 포팅하고 테스트를 실행하여 해당 버전에서 수정한 모든 버그가 수정되었는지 확인합니다. 저희의 QA 및 지속적 엔지니어링 팀은 이러한 속도를 구현하기 위해 놀라운 작업을 수행했습니다. 즉, 사용자들은 IL2CPP 버그에 대한 최신 수정 사항을 약 일주일 이내에 받을 수 있습니다.
사용자 커뮤니티는 수준 높은 버그 리포트를 많이 제출하여 그 가치를 입증했습니다. IL2CPP를 지속적으로 개선하는 데 도움이 되는 사용자 여러분의 모든 피드백에 감사드리며, 앞으로도 더 많은 피드백을 부탁드립니다.
IL2CPP를 개발하는 개발팀은 테스트 우선주의가 강합니다. 우리는 종종 테스트 중심 디자인 관행을 따르며, 제대로 된 테스트 없이 풀 리퀘스트를 병합하는 경우는 거의 없습니다. 이 전략은 IL2CPP와 같이 입력과 출력이 명확한 기술에 적합합니다. 즉, 우리가 발견하는 버그의 대부분은 예상치 못한 동작이 아니라 예상치 못한 경우입니다(예: 64비트 IntPtr을 32비트 배열 인덱스로 사용하면 C++ 컴파일러 오류로 clang이 실패할 수 있으며 실제 코드에서는 실제로 이런 일이 발생하기도 합니다!). 이러한 차이를 통해 높은 수준의 자신감을 가지고 신속하게 버그를 수정할 수 있습니다.
저희는 커뮤니티의 도움을 받아 IL2CPP를 최대한 안정적이고 빠르게 만들기 위해 열심히 노력하고 있습니다. 혹시 이 내용 중 흥미를 끄는 부분이 있다면 채용 중이니 참고하세요.
향후 블로그 게시물에 대해 너무 많은 시간을 할애한 것은 아닌지 걱정됩니다. 하고 싶은 말이 너무 많아서 한 게시물에 다 담을 수 없습니다. 다음 시간에는 il2cpp.exe가 생성한 코드를 자세히 살펴보고 프로젝트가 실제로 C++ 컴파일러에서 어떻게 보이는지 살펴보겠습니다.
