Working Through WACC – chapter 18 structs

WACC is this excellent book

Oh my goodness! I expected this to be a tricky chapter but not this hard.

Initializers

I knew when I wrote the initializer code for arrays that it wouldn’t work for structs, so a complete rewrite for this chapter. Why, well because an array, even a multi dimensional one is a list of values of all the same type. Structs are arbitrarily complex. This requires a recursive descent of the struct definition and the specified values

Much happier with it now

Lvalue vs rvalue

For some reason I got stuck on this

Struct x{
    Int arr[10];
};
.....
Struct x x1;
....
.. x.arr ....

Is x.arr an rvalue to an lvalue? This is the same for a plain array that is not embedded in a structure. It’s all to do with when and if the array to pointer conversion is triggered, plus the use of the & operator does not trigger the conversion. Took me a long time to get the right.

Calling convention

This was the biggest challenge. I read the windows x64 convention docs x64 Calling Convention | Microsoft Learn for passing in and returning structs. Having read the book describing the Unix version I thought “I am glad windows is so much simpler”. Ha!

Btw, the scalar arg passing rules are similar, just different registers used. Same stack push once all the registers used up. The one odd thing is that the caller must reserve 32 bytes on the stack that the callee can use to store the register values if they so wish.

A quick summary of the windows version of struct passing (note that structs and unions are treated identically).

  • Structs of size 1,2,4,8 bytes are passed in a register. Note that this means that 5 byte structs, for example, are *not* passed in registers even though they would fit.
  • Larger structs are copied by the caller into a temporary in the caller’s stack. The address of that temporary is passed to the callee in a register (Reminder that structs are passed by value, the callee gets its own private copy to do whatever it likes)
  • Return structs of size 1,2,4,8 are returned in rax
  • For other return structs the caller allocates space for the value and passes the address as the first arg. All other args are shunted down 1 register.

On the face of it this is way simpler. None of this complex analysis of structs. But that means that this code

struct s{
   int x;
};
int  func(struct s s1){
   Return s1.x;
}

Is really

struct s{
   int x;
};
int  func(struct s *s1){
   Return s1->x;
}

Syntactically the argument and all accesses to it are as though it’s a struct; semantically though, it is a pointer. This is basically references, stolen from c++. I worked out three ways of dealing with this in the called function (the calling function has no choice )

  • #1 In the pre TACKY phase emit TACKY code as though s1 was a pointer even though it is syntactically treated as an actual value.
  • #2 in the post TACKY phase reverse engineer the tacky code and replace the moves by load and stores etc.
  • #3 In the prologue of the called function copy the pointed at copy of s and make a local copy in the callee’s stack.

The problem with #1 is that it pollutes the pre TACKY phase with knowledge of the os and processor. One the main aims of TACKY is to be platform agnostic; you should be able to transform TACKY into 6502 code running on a commodore 64, a pdp11 or an x64 Linux box. Now there would have to be switches to say what os I am targeting.

#2 is just hard to do. Having to unwind some code from being targeted at values and change it to be targeted at dereferenced pointers.

#3 is simple but costly. Now there are 2 copies made of the passed struct. It’s costs extra space and time.

I started out doing #1. I got stuck trying to make it work. I could not work out why I was getting mysterious crashes. So I wound back all the code and decided to do #3, just to get things working. I reminded myself that no sane c programmer reads or returns large structures. So the overhead was fine.

Turned out that I still had the mysterious crashes, finally worked it out, you have to keep your stack frame 16 byte aligned! I could revert to #1 now I understand what was going wrong, but have not done so.


Ran 1381 tests in 145.163s

OK

And were done with part 2!

The remainder of the book is about optimizations. I am going to do a part2 summary post next


Comments

Leave a comment