IL2CPP 내부 소개

JOSH PETERSON / UNITY TECHNOLOGIESSenior Software Engineer
May 6, 2015|10 분
IL2CPP 내부 소개
이 웹페이지는 이해를 돕기 위해 기계 번역으로 제공됩니다. 기계 번역으로 제공되는 콘텐츠에 대한 정확도나 신뢰도는 보장되지 않습니다. 번역된 콘텐츠의 정확도에 관해 의문이 있는 경우 웹페이지의 공식 영어 원문을 참고해 주시기 바랍니다.

거의 1년 전부터 유니티에서 스크립팅의 미래에 대해 이야기하기 시작했습니다. 새로운 IL2CPP 스크립팅 백엔드는 성능이 뛰어나고 휴대성이 뛰어난 가상 머신을 Unity에 도입할 것을 약속했습니다. 1월에는 IL2CPP를 사용한 첫 번째 플랫폼인 iOS 64비트를 출시했습니다. Unity 5 릴리스에는 또 다른 플랫폼인 WebGL이 추가되었습니다. 엄청난 사용자 커뮤니티의 의견 덕분에 IL2CPP에 대한 많은 패치 릴리스 업데이트를 제공하여 컴파일러와 런타임을 꾸준히 개선해 왔습니다. IL2CPP의 개선을 멈출 계획은 없지만, 한 발짝 물러서서 IL2CPP가 어떻게 작동하는지에 대해 잠시 설명해 드리는 것이 좋겠다고 생각했습니다. 앞으로 몇 달에 걸쳐 다음 주제(및 다른 주제)에 대해 IL2CPP 내부 시리즈 포스팅을 작성할 계획입니다:

1.기본 사항 - 툴체인 및 명령줄 인수(이 게시물)

2. 생성된 코드 둘러보기

3. 생성된 코드에 대한 디버깅 팁

4. 메서드 호출 (일반 메서드, 가상 메서드 등)

5. 일반 공유 구현

6. 유형 및 메서드에 대한 P/인보크 래퍼

7. 가비지 컬렉터 통합

8. 테스트 프레임워크 및 사용법

이 시리즈 포스팅에서는 앞으로 반드시 변경될 IL2CPP 구현에 대한 몇 가지 세부 사항을 논의할 예정입니다. 앞으로도 유용하고 흥미로운 정보를 제공할 수 있기를 바랍니다.

IL2CPP란 무엇인가요?

IL2CPP라고 부르는 기술은 크게 두 가지로 나뉩니다.

  • AOT(미리 보기) 컴파일러
  • 가상 머신을 지원하는 런타임 라이브러리

AOT 컴파일러는 .NET 컴파일러의 저수준 출력인 중간 언어(IL)를 C++ 소스 코드로 변환합니다. 런타임 라이브러리는 가비지 컬렉터, 스레드와 파일에 대한 플랫폼 독립적인 액세스, 내부 호출(관리되는 데이터 구조를 직접 수정하는 네이티브 코드) 구현과 같은 서비스 및 추상화를 제공합니다.

AOT 컴파일러

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 툴체인 더 작게
런타임 라이브러리

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의 통합을 연구하고 있습니다. 이 시리즈의 뒷부분에 있는 가비지 컬렉터 통합에 대한 글에서 이에 대해 자세히 설명하겠습니다.

il2cpp.exe는 어떻게 실행되나요?

한 가지 예를 살펴보겠습니다. 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에서 해결하지 못한 과제 중 하나를 지적하고 싶은데, 이를 간과한 것이 아쉽습니다. IL2CPP로 C# 표준 라이브러리를 다시 작성하려고 시도하지 않았습니다. IL2CPP 스크립팅 백엔드를 사용하는 Unity 프로젝트를 빌드할 때 mscorlib.dll, System.dll 등의 모든 C# 표준 라이브러리 코드는 Mono 스크립팅 백엔드에 사용되는 코드와 완전히 동일합니다.

유니티는 이미 사용자들에게 잘 알려져 있고 Unity 프로젝트에서 충분한 테스트를 거친 C# 표준 라이브러리 코드를 사용합니다. 따라서 IL2CPP와 관련된 버그를 조사할 때 버그가 AOT 컴파일러나 런타임 라이브러리에 있다고 확신할 수 있으며, 다른 곳에는 버그가 없다고 확신할 수 있습니다.

IL2CPP 개발, 테스트 및 출시 방법

지난 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++ 컴파일러에서 어떻게 보이는지 살펴보겠습니다.