Неперекладна гра слів з приводу CLANG, LLVM і msp430

Метою даної роботи є отримання коду, згенерованого бек-ендом компілятора LLVM MSP430

Введення
CLANG — це просто фронт-енд компілятора для LLVM. LLVM — це фреймворк для розробки фронт-ендів, бек-ендів компіляторів для різних цільових платформ. Цей фреймворк полегшує аналіз шляхом приведення всіх фронт-ендів до проміжного поданням (IR). Всі бек-енди оперують з цим спільним поданням.

Одна з цільових платформ, яка мене цікавить, це MSP430 — популярний вбудований мікроконтролер, який використовується в багатьох микропотребляющих додатках.

Поточна підтримка MSP430 вельми обмежена. Я сподіваюся, що зможу допомогти кому-небудь, наполегливо фейс-палмя, щоб зберегти обличчя.

Поїхали

Поточна підтримка даної платформи гранично обмежена. Сподіваюся, я зможу заповнити цей пробіл. Нижче наведена спрощена діаграма того, як має відбуватися складання:

[Source Code (.cpp)] -> [clang] --.ll --> [llc] --.s --> [msp430-gcc] --> elf


Clang бере ваш вихідний код і компілює його, грунтуючись на наданих параметрах. За замовчуванням він приховує багато чого під своєю оболонкою. Все це не що інше, як спроба зробити процес більш приємним для розробника. Іншими словами, зазвичай ви ніколи не заглянете в проміжне представлення IR, асемблер або лінковку.

Що упущено в документації по цільовій платформі MSP430, так це факт, що clang + llvm насправді не буде створювати для вас бінарники elf. Кодогенератор просто виробляє асемблерний код для вашої платформи. Це ваша особиста обов'язок провести лінковку і генерацію файлу elf.

Технічно, вам не потрібен весь msp430-gcc. Вам всього лише потрібні libc, скрипти лінкера і binutils. З іншого боку, msp430-gcc робить корисну справу, дозволяючи вам не згадувати всі посилання і залежності, необхідні для генерації фінального elf (vector tables, crt0,...etc). Так що скористаємося ним.

Робоче оточення і первинне дослідження
Машина, яка використовується для роботи — це macbook pro під управлінням OSX 10.9.2. Додатково використовується LLVM і msp430-gcc.

До msp430-gcc я використовував патченную версію gcc, викладену на sourceforge.net. В пошуках більш свіжої версії я виявив, що вона більше не підтримується, тому що Texas Instruments і RHEL беруть це на себе.

Добре, подумав я, TI збирається працювати з RHEL, щоб взяти на себе розробку, більше не потрібні будуть закриті компілятори під windows.

FACEPALM 1

TI не рекомендує до використання власне рішення, використовуйте на свій страх і ризик (see TI's compiler download). Оці-їй… Так що я сподіваюся?

FACEPALM 2

Добре, я можу погодитися, що вони не довіряють власним продукту, що і я відчуваю велику частину часу. Намагаючись зібрати це, я швидко виявив, що TI не підтримує збірку на OS X. Це наслідок використання xgcc і того факту, що деякі прапори не підтримуються OS X.

Слід зауважити, що робота йде, потрібно лише прибрати зайві параметри і додати деякі хакі. Я лінивий, і допиливание існуючої системи збирання наводило сум. Що ж, давайте подивимося інші варіанти, раз вже сказали, щоб не довіряти даним рішенням.

Що я зробив
Технічно мені не потрібен фронт-енд, мені лише потрібен лінкер/асемблер. Я припускав, що є brew/fink/ports шлях. Я хотів використовувати оточення gcc4.7 і використовувати brew, але нічого не знайшов.

Так що я створив свій власний форк (заснований на sampsyo/homebrew-mspgcc). Він включає gcc, libc, gdb, mcu, binutils і mspdebug:
brew tap wbennett/homebrew-mspgcc 
brew install wbennett/mspgcc/msp430-binutils 
brew install wbennett/mspgcc/msp430-gcc 
brew install wbennett/mspgcc/msp430-libc 
brew install wbennett/mspgcc/msp430-mcu 
brew install wbennett/mspgcc/msp430-gdb 
brew install mspdebug 


Вах… Тепер у нас є gcc.

Моє обличчя горить
Все стає жертвою цифрового розпаду, але особливе страждання приносить open source софт. Зокрема, численні хінти, представлені в тредах допомоги і ступінь релевантності вашим запитам, по відношенню до періоду напіврозпаду інформації, в них наданої.

Якщо ви шукаєте в інтернеті інформацію, як зібрати в clang для платформи msp430, безпосередньо ви нічого не знайдете. Ви знайдете трохи обнадійливі набори параметрів, непотрібні обсяги документації і можливо, пропозиції використати деякі опції, про яких ви не думали:
$>clang-ccc-host-triple msp430-elf

clang-3.5: error: unsupported option '-ccc-host-triple' 
clang-3.5: error: no such file or directory: 'msp430-elf' 
clang-3.5: error: no input files

Відмінно, це не працює.
Я можу тільки припустити, що це з-за старої версії LLVM. Може бути, я повинен був використовувати llc після того, як clang перетворив його в LLVM байткод?.. Добре, подивимося, які варіанти ще залишилися.

Спробуємо тестове додаток:
#include "msp430.h"
#include "stdlib.h"

int main(void) 
{
//disable watchdog
WDCTL = WDTPW + WDTHOLD;

//do something silly
auto i = 1;
auto result = i+1;

//avoid warnings
if(result == 2)
return 2;

//loop forever
while(true){}

//never reach this
return 0;
}


#application to compile llvm assembler
~/local/bin/clang++ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/ 
-D__MSP430F5438__-g-S-emit-llvm-std=c++11-Wall-c src/simpleadd.cpp -o src/simpleadd.ll


Відмінно! Виглядає, як нормальний LL
; ModuleID = 'src/simpleadd.cpp'
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128" 
target triple = "x86_64-apple-macosx10.9.0"

@"\01__WDTCTL" = external global i32

; Function Attrs: nounwind ssp uwtable
define i32 @main() #0 { 
%1 = alloca i32, align 4
%i = alloca i32, align 4
%result = alloca i32, align 4
store i32 0, i32* %1
store volatile i32 23168, i32* @"\01__WDTCTL", align 4
store i32 1, i32* %i, align 4
%2 = load i32* %i, align 4
%3 = add nsw i32 %2, 1
store i32 %3, i32* %result, align 4
%4 = load i32* %result, align 4
%5 = icmp eq i32 %4, 2
br i1 %5, label %6, label %7

; <label>:6 ; preds = %0
ret i32 2

; <label>:7 ; preds = %0
br label %8

; <label>:8 ; preds = %7, %8
br label %8
}

attributes #0 = { nounwind ssp uwtable "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.ident = !{!0}

!0 = metadata !{metadata !"clang version 3.5 "}

Давайте вже переведемо це в який-небудь асемблер для msp430, що llc може нам запропонувати?
$>~/local/bin/llc --help
...
-mcpu=<cpu-name> - Target a specific cpu type (-mcpu=help for details)
#ok more specific plz
$>~/local/bin/llc-mcpu=help
...
amdfam10 - Select the amdfam10 processor.
athlon - Select the athlon processor.
athlon-4 - Select the athlon-4 processor.
athlon-fx - Select the athlon-fx processor.
athlon-mp - Select the athlon-mp processor.
athlon-tbird - Select the athlon-tbird processor.
athlon-xp - Select the athlon-xp processor.
athlon64 - Select the athlon64 processor.
athlon64-sse3 - Select the athlon64-sse3 processor.
...

Чому ми не можемо використовувати msp430? Я клянуся, що десь читав, що існує частково підтримувана платформа, звана msp430.

Скористаємося допомогою.
$>~/local/bin/llc --help
...
-march=<string> - Architecture to generate code for (see --version)
#ok
$>~/local/bin/llc --version
LLVM (http://llvm.org/): 
LLVM version 3.5svn
DEBUG build.
Built Mar 1 2014 (22:49:35).
Default target: x86_64-apple-darwin13.1.0
Host CPU: core-avx-i

Registered Targets:
aarch64 - AArch64 (ARM 64-bit little endian target)
aarch64_be - AArch64 (ARM 64-bit big endian target)
arm - ARM
cpp - C++ backend
hexagon - Hexagon
mips - Mips
mips64 - Mips64 [experimental]
mips64el - Mips64el [experimental]
mipsel - Mipsel
msp430 - MSP430 [experimental]
nvptx - NVIDIA PTX 32-bit
nvptx64 - NVIDIA PTX 64-bit
ppc32 - PowerPC 32
ppc64 - PowerPC 64
ppc64le - PowerPC 64 LE
r600 - AMD GPUs HD2XXX-HD6XXX
sparc - Sparc
sparcv9 - Sparc V9
systemz - SystemZ
thumb - Thumb
x86 - 32-bit X86: Pentium-Pro and above
x86-64 - 64-bit X86: EM64T and AMD64
xcore - XCore

Дивно, але він підтримується. Давайте згенеруємо msp430 асемблер!
~/local/bin/llc-march=msp430 src/simpleadd.ll-o src/simpleadd.s

Не впевнений, чому він розмістив main в секції text, ну та бог з ним.
.file "src/simpleadd.ll"
.text
.globl main
.align 2
.type main,@function
main: ; @main 
; BB#0:
push.w r4
mov.w r1, r4
sub.w #12, r1
mov.w #0, -2(r4)
mov.w #0, -4(r4)
mov.w #0, &__WDTCTL+2
mov.w #23168, &__WDTCTL
mov.w #0, -6(r4)
mov.w #1, -8(r4)
mov.w #0, -10(r4)
mov.w #2, -12(r4)
mov.w #0, r12
cmp.w #0, r12
jne .LBB0_2
; BB#1:
mov.w #2, r14
mov.w #0, r15
add.w #12, r1
pop.w r4
ret
.LBB0_2: ; =>This Inner Loop Header: Depth=1
jmp .LBB0_2
.Ltmp0:
.size main, .Ltmp0-main


.ident "clang version 3.5 "

Давайте сгененируем бінарники.
$>msp430-gcc-Wall-D_GNU_ASSEMBLER_-I/usr/local/Cellar/msp430-libc/20120716/msp430/include/ 
-mmcu=msp430f5438-mcpu=430-x assembler-Wa,-gstabs-c src/simpleadd.s-o src/simpleadd.o
#let's hint our entry point (-e main), because it's weird to be placing main in the text section
#let's also generate a map to see how everything is linked
$>msp430-gcc-mmcu=msp430f5438-mcpu=430-Wl,-Map=a.out.map src/simpleadd.o-o a.out-e main

Без паніки, схоже, ми на вірному шляху.
На жаль, facepalm 3. Моє обличчя червоне і в синцях. Після перегляду map файлу бачимо, що лінкер робить щось дивне:
*(.init .init.*)
791 *(.init0)
792 .init0 0x0000000000005c00 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_re#
793 0x0000000000005c00 _reset_vector__
794 *(.init1)
795 .init1 0x0000000000005c00 0xc /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__w#
796 0x0000000000005c00 __watchdog_support
797 *(.init2)
798 .init2 0x0000000000005c0c 0x4 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__i#
799 0x0000000000005c0c __init_stack
800 *(.init3) 
801 .init3 0x0000000000005c10 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__l#
802 0x0000000000005c10 __low_level_init
803 *(.init4)
804 .init4 0x0000000000005c10 0x18 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_co#
805 0x0000000000005c10 __do_copy_data
806 .init4 0x0000000000005c28 0x16 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_cl#
807 0x0000000000005c28 __do_clear_bss
808 *(.init5)
809 *(.init6)
810 *(.init7)
811 *(.init8)
812 *(.init9)
813 *(.fini9)
814 .fini9 0x0000000000005c3e 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__s#
815 0x0000000000005c3e __stop_progExec__
816 *(.fini8)
817 *(.fini7)
818 *(.fini6)
819 *(.fini5)
820 *(.fini4)
821 *(.fini3)
822 *(.fini2)
823 *(.fini1)
824 *(.fini0)
825 .fini0 0x0000000000005c3e 0x6 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_en#
826 0x0000000000005c3e _endless_loop__
....
869 .text.crt0 0x0000000000005c48 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/crt0ivtbl64.o
870 .text 0x0000000000005c48 0x40 src/simpleadd.o
871 0x0000000000005c48 main

Лінкер пропустив запис про перехід на main в секції .init9. Гаразд, дам підказку.
Після інспекції асемблера видно, що clang не розуміє, як лінкуются програма, або йому все одно:
...
.file "src/simpleadd.ll"
.text
.globl main
.align 2
.type main,@function
main: ; @main 
...

Добре Mr.Clang, думаю, я можу вам трохи допомогти. Щоб вказати, де я хочу, щоб компілятор розмістити код, я повинен використовувати атрибут, а саме:
__attribute__((section(".init9")),aligned(2)). 

Бібліотека Crt0 використовується для виконання процедур стартапу, перш ніж переходити до основної програми. Крім того, вона обробляє поведінку після повернення вашої програми з функції main. Оскільки мікроконтроллер не може технічно «зупинитися», він увійде в нескінченний цикл після повернення з main.

Кроки, які будуть виконуватися до того, як запуститься основна програма (линкованная з Crt0), наступні:
  1. Вектор скидання
  2. Конфігурація системи вочдога
  3. Ініціалізація стека
  4. Низькорівнева ініціалізація
  5. Копіювання даних
  6. Очищення ОЗП
  7. Виклик конструкторів/деструкторів
  8. Виклик main
  9. Обробка повернення з main


Новий код:

__attribute__((section(".init9"),aligned(2))) 
int main(void) 
{
...

Спробуємо зібрати:

$>~/local/bin/clang++ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/ 
-D__MSP430F5438__-S-emit-llvm-std=c++11-Wall-c src/simpleadd.cpp -o src/simpleadd.ll
...
rc/simpleadd.cpp:4:24: error: argument to 'section' attribute is not valid for this target: mach-o section specifier requires a 
segment and section separated by a comma
__attribute__((section(".init9"),aligned(2))) 

Facepalm 4, Mr. Clang починає виводити мене з себе.
Повертаємося до --help. Я уважно читаю, щоб знайти, де я, можливо, щось упустив.

Врешті-решт, знайшов.
$>~/local/bin/clang++ --help
...
--target=<value> Generate code for the given target
...

Згоден, мій косяк. Прошу вибачення. Ну і як мені визначити, яка мета правильна?
Спробуємо за образом попередніх запитів:
~/local/bin/clang++ --target=help
clang-3.5: error: no input files 
#version maybe?
~/local/bin/clang++ --version
clang version 3.5 
Target: x86_64-apple-darwin13.1.0 
Thread model: posix 

Невдало… А що, якщо спробувати очевидне:
~/local/bin/clang++ -I/usr/local/Cellar/msp430-libc/20120716/msp430/include/ \
-D__MSP430F5438__-S-emit-llvm-std=c++11 --target=msp430 \
-Wall-c src/simpleadd.cpp -o src/simpleadd.ll

Бабах!

Нарешті складальник працює як від нього вимагається, і я можу запустити програму на пристрої.
Результуючий .ll:
; ModuleID = 'src/simpleadd.cpp'
target datalayout = "e-m:e-p:16:16-i32:16:32-n8:16" 
target triple = "msp430"

@"\01__WDTCTL" = external global i16

; Function Attrs: nounwind
define i16 @main() #0 section ".init9" align 2 { 
%1 = alloca i16, align 2
%i = alloca i16, align 2
%result = alloca i16, align 2
store i16 0, i16* %1
store volatile i16 23168, i16* @"\01__WDTCTL", align 2
store i16 1, i16* %i, align 2
%2 = load i16* %i, align 2
%3 = add nsw i16 %2, 1
store i16 %3, i16* %result, align 2
%4 = load i16* %result, align 2
%5 = icmp eq i16 %4, 2
br i1 %5, label %6, label %7

; <label>:6 ; preds = %0
ret i16 2

; <label>:7 ; preds = %0
br label %8

; <label>:8 ; preds = %7, %8
br label %8
}

attributes #0 = { nounwind "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "unsafe-fp-math"="false" "use-soft-float"="false" }

!llvm.ident = !{!0}

!0 = metadata !{metadata !"clang version 3.5 "}

Результуючий .s:
.file "src/simpleadd.ll"
.section .init9,"ax",@progbits
.globl main
.align 1
.type main,@function
main: ; @main 
; BB#0:
push.w r4
mov.w r1, r4
sub.w #6, r1
mov.w #0, -2(r4)
mov.w #23168, &__WDTCTL
mov.w #1, -4(r4)
mov.w #2, -6(r4)
mov.w #2, r12
cmp.w #2, r12
jne .LBB0_2
; BB#1:
mov.w #2, r15
add.w #6, r1
pop.w r4
ret
.LBB0_2: ; =>This Inner Loop Header: Depth=1
jmp .LBB0_2
.Ltmp0:
.size main, .Ltmp0-main


.ident "clang version 3.5 "

Зверніть увагу, що main тепер правильно розміщений у виводі .map:
792 .init0 0x0000000000005c00 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_re#
793 0x0000000000005c00 _reset_vector__
794 *(.init1)
795 .init1 0x0000000000005c00 0xc /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__w#
796 0x0000000000005c00 __watchdog_support
797 *(.init2)
798 .init2 0x0000000000005c0c 0x4 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__i#
799 0x0000000000005c0c __init_stack
800 *(.init3)
801 .init3 0x0000000000005c10 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__l#
802 0x0000000000005c10 __low_level_init
803 *(.init4)
804 .init4 0x0000000000005c10 0x18 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_co#
805 0x0000000000005c10 __do_copy_data
806 .init4 0x0000000000005c28 0x16 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(_cl#
807 0x0000000000005c28 __do_clear_bss
808 *(.init5) 
809 *(.init6)
810 *(.init7)
811 *(.init8)
812 *(.init9)
813 .init9 0x0000000000005c3e 0x2c src/simpleadd.o
814 0x0000000000005c3e main
815 *(.fini9)
816 .fini9 0x0000000000005c6a 0x0 /usr/local/Cellar/msp430-gcc/4.7.0/lib/gcc/msp430/4.7.0/mmpy-16/libcrt0.a(__s#
817 0x0000000000005c6a __stop_progExec__

Тепер я можу продовжити свою роботу. Сподіваюся, це допоможе зменшити кількість фейспалмов, що виявилися за межами даної статті.

Про автора:
Вільям Беннетт
Я старший інженер-програміст в страхової компанії. Я проводжу ночі, працюючи над компіляторами і менеджерами рантайма для вбудованих систем. Мені подобається возитися і збирати різні речі.

Від себе:
Враховуючи, що англійською я в основному користуюся для читання мануалів і даташітов, є ймовірність не цілком правильного перекладу деяких моментів, так що якщо є зауваження, прошу поправити. Особисто я давно збирався спробувати саме зв'язку LLVM-mspgcc, але щось завжди заважало, а тут ця стаття. Якщо все піде добре, планую повторити на Ubuntu-msp430-gcc-mspdebug-TI LaunchPad.
Також, в тексті є деякі моменти, пов'язані з особливостями OS X, які я теж можу не знати (вірніше, не можу знати). Так що знову ж таки, уточнення і конструктивна критика вітаються.

На цьому поки все, спасибі за вашу увагу.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.