D on esp32/esp8266(llvm-xtensa+ldc) and how to get started

From D Wiki
Jump to: navigation, search

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.

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. ^_^