Working Through WACC – Chapter 13 Floats

Lexer

I cheated. My lexer does not use regexes, it does a character-by-character analysis. The parsing of floating point literals looked way too complicated so I used fast_float – Rust. This has a nice feature, you can ask it to parse as far as it can converting to floating point, if it finds a valid number it returns it plus the number of characters it consumed.

It will also happily detect and report an integer constant as a float too. To work round this I convert the same string of characters to an integer. Testing two things

  • if the conversion fails that it wasnt an integer (1.2 for example)
  • if the conversion succeeds and the value is the same as the float then it really is an integer

Floats – how hard can it be?

Welcome to the wonderful world of the Intel instruction set. A wild west where anything and everything goes.

Floating point operations:-

  • have their own set of registers
  • multiple different instruction sets depending on how old the processor is
  • instructions that work differently from how the integer ones do, look at divide for example

WACC uses SSE2 which seems to be a pretty common tactic (looking at the code generated by different compilers).

Boy oh boy are there some odd corner cases. Here for example is the code from the book to convert an unsigned long to a double.

   cmpq $0, -8(%rbp)
   jl .L_out_of_range
   cvtsi2sdq -8(%rbp), %xmm0 
   jmp .L_end
.L_out_of_range:
   movq -8(%rbp), %rax
   movq %rax, %rdx
   shrq %rdx
   andq $1, %rax
   orq %rax, %rdx
   cvtsi2sdq %rdx, %xmm0
   addsd %xmm0, %xmm0
.L_end:

!

Once again the test suite is a fantastic resource. I have maybe come to rely on it too much. I code up enough to get the compiler to work and then work through the test failures.

A Dense Book

I have said this before , but it bers repeating, this is a very dense book. I dont mean stupid, I mean there is a lot of stuff crammed into it.

You have to read it all, and take it all in. This is probably the densest book I have ever worked through. It is my bedside reading; I reread each chapter several times before starting coding.

TACKY criticisms

One major issue is that the front end ‘knows’ that x64 doesnt support floating point constants. However other processors might and TACKY is meant to be CPU neutral.

Also now that there are several TACKY operations that change a value’s type I want to point out some inconsistencies.

Here is what there is

SignExtendint -> long
Truncatelong -> int
ZeroExtendunsigned int -> unsigned long
DoubleToIntdouble -> int
DoubleToUIntdouble -> unsigned int
IntToDoubleint -> double
UIntToDoubleunsigned int -> double

Things I dont like

  • the first three say how they are going to do something, without indicating the intent
  • The next four state the intent with no indication of how they will do it
  • Why isnt there a LongToDouble, a DoubleToULong…?

The point of TACKY is to capture the core intent of the program in a platform (X86, X64, 6502,…) and language (you could make a TACKY based FORTRAN compiler) neutral way. The ones that say how to do the conversions (SignExtend,…) are getting dangerously close to being platform specific, not quite, but almost.

I think much clearer would be a single operation: Convert. The backend knows the type of every operand and so it would be clear and unambiguous.

To be clear, I love TACKY, it makes everything much cleaner and also tells you what the front end needs to do. It encapsulates Nora’s knowledge about the C language and how to compile it. I would tweak it a bit though. In fact I already did ๐Ÿ™‚

Criticism of My Code

It generates a ton of debug output. I need to add flags to control the verbosity, plus maybe log to a file rather than the console

It, like Nora’s NQCC, gives up on the first error in the c code. I at least give the failing line number. I started looking into unwinding to a safe place to be able to continue compiling.

I don’t like some parts of the structure of the program. I made a start at having the back end pluggable (via rust traits) in anticipation of compiling for a different processor. All I have done is made things a bit too complicated

I am not sure that the two pass backend is really needed. The book suggests it, but I ignored that advice on the front end. I might revisit the backend too.

I am at ~5000 lines of rust code. Nora’s chapter 13 code is ~3500. I was worried that I was way too verbose, seems not. I am sure some of it is due to the nature of OCaml vs rust. OCaml is denser, there is a lot more packed into each line.

Stack Alignment

In the book and in Microsoft’s calling convention description, x64 Calling Convention | Microsoft Learn, it talks about keeping the stack 16 byte aligned. I thought “I dont have time for that, my code seems to work”.

Not so here in SSE land, some operands have to be 16 byte aligned (because the XMM register are 16 bytes wide). The Microsoft C library works on the assumption that the stack registers are aligned and so it can store in the stack without worrying about alignment.

The hard part was recognizing the error I got. The instruction fails like this

The instruction at 0x7FFDE96CD744 referenced memory at 0xFFFFFFFFFFFFFFFF. The memory could not be read (exc.code c0000005, tid 49584)

This is the IDA Free debugger reporting it. It does not say ‘misaligned data’ or anything helpful. Took me a long time to work out what this meant

Test Changes

The test suite has code that calls the standard math library, so it adds -lm to the linker to the command line of the compiler, plus, when it links in code from the system compiler it adds -lm. This is not needed on Windows as the math library is automatically linked in (as it is on OSX), so I suppressed the -lm.


Ran 674 tests in 63.601s

OK

Woohoo. Off to pointers


Comments

Leave a comment