shecc
создан с нуля и ориентирован как на 32-битную архитектуру Arm, так и на RISC-V, как самокомпилирующийся компилятор для подмножества языка C. Несмотря на свою упрощенную природу, он способен выполнять базовые стратегии оптимизации как автономный оптимизирующий компилятор.
shecc
способен компилировать исходные файлы C, написанные в следующем синтаксисе:
+=
, -=
, *=
int i = [expr]
#define
, #ifdef
, #elif
, #endif
, #undef
и #error
__VA_ARGS__
Бэкэнд предназначен для Armv7hf с Linux ABI, проверенным на Raspberry Pi 3, а также поддерживает 32-битную архитектуру RISC-V, проверенную с помощью QEMU.
Шаги для проверки загрузки shecc
:
stage0
: исходный код shecc
изначально компилируется с использованием обычного компилятора, который генерирует собственный исполняемый файл. Сгенерированный компилятор можно использовать в качестве кросс-компилятора.stage1
: построенный двоичный файл считывает собственный исходный код в качестве входных данных и генерирует двоичный файл ARMv7-A/RV32IM.stage2
: сгенерированный двоичный файл ARMv7-A/RV32IM вызывается (через QEMU или на устройствах Arm и RISC-V) с собственным исходным кодом в качестве входных данных и генерирует другой двоичный файл ARMv7-A/RV32IM.bootstrap
: создайте компиляторы stage1
и stage2
и убедитесь, что они побайтно идентичны. В этом случае shecc
может скомпилировать собственный исходный код и создать новые версии той же программы. Генератор кода в shecc
не зависит от внешних утилит. Вам понадобятся только обычные компиляторы C, такие как gcc
и clang
. Однако shecc
загрузится самостоятельно, и потребуется эмуляция Arm/RISC-V ISA. Установите QEMU для эмуляции пользователя Arm/RISC-V в GNU/Linux:
$ sudo apt-get install qemu-user
По-прежнему можно собрать shecc
на macOS или Microsoft Windows. Однако загрузка второго этапа завершится неудачно из-за отсутствия qemu-arm
.
Чтобы выполнить тест моментального снимка, установите следующие пакеты:
$ sudo apt-get install graphviz jq
Настройте желаемый бэкэнд, shecc
поддерживает бэкенд ARMv7-A и RV32IM:
$ make config ARCH=arm
# Target machine code switch to Arm
$ make config ARCH=riscv
# Target machine code switch to RISC-V
Запустите make
и вы должны увидеть это:
CC+LD out/inliner
GEN out/libc.inc
CC out/src/main.o
LD out/shecc
SHECC out/shecc-stage1.elf
SHECC out/shecc-stage2.elf
Файл out/shecc
— это компилятор первого этапа. Его использование:
$ shecc [-o output] [+m] [--no-libc] [--dump-ir] < infile.c >
Параметры компилятора:
-o
: указать имя выходного файла (по умолчанию: out.elf
).+m
: использовать аппаратные инструкции умножения/деления (по умолчанию: отключено)--no-libc
: исключить встроенную библиотеку C (по умолчанию: встроенная)--dump-ir
: Дамп промежуточного представления (IR)Пример:
$ out/shecc -o fib tests/fib.c
$ chmod +x fib
$ qemu-arm fib
Убедитесь, что созданные IR идентичны снимкам, указав цель check-snapshots
при вызове make
:
$ make check-snapshots
shecc
поставляется с модульными тестами. Чтобы запустить тесты, укажите check
в качестве аргумента:
$ make check
Эталонный вывод:
...
int main(int argc, int argv) { exit(sizeof(char)); } => 1
int main(int argc, int argv) { int a; a = 0; switch (3) { case 0: return 2; case 3: a = 10; break; case 1: return 0; } exit(a); } => 10
int main(int argc, int argv) { int a; a = 0; switch (3) { case 0: return 2; default: a = 10; break; } exit(a); } => 10
OK
Чтобы очистить сгенерированные файлы компилятора, выполните команду make clean
. Для сброса конфигурации архитектуры используйте команду make distclean
.
Как только опция --dump-ir
будет передана в shecc
, будет сгенерировано промежуточное представление (IR). Возьмем, к примеру, tests/fib.c
. Он состоит из рекурсивной функции последовательности Фибоначчи.
int fib ( int n )
{
if ( n == 0 )
return 0 ;
else if ( n == 1 )
return 1 ;
return fib ( n - 1 ) + fib ( n - 2 );
}
Выполните следующее для создания IR:
$ out/shecc --dump-ir -o fib tests/fib.c
Построчное объяснение между источником C и IR:
C Source IR Explanation
-- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
int fib ( int n ) def int @ fib ( int % n ) Indicate a function definition
{ {
if ( n == 0 ) const %. t1001 , $0 Load constant 0 into a temporary variable ".t1001"
%. t1002 = eq % n , %. t1001 Test "n" equals ".t1001" or not , and write the result in temporary variable ".t1002"
br %. t1002 , . label . 1177 , . label . 1178 If ".t1002" equals zero , goto false label ".label.1178" , otherwise ,
goto true label ".label.1177"
. label . 1177
return 0 ; const %. t1003 , $0 Load constant 0 into a temporary variable ".t1003"
ret %. t1003 Return ".t1003"
j . label . 1184 Jump to endif label ".label.1184"
. label . 1178
else if ( n == 1 ) const %. t1004 , $1 Load constant 1 into a temporary variable ".t1004"
%. t1005 = eq % n , %. t1004 Test "n" equals ".t1004" or not , and write the result in temporary variable ".t1005"
br %. t1005 , . label . 1183 , . label . 1184 If ".t1005" equals zero , goto false label ".label.1184" . Otherwise ,
goto true label ".label.1183"
. label . 1183
return 1 ; const %. t1006 , $1 Load constant 1 into a temporary variable ".t1006"
ret %. t1006 Return ".t1006"
. label . 1184
return
fib ( n - 1 ) const %. t1007 , $1 Load constant 1 into a temporary variable ".t1007"
%. t1008 = sub % n , %. t1007 Subtract ".t1007" from "n" , and store the result in temporary variable ".t1008"
push %. t1008 Prepare parameter for function call
call @ fib , 1 Call function " fib " with one parameter
+ retval %. t1009 Store return value in temporary variable ". t1009 "
fib ( n - 2 ); const %. t1010 , $2 Load constant 2 into a temporary variable ".t1010"
%. t1011 = sub % n , %. t1010 Subtract ".t1010" from "n" , and store the result in temporary variable ".t1011"
push %. t1011 Prepare parameter for function call
call @ fib , 1 Call function " fib " with one parameter
retval %. t1012 Store return value in temporary variable ". t1012 "
%. t1013 = add %. t1009 , %. t1012 Add ". t1009 " and ". t1012 ", and store the result in temporary variable " . t1013 "
ret %. t1013 Return ".t1013"
} }
нельзя. Альтернативно, проверьте реализацию printf
в исходном коде lib/cc
на наличие var_arg
. shecc
распространяется свободно в соответствии с лицензией BSD 2. Использование этого исходного кода регулируется лицензией в стиле BSD, которую можно найти в файле LICENSE
.