LLVM tutorial chapter 9

안상준·2025년 7월 22일

LLVM

목록 보기
3/12

https://llvm.org/docs/tutorial/MyFirstLanguageFrontend/LangImpl09.html

Introduction

  • Debug Information : 컴파일된 바이너리와 기계 상태를 소스코드로 매핑하는 정보(DWARF 형식)을 의미. 이를 통해 브레이크포인터 설정, 변수 추적, 호출 스택 확인 등이 가능. 해당 챕터에서 Kaleidoscope 언어에 DWARF 디버깅 정보를 추가
  • 주의 사항 : 현재 JIT을 통해 디버깅할 수 없으므로 프로그램을 독립적인 프로그램으로 컴파일 필요.

Sample

def fib(x)
  if x < 3 then
    1
  else
    fib(x-1)+fib(x-2);

fib(10)

DWARF

DWARF : Debugging With Attributed Record Formats
컴파일된 바이너리 안에 소스코드와 대응되는 정보를 저장
ex)

  • 이 기계어 명령어는 소스 코드 몇 번째 줄인지
  • 이 메모리 위치는 어떤 변수인지
  • 함수 이름, 인자, 지역 변수 이름

Debug Information이 어려운 이유

  1. 최적화는 소스 위치 유지를 더욱 여렵게 만듦
    ex)
int a = 3 + 4;
int b = a * 2;
-> int b = (3 + 4) * 2;

원래 두 줄이었던 코드가 최적화로 줄어듦
디버거는 명령어가 몇 번째 줄에서 왔는지 결정할 수 없음

  1. 변수 추적 문제
    ex)
int x = 10;
int y = x + 5;
-> int y = 15;

최적화로 인해 변수가 상수로 대체됨. 디버거 입장에선 x가 어디 있는지 알 수 없음
그래서 디버그 정보를 제대로 다루기 위해 최적화를 꺼놓고 수행

사전 컴파일 모드(AOT mode)

AOT(Ahedad-of-Time) : 사전 컴파일 모드

  • 디버그 정보를 잘 다루기 위해
  • JIT은 런타임 중 IR을 기계어로 바꾸고 바로 실행하기 대문에, 디버거가 인식하기 어려움.
  • 디버그 정보를 IR에 포함한 후, 이를 .ll파일로 저장하고, clang으로 실행파일을 만드는 방식을 사용

익명함수

먼저 최상위 명령문을 포함하는 익명 함수를 "main"으로 만든다. -> 디버깅 도구에서 진입점을 찾기 쉽게 하기 위해

REPL 제거

REPL : 인터프리터처럼 한 줄씩 입력받아 실행

위와 같이 while을 통해 하나씩 입력받아 실행하는 로직을 제거

JIT관련 코드 제거


모든 최적화 패스와 JIT을 비활성화하여 코드 구문 분석 및 생성이 완료된 후 LLVM IR이 표준 오류로 전송되는 것만 발생하도록 함.

컴파일 단위

DWARF에서 코드 섹션의 최상위 컨테이너는 컴파일 단위이다. 여기에는 개별 번역 단위(소스 코드 파일 하나)의 형식 및 함수 데이터가 포함.
따라서 가장 먼저 해야할 일은 fib.ks 파일에 대한 컴파일 단위를 생성하는 것.

DWARF 방출 설정

LLVM IR에 DWARF 디버깅 정보를 붙이기 위해 DIBuilderDebugInfo구조를 설정하는 과정

  • DIBuilder : Debug Info Builder. LLVM IR에 DWARF 메타데이터를 붙이는 클래스
  • DICompileUnit : DWARF의 컴파일 단위. 하나의 소스 파일을 표현
  • DITYpe : 변수/인자 등의 타임 정보 표현(ex. deouble)
  • DebugInfo : 디버그 정보 관련 객체를 모은 구조체

전역 변수 설정

  • DBuilder : IR 전체에 걸쳐 사용하는 디버그 정보 빌더
  • KSDbgInfo : 사용할 디버깅 관련 정보를 저장하는 구조체
  • getDoubleTy() : double 타입 정보를 캐싱해서 재사용

double 타입 정보 만들기


"double"이란 이름, 64바이트 크기, 실수 타입을 의미하는 DW_ATE_float을 넣어서 DWARF 타입 정보를 만듦

모듈 초기화

  • DW_LANG_C : Kaleidoscope는 C 를 따르므로, DWARF 상에서도 C로 설정
  • "fib.ks" : 입력 소스 파일 이름
  • "Kaleidoscope Compiler" : 디버깅 시 표시될 컴파일러 이름

디버깅 종료 처리


디버깅 정보 생성이 끝났다는 신호를 주는 함수

함수

LLVM IR에 함수 정의의 메타데이터를 붙여서 디버깅 시 함수 이름, 정의 위치, 시작 줄, 파라미터 정보 등을 디버거에서 볼 수 있도록 설정
파일에 정의된 함수에 대한 디버그 정보(DWARF의 DISubprogram 메타데이터)를 생성

KSDbgInfo.TheCU는 아가 만든 컴파일 단위. 이를 바탕으로 디버그 정보용 파일 객체(DIFile)를 생성

이 함수는 DISubprogram이라는 함수 전체 정보를 담는 노드를 만들어 줌. 디버깅 시 함수 이름, 정의 위치, 인자 등을 이 정보로 확인
마지막에 생성한 디버깅 정보를 LLVM 함수에 붙인다. LLVM 함수 TheFunctionSP라는 디버깅 정보를 갖게 된다. 디버거는 이 정보를 통해 이 IR 함수가 "fib.ks"의 몇 번째 줄에 있는 함수인지 알 수 있음.

소스 위치

LLVM IR 디버깅 정보 중에서 소스 코드 위치 추적을 구현한 부분

SourceLocation 구조체 추가


줄과 열을 추적하기 위한 구조체, 모든 AST 노드에서 이 정보를 갖고 있게 됨

렉서에서 줄/열 추적

  • CurLoc : 현재 파서 위치
  • LexLoc : 현재 렉서 위치

getchar()를 통해 문자열을 읽어와 Line, Col을 계산

AST노드에 SourceLocation 추가


AST 객체 생성시 현재 소스 위치를 받아서 저장

BinLoc에는 해당 연산자의 소스 코드 위치가 들어감
모든 AST 객체에서 코드의 위치를 알 수 있음

emitLocation()


IRBuilder가 명령어를 생성할 때, 소스코드 몇 번째 줄에서 나왔는지 알려주는 역할
DILocation은 명령어의 열과 줄의 정보

Lexical Scope


현재 어떤 블록 안에 있는지 관리하는 스택

함수에 대해 코드 생성 시작시 스택의 맨 위로 푸시

함수에 대한 코드 생성이 끝날 때 스택에서 팝
이를 통해 IRBuilder가 현재 어떤 범위 안에 있는지 추적이 가능

변수

함수에 대한 정보를 생성, 범위 내에 있는 변수들을 출력할 수 있어햐 한다.
디버깅 정보를 통해 변수의 이름타입, 위치 정보를 생성

  • CreateEntryBlockAlloca : 함수의 entry block에 스택 변수 생성
  • createParameterVariable : 디버깅 정보용 인자 변수(DILocalVariable) 생성
  • insertDeclare : alloca와 변수 정보를 연결
  • CreateStore : 함수 인자의 실제 값을 alloca에 저장
  • NameValues : AST codegen에서 이 변수 이름으로 접근 가능하게 등록

CreateParamterVariable

  • SP : 함수의 디버그 스코프
  • Arg.getName() : 변수 이름
  • ArgIdx : 인자 인덱스
  • Unit : 정의된 파일
  • LineNo : 줄 번호
  • KSDbgInfo.GetDoubleTy() : double 타입
  • true : isArgument = true


함수 프롤로그는 디버거가 스킵하도록 명시

본문 진입 전 줄 번호 정보 재지정

실행

clang으로 컴파일 하는데 리턴타입이 int인 main을 찾는데 IR에는 리턴 타입이 double인 main이 존재
wrapper를 만들어 해결

#include <stdio.h>

extern double kaleidoscope_main();

int main(int argc, char** argv) {
    double result = kaleidoscope_main();
    printf("Fibonacci result: %f\n", result);
    return 0;
}

0개의 댓글