‘for’ Loop Expressions

Now that we know how to add basic control flow constructs to the language, we have the tools to add more powerful things. Let’s add something more aggressive, a ‘for’ expression:

  1. extern putchard(char);
  2. def printstar(n)
  3. for i = 1, i < n, 1.0 in
  4. putchard(42); # ascii 42 = '*'
  5. # print 100 '*' characters
  6. printstar(100);

This expression defines a new variable (i in this case) which iterates from a starting value, while the condition (i < n in this case) is true, incrementing by an optional step value (1.0 in this case). While the loop is true, it executes its body expression. Because we don’t have anything better to return, we’ll just define the loop as always returning 0.0. In the future when we have mutable variables, it will get more useful.

To get started, we again extend our lexer with new reserved names “for” and “in”.

  1. lexer :: Tok.TokenParser ()
  2. lexer = Tok.makeTokenParser style
  3. where
  4. ops = ["+","*","-","/",";",",","<"]
  5. names = ["def","extern","if","then","else","in","for"]
  6. style = emptyDef {
  7. Tok.commentLine = "#"
  8. , Tok.reservedOpNames = ops
  9. , Tok.reservedNames = names
  10. }

As before, let’s talk about the changes that we need to Kaleidoscope to support this. The AST node is just as simple. It basically boils down to capturing the variable name and the constituent expressions in the node.

  1. data Expr
  2. ...
  3. | For Name Expr Expr Expr Expr
  4. deriving (Eq, Ord, Show)

The parser code captures a named value for the iterator variable and the four expressions objects for the parameters of the loop parameters.

  1. for :: Parser Expr
  2. for = do
  3. reserved "for"
  4. var <- identifier
  5. reservedOp "="
  6. start <- expr
  7. reservedOp ","
  8. cond <- expr
  9. reservedOp ","
  10. step <- expr
  11. reserved "in"
  12. body <- expr
  13. return $ For var start cond step body

Now we get to the good part: the LLVM IR we want to generate for this thing. With the simple example above, we get this LLVM IR (note that this dump is generated with optimizations disabled for clarity):

  1. declare double @putchard(double)
  2. define double @printstar(double %n) {
  3. entry:
  4. br label %loop
  5. loop:
  6. %i = phi double [ 1.000000e+00, %entry ], [ %nextvar, %loop ]
  7. %calltmp = call double @putchard(double 4.200000e+01)
  8. %nextvar = fadd double %i, 1.000000e+00
  9. %cmptmp = fcmp ult double %i, %n
  10. %booltmp = uitofp i1 %cmptmp to double
  11. %loopcond = fcmp one double %booltmp, 0.000000e+00
  12. br i1 %loopcond, label %loop, label %afterloop
  13. afterloop:
  14. ret double 0.000000e+00
  15. }

‘for’ Loop Expressions - 图1

The code to generate this is only slightly more complicated than the above “if” statement.

  1. cgen (S.For ivar start cond step body) = do
  2. forloop <- addBlock "for.loop"
  3. forexit <- addBlock "for.exit"
  4. -- %entry
  5. ------------------
  6. i <- alloca double
  7. istart <- cgen start -- Generate loop variable initial value
  8. stepval <- cgen step -- Generate loop variable step
  9. store i istart -- Store the loop variable initial value
  10. assign ivar i -- Assign loop variable to the variable name
  11. br forloop -- Branch to the loop body block
  12. -- for.loop
  13. ------------------
  14. setBlock forloop
  15. cgen body -- Generate the loop body
  16. ival <- load i -- Load the current loop iteration
  17. inext <- fadd ival stepval -- Increment loop variable
  18. store i inext
  19. cond <- cgen cond -- Generate the loop condition
  20. test <- fcmp FP.ONE false cond -- Test if the loop condition is True ( 1.0 )
  21. cbr test forloop forexit -- Generate the loop condition

The first step is to set up the LLVM basic block for the start of the loop body. In the case above, the whole loop body is one block, but remember that the generating code for the body of the loop could consist of multiple blocks (e.g. if it contains an if/then/else or a for/in expression).

  1. forloop <- addBlock "for.loop"
  2. forexit <- addBlock "for.exit"

Next we allocate the iteration variable and generate the code for the constant initial value and step.

  1. i <- alloca double
  2. istart <- cgen start -- Generate loop variable initial value
  3. stepval <- cgen step -- Generate loop variable step

Now the code starts to get more interesting. Our ‘for’ loop introduces a new variable to the symbol table. This means that our symbol table can now contain either function arguments or loop variables. Once the loop variable is set into the symbol table, the code recursively codegen’s the body. This allows the body to use the loop variable: any references to it will naturally find it in the symbol table.

  1. store i istart -- Store the loop variable initial value
  2. assign ivar i -- Assign loop variable to the variable name
  3. br forloop -- Branch to the loop body block

Now that the “preheader” for the loop is set up, we switch to emitting code for the loop body.

  1. setBlock forloop
  2. cgen body -- Generate the loop body

The body will contain the iteration variable scoped with its code generation. After loading its current state we increment it by the step value and store the value.

  1. ival <- load i -- Load the current loop iteration
  2. inext <- fadd ival stepval -- Increment loop variable
  3. store i inext

Finally, we evaluate the exit test of the loop, and conditionally either branch back to the same block or exit the loop.

  1. cond <- cgen cond -- Generate the loop condition
  2. test <- fcmp FP.ONE false cond -- Test if the loop condition is True ( 1.0 )
  3. cbr test forloop forexit -- Generate the loop condition

Finally, code generation of the for loop always returns 0.0. Also note that the loop variable remains in scope even after the function exits.

  1. setBlock forexit
  2. return zero

We can now generate the assembly for our printstar function, for example the body of our function will generate code like the following on x86.

  1. printstar: # @printstar
  2. .cfi_startproc
  3. # BB#0: # %entry
  4. subq $24, %rsp
  5. .Ltmp1:
  6. .cfi_def_cfa_offset 32
  7. vmovsd %xmm0, 8(%rsp) # 8-byte Spill
  8. vmovsd .LCPI0_0(%rip), %xmm0
  9. vmovapd %xmm0, %xmm1
  10. .align 16, 0x90
  11. .LBB0_1: # %loop
  12. # =>This Inner Loop Header: Depth=1
  13. vmovsd %xmm1, 16(%rsp) # 8-byte Spill
  14. vmovsd .LCPI0_1(%rip), %xmm0
  15. callq putchard
  16. vmovsd 16(%rsp), %xmm1 # 8-byte Reload
  17. vucomisd 8(%rsp), %xmm1 # 8-byte Folded Reload
  18. sbbl %eax, %eax
  19. andl $1, %eax
  20. vcvtsi2sd %eax, %xmm0, %xmm0
  21. vaddsd .LCPI0_0(%rip), %xmm1, %xmm1
  22. vucomisd .LCPI0_2, %xmm0
  23. jne .LBB0_1
  24. # BB#2: # %afterloop
  25. vxorpd %xmm0, %xmm0, %xmm0
  26. addq $24, %rsp
  27. ret