www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - D at 8-bit platform - some experiences

reply Dukc <ajieskola gmail.com> writes:
D is not designed to operate at platforms where the native 
integer/pointer size is under 32 bits. Nonetheless, I've lately 
been learning AVR programming with D, and thought I'd share my 
experiences.

I'm not the first one to do this. [Ernesto 
Castellotti](https://forum.dlang.org/post/kctkzmrdhocsfummll
q forum.dlang.org), [Adam
Ruppe](https://dpldocs.info/this-week-in-d/Blog.Posted_2022_10_10
html#hello-arduino) and others have experimented before me, but this still
seems a fringe area for D.

Overall D works passably. There are shortcomings, but since you 
do everything yourself anyway on platforms like this, and the 
alternative would be C, D so far seems a viable option. I think 
that with relatively little modification, AVR and similar 
platforms could be made a first-class citizen for D (albeit 
without a full standard library support since they're still 
bare-metal platforms - 
[LWDR](https://forum.dlang.org/post/unfecovccvpocxtkprxs forum.dlang.org) on
the other hand could probably work).

`size_t` is defined as `uint`, which is maybe technically wrong, 
but in practice makes dealing with array lengths much easier than 
it'd be otherwise. The problem is that LDC doesn't currently 
[fully work](https://github.com/ldc-developers/ldc/issues/2520) 
with this scheme. As a result, array indexing does not work 
unless you disable bounds checking. My current solution is to 
define a custom indexing function instead:

```D
pragma(inline, true)
 trusted pure ref ix(El, Idx)(El[] arr, Idx idx)
{   // I haven't yet hooked the D assert failure handler so
     // using a custom replacement instead.
     // Casting to ushort necessary to work around the mentioned 
issue.
     if(idx >= cast(ushort) arr.length) assert0();
     return arr.ptr[idx];
}
```

Another big limitation on AVR isn't due to the bit width, but 
it's Harvard architechture. LLVM considers pointers to program 
memory a different type from data pointers, but LDC declares 
function pointers as data pointers, resulting in LLVM type error 
trying to compile their usage code. One would have to define some 
sort of custom function pointer type, implementing it's 
invocation in assembly or LLVM IR.

The good news for D?

First off, when something doesn't work, you can usually hack 
together something custom, and put it behind a reasonably usable 
API. Even portable D code offers a far better arsenal of tricks 
than most languages, as that custom indexing function shows 
(returns by ref, works with any type, gets inlined, can be done 
in the first place despite requiring potentially type system 
breaking system code). When that isn't enough, LLVM intrinsics, 
inline IR and assembly let one to implement almost anything. For 
example, need a special target-specific return statement for an 
interrupt handler?

```D
// Timer/Counter1 compare match A interrupt
extern(C)  trusted void __vector_11()
{   /*  ...
         Normal D code here - no need to implement
         the whole handler in assembly!
         ...
     */

     // Return while enabling interrupts
     __asm("reti", "");
     // Prevent emitting a reduntant regular return instruction.
     // (Yes this works! I checked the object assembly!)
     // OTOH risking UB to save one word of program memory isn't a 
good tradeoff
     // even on atmega328p so in my own code I'm using assert0() 
instead.
     assume0();
}

// I'm not going to use the definition in ldc.llvmasm
// because it's defined as  trusted, which doesn't fit at all.
pragma(LDC_inline_ir) R llvmIR(string s, R, P...)(P) pure nothrow 
 nogc;

// Calling this function is always undefined behaviour.
alias assume0 = llvmIR!("unreachable", noreturn);
```

I could improve this even further by hiding `reti` inside an 
inlined `noreturn` D function, or failing that, a mixin.

Second, even on a bare-metal microcontroller with no 
preimplemented memory allocator, all business logic can be ` safe 
pure`. As with application code, only low-level type 
manipulation, I/O and memory / global variable management need to 
be impure and/or ` system`/` trusted`. Since there's no garbage 
collector, some algorithms need to be written differently. A 
simple approach is to give the needed working memory to a 
function as an array argument. Otherwise, it's not that different 
from your regular desktop application.

Third, you have the tools to make the object code as compact as 
you want. For some reason, the linker doesn't recognise unused 
functions by default, even with `-L--gc-sections` and 
`--fvisibility=hidden`. I spent a long time fighting the linker, 
once finally finding out that `--function-sections` flag for the 
compiler is needed. From there, it was smooth sailing. My build 
(and disassembly) script:

```bash
ldc2 -betterC -O1 --function-sections -mtriple=avr  
-mcpu=atmega328p --gcc=avr-gcc --Xcc=-mmcu=atmega328p 
-L--gc-sections delay.d
avr-objdump -x -D delay >delay.s
avr-objcopy -O ihex delay delay.hex
```

Note that I don't use `-Oz`. For some reason that tries to link 
to GCC-defined function that isn't in my GCC library (maybe I 
have a GCC version mismatch). But I find -O1 emits almost as 
compact code anyway, while being clearer to read in disassembly. 
`-Os` actually emits a bigger binary than `O1`. With this one can 
define all sorts of inlined or CTFE functions for convenience, 
with no effect on the final binary size.

Just thought to share my experiences, in case someone is 
interested in D on 8-bit. Note that I've been using LDC all 
along. I'm pretty sure GDC can also be used for AVR, but I don't 
know how the experience would compare. For LDC, I think the 
experience is better than expected considering it's an 
environment D isn't designed for. Solving a few worst codegen 
bugs might make it about as good there as on any bare-metal 
platform. As always though, LDC is a volunteer project so I'm not 
saying that anyone should tackle them.

Maybe I'll publish my avr code at some point, but not promising.
Jul 06 2023
next sibling parent reply Iain Buclaw <ibuclaw gdcproject.org> writes:
Nice write up!

On Thursday, 6 July 2023 at 17:18:26 UTC, Dukc wrote:
 Just thought to share my experiences, in case someone is 
 interested in D on 8-bit. Note that I've been using LDC all 
 along. I'm pretty sure GDC can also be used for AVR, but I 
 don't know how the experience would compare. For LDC, I think 
 the experience is better than expected considering it's an 
 environment D isn't designed for. Solving a few worst codegen 
 bugs might make it about as good there as on any bare-metal 
 platform. As always though, LDC is a volunteer project so I'm 
 not saying that anyone should tackle them.
There's a gdc-9.x compiler configured to generate assembly for avr-elf on my self-hosted compiler-explorer site. so did a v.quick translation of llvmir into gcc built-ins for sake of comparison. https://explore.dgnu.org/z/MnT35x There's going to have been quite a few changes compared to gdc-13.1 - I hope for the better, but I have some intuition that tells me we're more at the mercy of the front-end implementation doing silly lowerings now compared to 2.076.x.
Jul 06 2023
parent Max Samukha <maxsamukha gmail.com> writes:
On Thursday, 6 July 2023 at 20:33:13 UTC, Iain Buclaw wrote:

 There's going to have been quite a few changes compared to 
 gdc-13.1 - I hope for the better, but I have some intuition 
 that tells me we're more at the mercy of the front-end 
 implementation doing silly lowerings now compared to 2.076.x.
GDC 13.1 doesn't work for me because `~=` is 'lowered' to something involving `cast(int)r.length - 1`, which is not cast back to `ushort` and causes an implicit conversion error. GDC 11.4 works fine.
Jul 27 2023
prev sibling next sibling parent IchorDev <zxinsworld gmail.com> writes:
On Thursday, 6 July 2023 at 17:18:26 UTC, Dukc wrote:
 D is not designed to operate at platforms where the native 
 integer/pointer size is under 32 bits. Nonetheless, I've lately 
 been learning AVR programming with D, and thought I'd share my 
 experiences.
This is very cool! I'm glad you've managed to hack it work so nicely. :) More 8-bit and 16-bit architecture support for BetterC would be really cool, even though it's unlikely to actually happen.
Jul 07 2023
prev sibling next sibling parent reply kinke <noone nowhere.com> writes:
Thanks for the interesting write-up! :)

I haven't thought about what to do with size/ptrdiff_t and 
integer promotion rules for 8/16 bit targets yet. I guess the 
least surprising behavior would be mimicking gcc/clang there.

 Another big limitation on AVR isn't due to the bit width, but 
 it's Harvard architechture. LLVM considers pointers to program 
 memory a different type from data pointers, but LDC declares 
 function pointers as data pointers, resulting in LLVM type 
 error trying to compile their usage code.
Please file an LDC issue, fixing this shouldn't be rocket science.
 Third, you have the tools to make the object code as compact as 
 you want. For some reason, the linker doesn't recognise unused 
 functions by default, even with `-L--gc-sections` and 
 `--fvisibility=hidden`. I spent a long time fighting the 
 linker, once finally finding out that `--function-sections` 
 flag for the compiler is needed.
Both `--function-sections` and `-L--gc-sections` should be the default settings for bare-metal (and other) targets since LDC v1.32 - LDC was probably a bit too conservative in this regard before.
Jul 07 2023
parent Dukc <ajieskola gmail.com> writes:
On Friday, 7 July 2023 at 15:00:41 UTC, kinke wrote:
 Thanks for the interesting write-up! :)

 I haven't thought about what to do with size/ptrdiff_t and 
 integer promotion rules for 8/16 bit targets yet. I guess the 
 least surprising behavior would be mimicking gcc/clang there.
I'm finding the current one (size/ptrdiff_t always at least 4 bytes, but sizeof(T*) still 2) a practical choice. It follows that no changes to int promotion rules are needed (for 8/16 bits specially - there's still the question whether we want to change them in general though).
 Another big limitation on AVR isn't due to the bit width, but 
 it's Harvard architechture. LLVM considers pointers to program 
 memory a different type from data pointers, but LDC declares 
 function pointers as data pointers, resulting in LLVM type 
 error trying to compile their usage code.
Please file an LDC issue, fixing this shouldn't be rocket science.
[okay](https://github.com/ldc-developers/ldc/issues/4432).
 Both `--function-sections` and `-L--gc-sections` should be the 
 default settings for bare-metal (and other) targets since LDC 
 v1.32 - LDC was probably a bit too conservative in this regard 
 before.
Great! I'm currently using a somewhat outdated compiler, namely 1.30.0, but sooner or later I will update.
Jul 08 2023
prev sibling parent reply kinke <noone nowhere.com> writes:
On Thursday, 6 July 2023 at 17:18:26 UTC, Dukc wrote:
 Note that I don't use `-Oz`. For some reason that tries to link 
 to GCC-defined function that isn't in my GCC library (maybe I 
 have a GCC version mismatch).
You might likely need the LLVM `builtins` compiler-rt library (built for avr obviously, and the version matching the LLVM version LDC was linked against): https://github.com/llvm/llvm-project/tree/main/compiler-rt/lib/builtins. [There's an `avr` subdirectory.]
Jul 07 2023
parent Dukc <ajieskola gmail.com> writes:
On Friday, 7 July 2023 at 19:02:58 UTC, kinke wrote:
 On Thursday, 6 July 2023 at 17:18:26 UTC, Dukc wrote:
 Note that I don't use `-Oz`. For some reason that tries to 
 link to GCC-defined function that isn't in my GCC library 
 (maybe I have a GCC version mismatch).
You might likely need the LLVM `builtins` compiler-rt library (built for avr obviously, and the version matching the LLVM version LDC was linked against): https://github.com/llvm/llvm-project/tree/main/compiler-rt/lib/builtins. [There's an `avr` subdirectory.]
Very likely. At least the names of the functions there read familiar. Didn't know there's such a library at LLVM - thanks! I thought they all are at libGCC and LLVM would only emit calls to them. Now when I think of it it wouldn't have made sense though since avr-gcc is supposedly only used for linking, not as a runtime provider.
Jul 08 2023