Working Through WACC – Chapter 11 , Long Integers

Now working through Part II of the excellent ‘Writing a c Compiler’

chapter 11 is called ‘Long Integers’. I was discussing this with my son-in-law who works in tech, he laughed saying that that was surely not a big change, I though so too. Boy were we wrong.

Its interesting to note that at the end of part I we are in line with the very early ancestor of c – b. Discussed in this paper The development of the C programming language | History of programming languages—II At that point the language only supported one type that matched the machines (PDP7) natural size of operation. There were no types in the languages, everything was implicitly an int, when they moved to the more complex PDP11 they needed types of varying sizes. WACC now hits the same thing. In part 1 everything is an int.

Big Code Change

Now we have to support different types, this changes a ton of code

  • symbol table has type
  • I extended TACKY to add types (I did not want to have to propagate the symbol table to the backend)
  • implicit conversions and promotions
  • explicit casts

The last one is buried away in this chapter, the only mention is here

We’ll make one tiny mechanical change to identifier resolution: we’ll extend resolve_exp to traverse cast expressions the same way it traverses other kinds of expressions

plus one tweak to the EBNF

<factor> ::= <const> | <identifier>
       | "(" { <type-specifier> }+ ")" <factor>

thats it, there is no modified resolve_exp function or any hint as to how to parse a cast. I actually assumed there were no explicit casts in this chapter until I hit the tests and failed.

I parsed out explicit casts it the factor part of handling an expression

 Token::LeftParen => {
    // is this a cast?
  let specifiers = self.load_specifiers()?;
  if specifiers.is_external || specifiers.is_static {
       bail!("Cannot cast to external or static type");
   }
   if specifiers.specified_type.is_some() {
       self.expect(Token::RightParen)?;
       let target_type = specifiers.specified_type.unwrap();
       let right = self.do_factor()?;
       let dest_name = self.make_temporary();
       let ret_dest = Value::Variable(dest_name.clone(), target_type.clone());
       let converted = self.convert_to(&right, &target_type);
       self.instruction(Instruction::Copy(converted, ret_dest.clone()));
       Ok(ret_dest)
    } else {
       let ret = self.do_expression(0)?;
       self.expect(Token::RightParen)?;
       Ok(ret)
    }
}

You can see here the parser emitting TACKY code as it goes along (self.instruction() add a TACKY instruction)

The convert_to function ensures that the types match. Note also that when I create a Value::Variable it is tagged with its type

LLP64 vs LP64

The book is written using gcc or clang on Linux and Mac. I am doing it on Windows. We now hit a huge difference: gcc and clang use LP64, msvc uses LLP64, see 64-bit computing – Wikipedia

The big difference is that LLP64 still has long as 32 bit, long long is 64 bit. LP64 has long and long long as 64 bit. Some explanation here as to why Microsoft chose this model: Why did the Win64 team choose the LLP64 model? – The Old New Thing

I have to follow the MSVC model because I want to interoperate with windows libraries. The problem is that the tests all say ‘long’ with the expectation that that means 64 bit, many tests combine code compiled by the wacc compiler and the native compiler (msvc). So I made a big change to the tests, I replaced every ‘long’ with ‘LONG64’ and added a ‘/DLONG64=long long’ for msvc and ‘-DLONG64=long’ for gcc and clang


Comments

Leave a comment