D on esp32/esp8266(llvm-xtensa+ldc) and how to get started
The Rust community has proved that Rust is running on ESP32. I just looked at that tutorial and then had an idea: is D's betterC running on ESP32? hmm, it should be! For those who don't know what ESP32 is:
ESP32 is a low-cost low power consumption WiFi/BLE soc chip with up to 240Mhz 32bit Xtensa CPU and 520KB RAM and SPI nor flash support, and it's a star in the IoT device market and has powerfull open embedded software ecosystem like MicroPython and Lua and even javascript.
I took the Rust ESP32 tutorial's idea with building llvm+clang for xtensa and referenced the component integration with ESP-IDF(esp32 offical C SDK), and finally got a success.
Contents
Get the llvm and clang source for xtensa
$ git clone https://github.com/espressif/llvm-xtensa.git
$ git clone https://github.com/espressif/clang-xtensa.git llvm-xtensa/tools/clang
Building llvm-clang for xtensa
mkdir llvm_build
cd llvm_build
# from https://gist.github.com/MabezDev/26e175790f84f2f2b0f9bca4e63275d1
cmake ../llvm-xtensa -DLLVM_TARGETS_TO_BUILD="Xtensa" -DCMAKE_BUILD_TYPE=Release -G "Ninja" -D CMAKE_INSTALL_PREFIX=/opt/llvm-xtensa
# Take a while
cmake --build . OR ninja
ninja install
this should build the esp32 clang toolchain with Assemblly output(llvm-xtensa now can only generate AS output). and building a test c code will has these kind of error:
$ /opt/llvm-xtensa/bin/clang -target xtensa -c test.c
/tmp/test-2191f8.s: Assembler messages:
/tmp/test-2191f8.s:4: Error: unknown pseudo-op: `.literal'
/tmp/test-2191f8.s:5: Error: unknown pseudo-op: `.literal'
/tmp/test-2191f8.s:6: Error: unknown pseudo-op: `.literal'
/tmp/test-2191f8.s:12: Error: no such instruction: `entry sp,40'
/tmp/test-2191f8.s:13: Error: no such instruction: `mov.n a7,sp'
/tmp/test-2191f8.s:14: Error: no such instruction: `l32r a8,.LCPI0_0'
/tmp/test-2191f8.s:15: Error: no such instruction: `s32i.n a8,a7,4'
/tmp/test-2191f8.s:16: Error: no such instruction: `l32i.n a11,a7,4'
/tmp/test-2191f8.s:17: Error: no such instruction: `l32r a10,.LCPI0_1'
/tmp/test-2191f8.s:18: Error: no such instruction: `l32r a8,.LCPI0_2'
/tmp/test-2191f8.s:19: Error: no such instruction: `callx8 a8'
/tmp/test-2191f8.s:20: Error: no such instruction: `s32i.n a10,a7,0'
/tmp/test-2191f8.s:21: Error: no such instruction: `movsp sp,a7'
/tmp/test-2191f8.s:22: Error: no such instruction: `retw.n'
clang-6.0: error: assembler command failed with exit code 1 (use -v to see invocation)
but building .S output is working:
$ /opt/llvm-xtensa/bin/clang -target xtensa -S test.c -o test.S
Then we can use the as tool from ESP32 C toolchain to turn assemblly code to machine code:
$ xtensa-esp32-elf-as test.S -o test-c.o
$ file test-c.o
test-c.o: ELF 32-bit LSB relocatable, Tensilica Xtensa, version 1 (SYSV), not stripped
Building ldc2 for esp32 from source using above xtensa llvm tool
get ldc source, I just used ldc-1.19.0-beta1-src.tar.gz and untared to somewhere, then make the building:
mkdir build-ldc
cmake -G Ninja ../ldc_source_dir -DCMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX=/opt/ldc-xtensa -DLLVM_ROOT_DIR=/opt/llvm-xtensa
ninja ldc2
And you may experience this:
Linking CXX executable bin/ldc2
FAILED: bin/ldc2
: && /usr/bin/c++ -DDMDV2 -DHAVE_SC_ARG_MAX -O3 -DNDEBUG -rdynamic obj/ldc2.o -o bin/ldc2 lib/libldc.a -lLLVMWindowsManifest -lLLVMMCDisassembler -lLLVMLTO -lLLVMPasses -lLLVMObjCARCOpts -lLLVMLibDriver -lLLVMOption -lLLVMipo -lLLVMInstrumentation -lLLVMVectorize -lLLVMLinker -lLLVMIRReader -lLLVMDebugInfoPDB -lLLVMDebugInfoDWARF -lLLVMAsmParser -lLLVMXtensaCodeGen -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMDebugInfoCodeView -lLLVMDebugInfoMSF -lLLVMCodeGen -lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -lLLVMBitWriter -lLLVMXtensaAsmParser -lLLVMXtensaDesc -lLLVMXtensaInfo -lLLVMTarget -lLLVMAnalysis -lLLVMProfileData -lLLVMObject -lLLVMMCParser -lLLVMBitReader -lLLVMCore -lLLVMBinaryFormat -lLLVMXtensaAsmPrinter -lLLVMMC -lLLVMSupport -lLLVMDemangle -L/opt/llvm-xtensa/lib -lz -lrt -ldl -ltinfo -lpthread -lm -lxml2 -Wl,--export-dynamic -fuse-ld=gold -L/usr/lib -lphobos2-ldc-shared -ldruntime-ldc-shared -Wl,-rpath,/usr/lib -Wl,--gc-sections -lrt -ldl -lpthread -lm -m64 -ldl -ltinfo -lpthread -lm -lxml2 && :
lib/libldc.a(main.cpp.o):main.cpp:function cppmain(): error: undefined reference to 'llvm::initializeGlobalISel(llvm::PassRegistry&)'
collect2: error: ld returned 1 exit status
ninja: build stopped: subcommand failed.
I don't know why this error happens, but it's easy to fix it for the building:
Edit build.ninja in your build-ldc dir, search build bin/ldc2: and edit LINK_LIBRARIES add -lLLVMGlobalISel before -lLLVMCodeGen and type ninja ldc2 again.
Then we got ldc2 for xtensa in build-ldc/bin
Test
edit some D betterC code:
void d_func ()
{
int[4] arr;
auto c = arr.length;
}
and build the d code using command like this:
build-ldc2/bin/ldc2 -mtriple=xtensa-esp32-elf -mcpu=esp32 -gcc=xtensa-esp32-elf-gcc -betterC -dip1000 -boundscheck=off -linkonce-templates test.d -c
$ file test.o
test.o: ELF 32-bit LSB relocatable, Tensilica Xtensa, version 1 (SYSV), not stripped
$ readelf -s test.o
Symbol table '.symtab' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FILE LOCAL DEFAULT ABS test.d
2: 00000000 0 SECTION LOCAL DEFAULT 4
3: 00000000 24 FUNC GLOBAL DEFAULT 4 _D4test6d_funcFZv
You can put the long compiler command into a shell script:
$ cat xtensa-ldc
#!/bin/sh
export PATH=${HOME}/esp/xtensa-esp32-elf-5/bin:${PATH}
build-ldc/bin/ldc2 -mtriple=xtensa-esp32-elf -mcpu=esp32 -gcc=xtensa-esp32-elf-gcc -betterC -dip1000 -boundscheck=off -linkonce-templates $@
ps: you can also edit ldc.conf to make all the options by default.
integrating with IDF as a component
create a component dir called `dcode`, and write some makefile:
$ cat component.mk
$(COMPONENT_LIBRARY):dcode.a
SRC := $(wildcard $(COMPONENT_PATH)/*.d)
COMOBJS := $(patsubst %.d, %.o, $(SRC))
%.o : %.d
ldc2-xtensa -c $< -of=$@
COMPONENT_ADD_LDFLAGS += $(COMPONENT_BUILD_DIR)/dcodelib.a
dcodelib :
xtensa-esp32-elf-ar rc $(COMPONENT_BUILD_DIR)/dcodelib.a $(COMOBJS)
dcode.a: $(SRC) $(COMOBJS) dcodelib
And you will have betterC d code compiles and linking:
$ cat dcode.d
module dcode;
extern(C) int printf (scope const char * fmt, ...);
char[8] a = ['a'];
extern (C) void dlang_main()
{
uint i = 0;
printf("hello, dlang says : i = %d, char a len = %d\r\n", i, a.length);
}
Then, call dlang_main as a plain C function in any other place of your esp32 application, you should see that D is saying hello to you. ^_^