SyphonArch / swpp202301-compiler-team1

MIT License
1 stars 0 forks source link

[Sprint 2] [A-IX] Function Inlining #35

Closed heatz123 closed 1 year ago

heatz123 commented 1 year ago

Function Inlining

Issue link: #23

Note: this PR is built on the top of #33.

1. 착안점

2. 최적화 개요

Function Inlining에서는 함수 Call을 Callee의 body로 바꾼다. 이때, 전체 코드 길이와 현재 Register Pressure를 고려한다. 이때 Register Pressure프로그램의 특정 시점에 관리되는 변수의 총 개수로 정의하고, 이를 계산하기 위해 기존에 구현된 Register Pressure Analysis를 활용할 수 있다. (#33 )

3. 구현 방식

핵심적인 함수는 다음과 같다.

bool shouldInline(CallInst *CI)

다음 조건을 확인하여 inline 여부를 결정한다.

이때 Return문이 여러 개인 파일에 대한 inlining은 아직 구현하지 않았다. (코드 길이 제한 때문) 단, Return문이 여러개인 함수가 존재하는 벤치마크 파일을 전수조사해본 결과, 재귀가 존재하는 프로그램인 gcdmergesort 단 두 경우밖에 없었기 때문에, 우선순위는 아니라고 판단된다.

void inlineFunction(CallInst *CI, Function &Callee)

실제 inlining을 수행한다.

구체적인 예시

define i32 @caller(i32 %a, i32 %b) {
  %add = add i32 %a, %b
  %call = call i32 @callee(i32 %add)
  ret i32 %call
}

define i32 @callee(i32 %x) {
  %sub = sub i32 %x, 5
  br label %exit

exit:
  %sub2 = sub i32 %sub, 10
  ret i32 %sub2
}
  1. Callee 복사

    define i32 @caller(i32 %a, i32 %b) {
      %add = add i32 %a, %b
      %call = call i32 @callee(i32 %add)
      ret i32 %call
    
    entry2:
      %sub = sub i32 %x, 5
      br label %exit
    
    exit:
      %sub2 = sub i32 %sub, 10
      ret i32 %sub2
    }
    
    define i32 @callee(i32 %x) {
      %sub = sub i32 %x, 5
      br label %exit
    
    exit:
      %sub2 = sub i32 %sub, 10
      ret i32 %sub2
    }
  2. static alloca를 위로 옮기는 과정(생략 ,현재 예시에는 static alloca가 없다.) alloca가 static하지 않은 경우 백엔드가 작동하지 않기 때문에, 프로그램의 정상 작동을 위해 alloca를 static하게 만들어야 한다.
  3. split BasicBlock

    define i32 @caller(i32 %a, i32 %b) {
      %add = add i32 %a, %b
      br %callee.exit
    
    callee.exit:
      %call = call i32 @callee(i32 %add)
      ret i32 %call
    
    entry2:
      %sub = sub i32 %x, 5
      br label %exit
    
    exit:
      %sub2 = sub i32 %sub, 10
      ret i32 %sub2
    }
  4. branch 변경

    define i32 @caller(i32 %a, i32 %b) {
      %add = add i32 %a, %b
      br %entry2 ; <- entry2로 변경됨
    
    callee.exit:
      %call = call i32 @callee(i32 %add)
      ret i32 %call
    
    entry2:
      %sub = sub i32 %x, 5
      br label %exit
    
    exit:
      %sub2 = sub i32 %sub, 10
      ret i32 %sub2
    }
  5. merge return block

    define i32 @caller(i32 %a, i32 %b) {
      %add = add i32 %a, %b
      br %entry2
    
    callee.exit:
      %sub2 = sub i32 %sub, 10
      ret i32 %sub2
      %call = call i32 @callee(i32 %add)
      ret i32 %call
    
    entry2:
      %sub = sub i32 %x, 5
      br label %exit
    }
  6. set branch to return block

    define i32 @caller(i32 %a, i32 %b) {
      %add = add i32 %a, %b
      br %entry2
    
    callee.exit:
      %sub2 = sub i32 %sub, 10
      ret i32 %sub2
      %call = call i32 @callee(i32 %add)
      ret i32 %call
    
    entry2:
      %sub = sub i32 %x, 5
      br label %callee.exit ; <- callee.exit으로 변경
    }
  7. replace use of ret & deletion

    define i32 @caller(i32 %a, i32 %b) {
      %add = add i32 %a, %b
      br %entry2
    
    callee.exit:
      %sub2 = sub i32 %sub, 10
      ; ret i32 %sub2 -> removed
      ; %call = call i32 @callee(i32 %add) -> removed
      ret i32 %sub2 ; -> replaced
    
    entry2:
      %sub = sub i32 %x, 5
      br label %callee.exit ; <- callee.exit으로 변경
    }

4. 유닛 테스트

5. 성능 비교 (이전/이후)

  1. register pressure를 고려하지 않고 inline한 결과 cost_gain_function_inlining 본래부터 register pressure가 큰 matmul2 프로그램의 경우 성능 하락이 존재하였음.

  2. register pressure를 설정하여 선택적으로 inline 구현 결과 cost_gain_after_considering_register_pressure 성능이 일관되게 향상하는 것을 확인하였음.

  3. Function Inlining Pass를 2회 적용한 결과 (상: 2회 적용 결과, 하: 1회 적용과의 비교) cost_gain_applying2times cost_gain_2_compare 2회 적용한 경우 추가적인 성능 향상이 존재하나, 컴파일 타임이 훨씬 길어져 성능 향상에 비해 리스크가 있는 것으로보임.