оптимизация уровня проекта clang / LLVM - PullRequest
0 голосов
/ 07 апреля 2020

Так что периодически я пробую LLVM, так как у меня есть эта теория, она должна превосходить GNU. И тогда это, к сожалению, нет.

Часть теории связана с его способностью связывать модули / объекты вместе и затем оптимизировать, где обычно происходит оптимизация для каждого файла / объекта.

Вместо использования обобщенного c один, я вижу, как построить для заданного c целевого объекта по умолчанию

rm -rf llvm-project
git clone https://github.com/llvm/llvm-project.git
cd llvm-project
git checkout llvmorg-10.0.0
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS='clang;lld' -DCMAKE_CROSSCOMPILING=True -DCMAKE_INSTALL_PREFIX=/opt/llvm/llvm10armv6m -DLLVM_DEFAULT_TARGET_TRIPLE=armv6m-none-eabi -DLLVM_TARGET_ARCH=ARM -DLLVM_TARGETS_TO_BUILD=ARM -G "Unix Makefiles" ../llvm
make -j 8
make -j 4
make
sudo make install

и тестовых файлов

test. c

unsigned int one ( void )
{
    return(1);
}
unsigned int two ( void );
unsigned int testone ( void )
{
    return(one());
}
unsigned int testtwo ( void )
{
    return(two());
}

два. c

unsigned int two ( void )
{
    return(2);
}

basi c run

clang -O2 -fomit-frame-pointer -c test.c -o test.o
llvm-objdump -D test.o

00000000 one:
       0: 01 20                         movs    r0, #1
       2: 70 47                         bx  lr

00000004 testone:
       4: 01 20                         movs    r0, #1
       6: 70 47                         bx  lr

00000008 testtwo:
       8: 80 b5                         push    {r7, lr}
       a: ff f7 fe ff                   bl  #-4
       e: 80 bd                         pop {r7, pc}

, как и следовало ожидать, one () был встроен в testone ().

Желание также сделать testwo () встроенным.

clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llc both.bc -o both.s
cat both.s
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s

дает

testone:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  one
    pop {r7, pc}


testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  two
    pop {r7, pc}

и

testone:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  one
    pop {r7, pc}

testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  two
    pop {r7, pc}

, что хуже.

opt -std-link-opts both.bc -o both.opt.bc

то же самое, не лучше

Теперь это работает

clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O2 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s

testone:
    .fnstart
@ %bb.0:                                @ %entry
    movs    r0, #1
    bx  lr

testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    movs    r0, #2
    bx  lr

Можно было бы подумать, что неоптимизация частей даст больше мяса для оптимизации всего пережевывания. Да? Хотя это указывает на иное.

clang -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
opt -O3 both.bc -o both.opt.bc
llc both.opt.bc -o both.opt.s
cat both.opt.s

testone:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  one
    movs    r0, #1
    pop {r7, pc}

testtwo:
    .fnstart
@ %bb.0:                                @ %entry
    .save   {r7, lr}
    push    {r7, lr}
    bl  two
    movs    r0, #2
    pop {r7, pc}

-O3 тоже не помогает, и этот вывод настолько же плох, что вызывает функцию И вставляет ее. Что там происходит?!

llvm-dis both.opt.bc
cat both.opt.ll

; ModuleID = 'both.opt.bc'
source_filename = "llvm-link"
target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
target triple = "thumbv6m-none-unknown-eabi"

; Function Attrs: noinline nounwind optnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  %call = call i32 @one()
  ret i32 1
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
  %call = call i32 @two()
  ret i32 2
}

; Function Attrs: noinline nounwind optnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
  ret i32 2
}

Как отменить это?

clang -O2 -fomit-frame-pointer -c -emit-llvm test.c -o test.bc
clang -O2 -fomit-frame-pointer -c -emit-llvm two.c -o two.bc
llvm-link test.bc two.bc -o both.bc
llvm-dis both.bc
cat both.ll
opt -O3 both.bc -o both.opt.bc
llvm-dis both.opt.bc
cat both.opt.ll

дает

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
  %call = tail call i32 @two() #2
  ret i32 %call
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
  ret i32 2
}

и

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testtwo() local_unnamed_addr #0 {
entry:
  ret i32 2
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @two() local_unnamed_addr #0 {
entry:
  ret i32 2
}

Так это правильно, что вы должны применять оптимизации везде, на уровне файлов / объектов, чтобы оптимизировать уровень проекта?

И тогда возникает вопрос о хвостовом вызове или листе, и т. Д. * Оптимизация 1089 *, если не что иное, testtwo: даже в первом случае

clang -O2 -fomit-frame-pointer -c test.c -o test.o

может просто переходить к two () и не устанавливать кадр стека, не делая ничего из этого. Или это большой палец? b не могу добраться?

one:
       0:   b8 01 00 00 00  movl    $1, %eax
       5:   c3  retq

testone:
      10:   b8 01 00 00 00  movl    $1, %eax
      15:   c3  retq

testtwo:
      20:   e9 00 00 00 00  jmp 0 <testtwo+5>

В GNU компоновщик исправляет любые проблемы с ветками или проблемы с батутами

arm-none-eabi-gcc -c -O2 -mcpu=cortex-m0 test.c -o test.o
arm-none-eabi-objdump -D test.o

00000000 <one>:
   0:   2001        movs    r0, #1
   2:   4770        bx  lr

00000004 <testone>:
   4:   2001        movs    r0, #1
   6:   4770        bx  lr

00000008 <testtwo>:
   8:   b510        push    {r4, lr}
   a:   f7ff fffe   bl  0 <two>
   e:   bd10        pop {r4, pc}

Хорошо, я исправлен ...

clang --version
clang version 10.0.0 (https://github.com/llvm/llvm-project.git d32170dbd5b0d54436537b6b75beaf44324e0c28)
Target: armv6m-none-unknown-eabi
Thread model: posix
InstalledDir: /opt/llvm/llvm10armv6m/bin

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Полагаю, вопрос в том, хочет ли кто-нибудь выполнить оптимизацию на уровне проекта, используя llvm-link и opt, является ли оптимизация каждого отдельного элемента обязательной или есть опция командной строки, которую я пропускаю. Не интересует спецификация компилятора c атрибутов, которые go в самом исходном коде, не хочет, чтобы код был заражен ни спецификой g cc, ни llvm.

После g cc 5.xx код получил более раздутые надеялись, что у llvm будет шанс, но всякий раз, когда я пытаюсь это сделать (в проектах не только 10 строк кода), g cc заканчивается меньшим количеством выполненных инструкций и / или меньшим количеством обращений к памяти, et c, et c. Для простых демонстрационных функций, подобных приведенным выше, за некоторыми исключениями они выдают одинаковый / эквивалентный результат.

Есть ли что-то, еще один инструмент или опции командной строки, которые мне не хватает, чтобы получить больше от clang / llvm?

Это слишком тривиально пример для инструмента?

РЕДАКТИРОВАТЬ на основе ответа

clang -c start.s -o start.o
clang -O2 -flto=thin -fomit-frame-pointer -c test.c
clang -O2 -flto=thin -fomit-frame-pointer -c two.c
ld.lld start.o test.o two.o -o test.elf
llvm-objdump -D test.elf

000110fc testtwo:
   110fc: 02 20                         movs    r0, #2
   110fe: 70 47                         bx  lr

00011100 two:
   11100: 02 20                         movs    r0, #2
   11102: 70 47                         bx  lr

, поэтому избавление от -emit-llvm и использование lto в основном дает желаемый результат.

Просмотр разборки b c

clang -O2 -flto=thin -fomit-frame-pointer -c test.c
llvm-dis test.o
cat test.o.ll

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @one() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: norecurse nounwind readnone
define dso_local i32 @testone() local_unnamed_addr #0 {
entry:
  ret i32 1
}

; Function Attrs: nounwind
define dso_local i32 @testtwo() local_unnamed_addr #1 {
entry:
  %call = tail call i32 @two() #3
  ret i32 %call
}

включает / добавляет хвостовой вызов. Мне очень не нравится использовать компилятор / оболочку в качестве компоновщика (для встроенных проектов, которые имеют свой собственный bootstrap и скрипт компоновщика), использование llvm-ldd было нелегко выяснить или вообще невозможно выяснить, но ld.lld также поддерживает tlo вещи, так что получилось.

1 Ответ

1 голос
/ 07 апреля 2020

Ответ довольно прост: на самом деле никогда не следует использовать ll c / opt / llvm-link для выполнения оптимизаций на уровне проекта для конечного пользователя. Это инструменты на стороне разработчика с различными значениями по умолчанию, пороговыми значениями и т. Д. c. По сути, это всего лишь простые интерфейсы командной строки для различных частей набора инструментов LLVM.

Чтобы выполнить правильную оптимизацию времени соединения, вам необходимо использовать конвейеры, предназначенные для такой задачи. По сути, компиляция всего с использованием "clang -flto" и последующее связывание всего снова с помощью "clang -flto" будет работать. Использование связующего с LTO, такого как lld, также является обязательным условием.

Дополнительную информацию о ThinLTO можно также найти здесь: https://clang.llvm.org/docs/ThinLTO.html и http://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html

...