D on lm32-CPU: string argument on stack instead of register

Michael Reese michaelate at gmail.com
Sat Aug 1 08:58:03 UTC 2020

On Friday, 31 July 2020 at 15:13:29 UTC, Chad Joan wrote:
> THAT SAID, I think there are things to try and I hope we can 
> get you what you want.
> If you're willing to entertain more experimentation, here are 
> my thoughts:

Thanks a lot for the suggestions and explanations. I tried them 
all but the only time I got different assembly code was your 
suggestion (3) which produced the following.

> (3) Try a different type of while-loop in the D-style version:
> // ideomatic D version
> void write_to_host(in string msg) {
> 	// a fixed address to get bytes to the host via usb
> 	char *usb_slave = cast(char*)BaseAdr.ft232_slave;
> 	size_t i = 0;
> 	while(i < msg.length) {
> 		*usb_slave = msg[i++];
> 	}
> }
	addi     sp, sp, -8
	addi     r4, r0, 4096
	sw       (sp+8), r2
	sw       (sp+4), r1
	addi     r2, r0, 0
	or       r5, r2, r0
	be     r2,r1,.L1
	lw       r3, (sp+8)
	addi     r2, r2, 1
	add      r3, r3, r5
	lbu      r3, (r3+0)
	sb       (r4+0), r3
	bi       .L3
	addi     sp, sp, 8
	b        ra

> At any rate, I don't think your code is larger or less 
> efficient due to utf-8 decoding, because I don't see the utf-8 
> decoding.

Agreed, I think there's no code for autodecoding being generated.

I did some more experiments:
Trying to put pointer and length into a struct and pass that to 
the function. Same result, the argument ended up on the stack.

Then, I wrote the function in C and compiled it with the 
C-compiler of lm32-elf-gcc. It also puts the 64-bit POD structure 
on the stack. It seems the only arguments passed in registers are 
primitive data types. However, if I pass a uint64_t argument, it 
is registered using registers r1 and r2. So the compiler knows 
how to use r1 and r2 for arguments. I checked again in the lm32 
and it says:

"As illustrated in Table 3 on page 8, the first eight function 
arguments are
passed in registers. Any remaining arguments are passed on the 
stack, as
illustrated in Figure 12."

So strings and structs should be passed on the stack and this 
seems to be more an issue of the gcc lm32 backend than a D issue.

But I just found a workaround using a wrapper function.

void write_to_host(in string msg) {
         write_to_hostC(msg.ptr, msg.length);

I checked the assembly code on the caller side, and the call 
write_to host("Hello, World!\n") is inlined. There is only one 
call to write_to_hostC. This is still not nice, but I think I can 
live with that for now. Now I have to figure out how make the 
cast back from from pointer-length pair into a string. I'm sure I 
read that somewhere before, but forgot it and was unable to find 
it now on a quick google search...
And since this is D: is there maybe some CTFE magic that allows 
to create these wrappers automatically? Somthing like

fix_stack!write_to_host("Hello, World!\n");

> Good luck with your lm32/FPGA coding. That sounds like cool 
> stuff!

I'm doing this mainly to improve my understanding of how embedded 
processors work, and how to write linker scripts for a given 
environment. Although I do have actual hardware where I can see 
if everything runs in the real world, I mainly use simulations. 
The coolest thing in my opinion is, nowadays it can be done using 
only open source tools (mainly ghdl and verilator, the lm32 
source code is open source, too). The complete system is 
simulated and you can look at every logic signal in the cpu or in 
the ram while the program executes.

More information about the Digitalmars-d-learn mailing list