링커의 썽크(Thunk) 기술, 대규모 코드의 분기 문제를 해결하다!

by DD
4개월 전
조회수 6

대부분의 아키텍처에서 분기 명령(Branch Instruction)은 PC 상대 주소 지정을 사용하며, 대상이 멀리 떨어져 있으면 '범위 초과(Out of Range)' 오류 발생

컴파일러는 조건 분기(Conditional Branch) 범위를 벗어나는 경우, 조건 반전 및 무조건 분기 삽입을 통해 해결

링커는 썽크(Thunk)를 생성하여 분기 범위를 확장, 대규모 코드에서 함수 호출 문제 해결

RISC-V 및 LoongArch는 썽크 대신 링커 리랙세이션(Linker Relaxation)을 통해 코드 크기를 최적화

분기 범위 제한과 아키텍처별 특징

다양한 아키텍처는 분기 명령의 최대 도달 범위에 차이를 보인다. 예를 들어, AArch64는 ±128MiB, RISC-V는 ±1MiB의 분기 범위를 가진다. 이러한 제한은 대규모 코드에서 함수 호출 시 '범위 초과(Out of Range)' 오류를 발생시키는 원인이 된다. 컴파일러(Compiler)는 조건 분기를 무조건 분기로 변환하여, 어셈블러(Assembler)는 명령어 축약을 통해, 링커(Linker)는 썽크(Thunk)를 생성하여 이러한 문제를 해결한다.

링커의 썽크(Thunk) 생성 알고리즘

링커는 분기 대상이 범위를 벗어날 경우, 썽크(Thunk)라는 작은 코드 조각을 생성하여 원래의 분기를 썽크로 리디렉션한다. 썽크는 더 긴 명령어 시퀀스를 사용하여 실제 대상에 도달한다. lld/ELF, lld/MachO, mold, GNU ld 등 다양한 링커는 각기 다른 썽크 생성 알고리즘을 사용하며, 다중 패스(Multi-pass) 또는 단일 패스(Single-pass) 방식을 통해 썽크를 배치한다. 썽크 생성(Thunk Generation)은 디버깅 및 프로파일링에 영향을 미칠 수 있다.

RISC-V의 링커 리랙세이션(Linker Relaxation)

RISC-V 및 LoongArch는 썽크 대신 링커 리랙세이션(Linker Relaxation) 기술을 사용하여 코드 크기를 최적화한다. 링커 리랙세이션은 auipc + jalr과 같은 명령어 시퀀스를 jal과 같은 더 짧은 명령어로 축약한다. 이러한 방식은 썽크를 사용하지 않아 코드 크기를 줄일 수 있지만, 링커의 복잡성을 증가시킨다. 링커 리랙세이션(Linker Relaxation)은 썽크 삽입보다 더 복잡하며, 코드 크기 측면에서 이점을 제공한다.

컴파일러, 어셈블러, 링커의 협력

컴파일러는 조건 분기 범위를 벗어나는 경우, 조건 반전 및 무조건 분기 삽입을 통해 문제를 해결한다. 어셈블러는 명령어 축약(Instruction Relaxation)을 수행하여 분기 명령의 크기를 최적화한다. 링커는 썽크(Thunk) 생성 또는 링커 리랙세이션(Linker Relaxation)을 통해 분기 범위를 확장한다. 이러한 툴체인(Toolchain)의 협력을 통해 대규모 코드에서도 정확한 실행 파일을 생성할 수 있다.

Long branches in compilers, assemblers, and linkers