shecc
está construido desde cero, dirigido a arquitecturas Arm y RISC-V de 32 bits, como un compilador autocompilador para un subconjunto del lenguaje C. A pesar de su naturaleza simplista, es capaz de realizar estrategias de optimización básicas como un compilador de optimización independiente.
shecc
es capaz de compilar archivos fuente C escritos con la siguiente sintaxis:
+=
, -=
, *=
int i = [expr]
#define
, #ifdef
, #elif
, #endif
, #undef
y #error
__VA_ARGS__
El backend apunta a armv7hf con Linux ABI, verificado en Raspberry Pi 3, y también es compatible con la arquitectura RISC-V de 32 bits, verificada con QEMU.
Los pasos para validar el arranque shecc
:
stage0
: el código fuente shecc
se compila inicialmente utilizando un compilador ordinario que genera un ejecutable nativo. El compilador generado se puede utilizar como compilador cruzado.stage1
: el binario compilado lee su propio código fuente como entrada y genera un binario ARMv7-A/RV32IM.stage2
: El binario ARMv7-A/RV32IM generado se invoca (a través de QEMU o ejecutándose en dispositivos Arm y RISC-V) con su propio código fuente como entrada y genera otro binario ARMv7-A/RV32IM.bootstrap
: compila los compiladores stage1
y stage2
y verifica que sean idénticos en bytes. Si es así, shecc
puede compilar su propio código fuente y producir nuevas versiones de ese mismo programa. El generador de código en shecc
no depende de utilidades externas. Sólo necesita compiladores de C ordinarios como gcc
y clang
. Sin embargo, shecc
se iniciaría solo y se requiere la emulación Arm/RISC-V ISA. Instale QEMU para la emulación de usuario Arm/RISC-V en GNU/Linux:
$ sudo apt-get install qemu-user
Todavía es posible compilar shecc
en macOS o Microsoft Windows. Sin embargo, el arranque de la segunda etapa fallaría debido a la ausencia qemu-arm
.
Para ejecutar la prueba de instantánea, instale los siguientes paquetes:
$ sudo apt-get install graphviz jq
Configure qué backend desea, shecc
admite backend ARMv7-A y RV32IM:
$ make config ARCH=arm
# Target machine code switch to Arm
$ make config ARCH=riscv
# Target machine code switch to RISC-V
Ejecute make
y debería ver esto:
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
File out/shecc
es el compilador de la primera etapa. Su uso:
$ shecc [-o output] [+m] [--no-libc] [--dump-ir] < infile.c >
Opciones del compilador:
-o
: especifica el nombre del archivo de salida (predeterminado: out.elf
)+m
: utiliza instrucciones de multiplicación/división de hardware (predeterminado: deshabilitado)--no-libc
: excluye la biblioteca C integrada (predeterminado: integrada)--dump-ir
: volcado de representación intermedia (IR)Ejemplo:
$ out/shecc -o fib tests/fib.c
$ chmod +x fib
$ qemu-arm fib
Verifique que los IR emitidos sean idénticos a las instantáneas especificando check-snapshots
target al invocar make
:
$ make check-snapshots
shecc
viene con pruebas unitarias. Para ejecutar las pruebas, dé check
como argumento:
$ make check
Salida de referencia:
...
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
Para limpiar los archivos del compilador generados, ejecute el comando make clean
. Para restablecer las configuraciones de arquitectura, use el comando make distclean
.
Una vez que se pasa la opción --dump-ir
a shecc
, se generará la representación intermedia (IR). Tomemos como ejemplo el archivo tests/fib.c
. Consiste en una función de secuencia de Fibonacci recursiva.
int fib ( int n )
{
if ( n == 0 )
return 0 ;
else if ( n == 1 )
return 1 ;
return fib ( n - 1 ) + fib ( n - 2 );
}
Ejecute lo siguiente para generar IR:
$ out/shecc --dump-ir -o fib tests/fib.c
Explicación línea por línea entre la fuente C y el 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"
} }
. Alternativamente, verifique la implementación printf
en el código fuente lib/cc
para var_arg
. shecc
se puede redistribuir libremente según la licencia de la cláusula BSD 2. El uso de este código fuente se rige por una licencia estilo BSD que se puede encontrar en el archivo LICENSE
.