1 //
2 // Copyright (c) 2006, Brian Frank and Andy Frank
3 // Licensed under the Academic Free License version 3.0
4 //
5 // History:
6 // 15 Sep 05 Brian Frank Creation
7 //
8
9 **
10 ** CodeAsm is used to assemble the fcode instructions of an Expr or Block.
11 **
12 class CodeAsm : CompilerSupport
13 {
14
15 //////////////////////////////////////////////////////////////////////////
16 // Construction
17 //////////////////////////////////////////////////////////////////////////
18
19 new make(Compiler compiler, Location location, FPod fpod)
20 : super(compiler)
21 {
22 this.location = location
23 this.fpod = fpod
24 this.code = Buf.make
25 this.errTable = Buf.make; errTable.writeI2(-1)
26 this.errCount = 0
27 this.lines = Buf.make; lines.writeI2(-1)
28 this.lineCount = 0
29 this.loopStack = Loop[,]
30 }
31
32 //////////////////////////////////////////////////////////////////////////
33 // Statements
34 //////////////////////////////////////////////////////////////////////////
35
36 Void block(Block block)
37 {
38 block.stmts.each |Stmt s| { stmt(s) }
39 }
40
41 Void stmt(Stmt stmt)
42 {
43 switch (stmt.id)
44 {
45 case StmtId.nop: return
46 case StmtId.expr: expr(((ExprStmt)stmt).expr)
47 case StmtId.localDef: localVarDefStmt((LocalDefStmt)stmt)
48 case StmtId.ifStmt: ifStmt((IfStmt)stmt)
49 case StmtId.returnStmt: returnStmt((ReturnStmt)stmt)
50 case StmtId.throwStmt: throwStmt((ThrowStmt)stmt)
51 case StmtId.forStmt: forStmt((ForStmt)stmt)
52 case StmtId.whileStmt: whileStmt((WhileStmt)stmt)
53 case StmtId.breakStmt: breakOrContinueStmt(stmt)
54 case StmtId.continueStmt: breakOrContinueStmt(stmt)
55 case StmtId.switchStmt: switchStmt((SwitchStmt)stmt)
56 case StmtId.tryStmt: tryStmt((TryStmt)stmt)
57 default: throw Err.make(stmt.id.toStr)
58 }
59 }
60
61 private Void ifStmt(IfStmt stmt)
62 {
63 endLabel := -1
64 c := Cond.make
65
66 // optimize: if (true)
67 if (stmt.condition.id == ExprId.trueLiteral)
68 {
69 block(stmt.trueBlock)
70 return
71 }
72
73 // optimize: if (false)
74 if (stmt.condition.id == ExprId.falseLiteral)
75 {
76 if (stmt.falseBlock != null)
77 block(stmt.falseBlock)
78 return
79 }
80
81 // check condition - if the condition is itself a CondExpr
82 // then we just have it branch directly to the true/false
83 // block rather than wasting instructions to push true/false
84 // onto the stack
85 if (stmt.condition is CondExpr)
86 {
87 cond((CondExpr)stmt.condition, c)
88 }
89 else
90 {
91 expr(stmt.condition)
92 c.jumpFalses.add(jump(FOp.JumpFalse))
93 }
94
95 // true block
96 c.jumpTrues.each |Int pos| { backpatch(pos) }
97 block(stmt.trueBlock)
98 if (!stmt.trueBlock.isExit && stmt.falseBlock != null)
99 endLabel = jump(FOp.Jump)
100
101 // false block
102 c.jumpFalses.each |Int pos| { backpatch(pos) }
103 if (stmt.falseBlock != null)
104 block(stmt.falseBlock)
105
106 // end
107 if (endLabel !== -1) backpatch(endLabel)
108 }
109
110 private Void returnStmt(ReturnStmt stmt)
111 {
112 // if we are in a protected region, then we can't return immediately,
113 // rather we need to save the result into a temporary local; and use
114 // a "leave" instruction which we will backpatch in finishCode() with
115 // the actual return sequence;
116 if (inProtectedRegion)
117 {
118 // if returning a result then stash in temp local
119 if (stmt.expr != null)
120 {
121 expr(stmt.expr)
122 returnLocal = stmt.leaveVar
123 op(FOp.StoreVar, returnLocal.register)
124 }
125
126 // jump to any finally blocks we are inside
127 protectedRegions.eachr |ProtectedRegion region|
128 {
129 if (region.hasFinally)
130 region.jumpFinallys.add(jump(FOp.JumpFinally))
131 }
132
133 // generate leave instruction and register for backpatch
134 if (leavesToReturn == null) leavesToReturn = Int[,]
135 leavesToReturn.add(jump(FOp.Leave))
136 return
137 }
138
139 // process as normal return
140 if (stmt.expr != null)
141 {
142 expr(stmt.expr)
143 op(FOp.ReturnObj)
144 }
145 else
146 {
147 op(FOp.ReturnVoid)
148 }
149 }
150
151 private Void throwStmt(ThrowStmt stmt)
152 {
153 expr(stmt.exception)
154 op(FOp.Throw)
155 }
156
157 private Void localVarDefStmt(LocalDefStmt stmt)
158 {
159 if (stmt.isCatchVar)
160 {
161 op(FOp.CatchErrStart, fpod.addTypeRef(stmt.ctype))
162 op(FOp.StoreVar, stmt.var.register)
163 }
164 else if (stmt.init != null)
165 {
166 expr(stmt.init)
167 }
168 }
169
170 //////////////////////////////////////////////////////////////////////////
171 // Loops
172 //////////////////////////////////////////////////////////////////////////
173
174 private Void whileStmt(WhileStmt stmt)
175 {
176 // push myself onto the loop stack so that breaks
177 // and continues can register for backpatching
178 loop := Loop.make(stmt)
179 loopStack.push(loop)
180
181 // assemble the while loop code
182 continueLabel := mark
183 expr(stmt.condition)
184 breakJump := jump(FOp.JumpFalse)
185 block(stmt.block)
186 jump(FOp.Jump, continueLabel)
187 breakLabel := mark
188 backpatch(breakJump)
189
190 // backpatch continues/breaks
191 loop.continues.each |Int pos| { backpatch(pos, continueLabel) }
192 loop.breaks.each |Int pos| { backpatch(pos, breakLabel) }
193
194 // pop loop from stack
195 loopStack.pop
196
197 // TODO - the fcode will often contain Jumps to Jumps which can be optimized
198 }
199
200 private Void forStmt(ForStmt stmt)
201 {
202 breakJump := -1
203
204 // push myself onto the loop stack so that breaks
205 // and continues can register for backpatching
206 loop := Loop.make(stmt)
207 loopStack.push(loop)
208
209 // assemble init if available
210 if (stmt.init != null) this.stmt(stmt.init)
211
212 // assemble the for loop code
213 condLabel := mark
214 if (stmt.condition != null)
215 {
216 expr(stmt.condition)
217 breakJump = jump(FOp.JumpFalse)
218 }
219 block(stmt.block)
220 updateLabel := mark
221 if (stmt.update != null) expr(stmt.update)
222 jump(FOp.Jump, condLabel)
223 endLabel := mark
224 if (breakJump != -1) backpatch(breakJump, endLabel)
225
226 // backpatch continues/breaks
227 loop.continues.each |Int pos| { backpatch(pos, updateLabel) }
228 loop.breaks.each |Int pos| { backpatch(pos, endLabel) }
229
230 // pop loop from stack
231 loopStack.pop
232
233 // TODO - the fcode will often contain Jumps to Jumps which can be optimized
234 }
235
236 private Void breakOrContinueStmt(Stmt stmt)
237 {
238 // associated loop should be top of stack
239 loop := loopStack.peek
240 if (loop.stmt !== stmt->loop)
241 throw err("Internal compiler error", stmt.location)
242
243 // if we are inside a protection region which was pushed onto
244 // my loop's own stack that means this break or continue
245 // needs to jump out of the protected region - that requires
246 // calling each region's finally block and using a "leave"
247 // instruction rather than a standard "jump"
248 Int toBackpatch := null
249 if (!loop.protectedRegions.isEmpty)
250 {
251 // jump to any finally blocks we are inside
252 loop.protectedRegions.eachr |ProtectedRegion region|
253 {
254 if (region.hasFinally)
255 region.jumpFinallys.add(jump(FOp.JumpFinally))
256 }
257
258 // generate leave instruction
259 toBackpatch = jump(FOp.Leave)
260 }
261 else
262 {
263 // generate standard jump instruction
264 toBackpatch = jump(FOp.Jump)
265 }
266
267 // register for backpatch
268 if (stmt.id === StmtId.breakStmt)
269 loop.breaks.add(toBackpatch)
270 else
271 loop.continues.add(toBackpatch)
272 }
273
274 //////////////////////////////////////////////////////////////////////////
275 // Switch
276 //////////////////////////////////////////////////////////////////////////
277
278 private Void switchStmt(SwitchStmt stmt)
279 {
280 // A table switch is a series of contiguous (or near contiguous)
281 // cases which can be represented an offset into a jump table.
282 minMax := computeTableRange(stmt)
283 if (minMax != null)
284 tableSwitchStmt(stmt, minMax[0], minMax[1])
285 else
286 equalsSwitchStmt(stmt)
287 }
288
289 **
290 ** Compute the range of this switch and return as a list of '[min, max]'
291 ** if the switch is a candidate for a table switch as a series of
292 ** contiguous (or near contiguous) cases which can be represented an
293 ** offset into a jump table. Return null if the switch is not numeric
294 ** or too sparse to use as a table switch.
295 **
296 private Int[] computeTableRange(SwitchStmt stmt)
297 {
298 // we only compute ranges for Ints and Enums
299 ctype := stmt.condition.ctype
300 if (!ctype.isInt && !ctype.isEnum)
301 return null
302
303 // now we need to determine contiguous range
304 min := 2147483647
305 max := -2147483648
306 count := 0
307 try
308 {
309 stmt.cases.each |Case c|
310 {
311 for (i:=0; i<c.cases.size; ++i)
312 {
313 count++
314 expr := c.cases[i]
315 literal := expr.asTableSwitchCase
316 if (literal == null) throw CompilerErr.make("return null", c.location)
317 if (literal < min) min = literal
318 if (literal > max) max = literal
319 }
320 }
321 }
322 catch (CompilerErr e)
323 {
324 return null
325 }
326
327 // if no cases, then don't use tableswitch
328 if (count == 0) return null
329
330 // enums and anything with less than 32 jumps is immediately
331 // allowed, otherwise base the table on a percentage of count
332 delta := max - min
333 if (ctype.isEnum || delta < 32 || count*32 > delta)
334 return [min,max]
335 else
336 return null
337 }
338
339 private Void tableSwitchStmt(SwitchStmt stmt, Int min, Int max)
340 {
341 stmt.isTableswitch = true
342 isEnum := stmt.condition.ctype.isEnum
343
344 // push condition onto the stack
345 expr(stmt.condition)
346
347 // if this is an enum get ordinal on the stack
348 if (isEnum)
349 op(FOp.CallVirtual, fpod.addMethodRef(ns.enumOrdinal))
350
351 // if min is not zero, then do a subtraction so that
352 // our condition is a zero based index into the jump table
353 if (min != 0)
354 {
355 op(FOp.LoadInt, fpod.ints.add(-min))
356 op(FOp.CallVirtual, fpod.addMethodRef(ns.intPlus))
357 }
358
359 // now allocate our jump table
360 count := max - min + 1
361 jumps := Case[,]
362 jumps.size = count
363
364 // walk thru each case, and map the jump offset to a block
365 stmt.cases.each |Case c|
366 {
367 for (i:=0; i<c.cases.size; ++i)
368 {
369 expr := c.cases[i]
370 literal := expr.asTableSwitchCase
371 offset := literal - min
372 jumps[offset] = c
373 }
374 }
375
376 // now write the switch bytecodes
377 op(FOp.Switch)
378 code.writeI2(count)
379 jumpStart := code.size
380 fill := count*2
381 fill.times |,| { code.write(0xff) } // we'll backpatch the jump offsets last
382
383 // default block goes first - it's the switch fall
384 // thru, save offset to back patch jump
385 defaultStart := mark
386 defaultEnd := switchBlock(stmt.defaultBlock)
387
388 // now write each case block
389 caseEnds := Int[,]
390 caseEnds.size = stmt.cases.size
391 stmt.cases.each |Case c, Int i|
392 {
393 c.startOffset = code.size
394 caseEnds[i] = switchBlock(c.block)
395 }
396
397 // backpatch the jump table
398 end := code.size
399 code.seek(jumpStart)
400 jumps.each |Case c, Int i|
401 {
402 if (c == null)
403 code.writeI2(defaultStart)
404 else
405 code.writeI2(c.startOffset)
406 }
407 code.seek(end)
408
409 // backpatch all the case blocks to jump here when done
410 if (defaultEnd != -1) backpatch(defaultEnd)
411 caseEnds.each |Int pos|
412 {
413 if (pos != -1) backpatch(pos)
414 }
415 }
416
417 private Void equalsSwitchStmt(SwitchStmt stmt)
418 {
419 stmt.isTableswitch = false
420
421 // push condition onto the stack
422 expr(stmt.condition)
423
424 // walk thru each case, keeping track of all the
425 // places we need to backpatch when cases match
426 jumpPositions := Int[,]
427 jumpCases := Case[,]
428 stmt.cases.each |Case c|
429 {
430 for (i:=0; i<c.cases.size; ++i)
431 {
432 op(FOp.Dup)
433 expr(c.cases[i])
434 op(FOp.CmpEQ) // TODO eq/jump combo?
435 jumpPositions.add(jump(FOp.JumpTrue))
436 jumpCases.add(c)
437 }
438 }
439
440 // default block goes first - it's the switch fall
441 // thru, save offset to back patch jump
442 defaultStart := mark
443 defaultEnd := switchBlock(stmt.defaultBlock, true)
444
445 // now write each case block
446 caseEnds := Int[,]
447 caseEnds.size = stmt.cases.size
448 stmt.cases.each |Case c, Int i|
449 {
450 c.startOffset = code.size
451 caseEnds[i] = switchBlock(c.block, true)
452 }
453
454 // backpatch the jump table
455 end := code.size
456 jumpPositions.each |Int pos, Int i|
457 {
458 backpatch(pos, jumpCases[i].startOffset)
459 }
460 code.seek(end)
461
462 // backpatch all the case blocks to jump here when done
463 if (defaultEnd != -1) backpatch(defaultEnd)
464 caseEnds.each |Int pos|
465 {
466 if (pos != -1) backpatch(pos)
467 }
468 }
469
470 private Int switchBlock(Block block, Bool pop := false)
471 {
472 if (pop) op(FOp.Pop);
473 if (block != null)
474 {
475 this.block(block)
476 if (block.isExit) return -1
477 }
478 return jump(FOp.Jump)
479 }
480
481 //////////////////////////////////////////////////////////////////////////
482 // Try
483 //////////////////////////////////////////////////////////////////////////
484
485 private Bool inProtectedRegion()
486 {
487 return protectedRegions != null && !protectedRegions.isEmpty
488 }
489
490 private Void tryStmt(TryStmt stmt)
491 {
492 // enter a "protected region" which means that we can't
493 // jump or return out of this region directly - we have to
494 // use a special "leave" jump of the protected region
495 if (protectedRegions == null) protectedRegions = ProtectedRegion[,]
496 region := ProtectedRegion.make(stmt)
497 protectedRegions.push(region)
498 if (!loopStack.isEmpty) loopStack.peek.protectedRegions.push(region)
499
500 // assemble body of try block
501 start := mark
502 block(stmt.block)
503 end := mark
504
505 // if the block isn't guaranteed to exit:
506 // 1) if we have a finally, then jump to finally
507 // 2) jump over catch blocks
508 tryDone := -1
509 finallyStart := -1
510 if (!stmt.block.isExit)
511 {
512 if (region.hasFinally)
513 {
514 region.jumpFinallys.add(jump(FOp.JumpFinally))
515 end = mark
516 }
517 tryDone = jump(FOp.Leave)
518 }
519
520 // assemble catch blocks
521 catchDones := Int[,]
522 catchDones.size = stmt.catches.size
523 stmt.catches.each |Catch c, Int i|
524 {
525 catchDones[i] = tryCatch(c, start, end, region)
526 }
527
528 // assemble finally block
529 if (region.hasFinally)
530 {
531 // wrap try block and each catch block with catch all to finally
532 addToErrTable(start, end, mark, null)
533 stmt.catches.each |Catch c|
534 {
535 addToErrTable(c.start, c.end, mark, null)
536 }
537
538 // handler code
539 region.jumpFinallys.each |Int pos| { backpatch(pos) }
540 op(FOp.FinallyStart)
541 block(stmt.finallyBlock)
542 op(FOp.FinallyEnd)
543 }
544
545 // mark next statement as jump destination for try block
546 if (tryDone != -1) backpatch(tryDone)
547 catchDones.each |Int pos| { if (pos != -1) backpatch(pos) }
548
549 // leave protected region
550 if (!loopStack.isEmpty) loopStack.peek.protectedRegions.pop
551 protectedRegions.pop
552 }
553
554 private Int tryCatch(Catch c, Int start, Int end, ProtectedRegion region)
555 {
556 // assemble catch block - if there isn't a local variable
557 // we emit the CatchAllStart, otherwise the block will
558 // start off with a LocalVarDef which will write out the
559 // CatchErrStart opcode
560 handler := mark
561 c.start = mark
562 if (c.errVariable == null) op(FOp.CatchAllStart)
563 block(c.block)
564 done := -1
565 if (!c.block.isExit)
566 {
567 if (region.hasFinally)
568 region.jumpFinallys.add(jump(FOp.JumpFinally))
569
570 done = jump(FOp.Leave)
571 }
572 c.end = mark
573 op(FOp.CatchEnd)
574
575 // fill in err table
576 addToErrTable(start, end, handler, c.errType)
577
578 // return position to backpatch
579 return done
580 }
581
582 private Void addToErrTable(Int start, Int end, Int handler, CType errType)
583 {
584 // catch all is implicitly a catch for sys::Err
585 if (errType == null) errType = ns.errType
586
587 // add to err table buffer
588 errCount++
589 errTable.writeI2(start)
590 errTable.writeI2(end)
591 errTable.writeI2(handler)
592 errTable.writeI2(fpod.addTypeRef(errType))
593 }
594
595 //////////////////////////////////////////////////////////////////////////
596 // Expressions
597 //////////////////////////////////////////////////////////////////////////
598
599 Void expr(Expr expr)
600 {
601 line(expr.location)
602 switch (expr.id)
603 {
604 case ExprId.nullLiteral: nullLiteral
605 case ExprId.trueLiteral:
606 case ExprId.falseLiteral: boolLiteral((LiteralExpr)expr)
607 case ExprId.intLiteral: intLiteral((LiteralExpr)expr)
608 case ExprId.floatLiteral: floatLiteral((LiteralExpr)expr)
609 case ExprId.strLiteral: strLiteral((LiteralExpr)expr)
610 case ExprId.durationLiteral: durationLiteral((LiteralExpr)expr)
611 case ExprId.uriLiteral: uriLiteral((LiteralExpr)expr)
612 case ExprId.typeLiteral: typeLiteral((LiteralExpr)expr)
613 case ExprId.rangeLiteral: rangeLiteral((RangeLiteralExpr)expr)
614 case ExprId.listLiteral: listLiteral((ListLiteralExpr)expr)
615 case ExprId.mapLiteral: mapLiteral((MapLiteralExpr)expr)
616 case ExprId.simpleLiteral: simpleLiteral((SimpleLiteralExpr)expr)
617 case ExprId.boolNot: not((UnaryExpr)expr)
618 case ExprId.cmpNull: cmpNull((UnaryExpr)expr)
619 case ExprId.cmpNotNull: cmpNotNull((UnaryExpr)expr)
620 case ExprId.assign: assign((BinaryExpr)expr)
621 case ExprId.same: same((BinaryExpr)expr)
622 case ExprId.notSame: notSame((BinaryExpr)expr)
623 case ExprId.boolOr: or((CondExpr)expr, null)
624 case ExprId.boolAnd: and((CondExpr)expr, null)
625 case ExprId.isExpr: isExpr((TypeCheckExpr)expr)
626 case ExprId.asExpr: asExpr((TypeCheckExpr)expr)
627 case ExprId.localVar:
628 case ExprId.thisExpr:
629 case ExprId.superExpr: loadLocalVar((LocalVarExpr)expr)
630 case ExprId.call: call((CallExpr)expr)
631 case ExprId.shortcut: shortcut((ShortcutExpr)expr)
632 case ExprId.field: loadField((FieldExpr)expr)
633 case ExprId.cast: cast((TypeCheckExpr)expr)
634 case ExprId.closure: this.expr(((ClosureExpr)expr).substitute)
635 case ExprId.ternary: ternary((TernaryExpr)expr)
636 case ExprId.withBlock: withBlock((WithBlockExpr)expr)
637 case ExprId.withBase: return
638 case ExprId.staticTarget: return
639 default: throw Err.make(expr.id.toStr)
640 }
641 }
642
643 //////////////////////////////////////////////////////////////////////////
644 // Literals
645 //////////////////////////////////////////////////////////////////////////
646
647 private Void nullLiteral()
648 {
649 op(FOp.LoadNull)
650 }
651
652 private Void boolLiteral(LiteralExpr expr)
653 {
654 if (expr.val == true)
655 op(FOp.LoadTrue)
656 else
657 op(FOp.LoadFalse)
658 }
659
660 private Void intLiteral(LiteralExpr expr)
661 {
662 op(FOp.LoadInt, fpod.ints.add(expr.val))
663 }
664
665 private Void floatLiteral(LiteralExpr expr)
666 {
667 op(FOp.LoadFloat, fpod.floats.add(expr.val))
668 }
669
670 private Void strLiteral(LiteralExpr expr)
671 {
672 op(FOp.LoadStr, fpod.strs.add(expr.val))
673 }
674
675 private Void durationLiteral(LiteralExpr expr)
676 {
677 op(FOp.LoadDuration, fpod.durations.add(expr.val))
678 }
679
680 private Void uriLiteral(LiteralExpr expr)
681 {
682 op(FOp.LoadUri, fpod.uris.add(expr.val))
683 }
684
685 private Void typeLiteral(LiteralExpr expr)
686 {
687 val := (CType)expr.val
688 op(FOp.LoadType, fpod.addTypeRef(val));
689 }
690
691 private Void rangeLiteral(RangeLiteralExpr r)
692 {
693 expr(r.start);
694 expr(r.end);
695 if (r.exclusive)
696 op(FOp.CallNew, fpod.addMethodRef(ns.rangeMakeExclusive))
697 else
698 op(FOp.CallNew, fpod.addMethodRef(ns.rangeMakeInclusive))
699 }
700
701 private Void listLiteral(ListLiteralExpr list)
702 {
703 v := ((ListType)list.ctype).v
704
705 op(FOp.LoadType, fpod.addTypeRef(v));
706 op(FOp.LoadInt, fpod.ints.add(list.vals.size))
707 op(FOp.CallNew, fpod.addMethodRef(ns.listMake))
708
709 add := fpod.addMethodRef(ns.listAdd)
710 for (i:=0; i<list.vals.size; ++i)
711 {
712 expr(list.vals[i])
713 op(FOp.CallVirtual, add)
714 }
715 }
716
717 private Void mapLiteral(MapLiteralExpr map)
718 {
719 op(FOp.LoadType, fpod.addTypeRef(map.ctype))
720 op(FOp.CallNew, fpod.addMethodRef(ns.mapMake))
721
722 set := fpod.addMethodRef(ns.mapSet)
723 for (i:=0; i<map.keys.size; ++i)
724 {
725 expr(map.keys[i])
726 expr(map.vals[i])
727 op(FOp.CallVirtual, set)
728 }
729 }
730
731 private Void simpleLiteral(SimpleLiteralExpr simple)
732 {
733 if (!simple.method.isStatic)
734 throw err("parse not static", simple.location)
735
736 expr(simple.arg)
737 op(FOp.CallStatic, fpod.addMethodRef(simple.method))
738 }
739
740 //////////////////////////////////////////////////////////////////////////
741 // UnaryExpr
742 //////////////////////////////////////////////////////////////////////////
743
744 private Void not(UnaryExpr unary)
745 {
746 expr(unary.operand)
747 op(FOp.CallVirtual, fpod.addMethodRef(ns.boolNot))
748 }
749
750 private Void cmpNull(UnaryExpr unary)
751 {
752 expr(unary.operand)
753 op(FOp.CmpNull)
754 }
755
756 private Void cmpNotNull(UnaryExpr unary)
757 {
758 expr(unary.operand)
759 op(FOp.CmpNotNull)
760 }
761
762 //////////////////////////////////////////////////////////////////////////
763 // BinaryExpr
764 //////////////////////////////////////////////////////////////////////////
765
766 private Void same(BinaryExpr binary)
767 {
768 if (binary.lhs.id === ExprId.nullLiteral)
769 {
770 expr(binary.rhs)
771 op(FOp.CmpNull)
772 }
773 else if (binary.rhs.id === ExprId.nullLiteral)
774 {
775 expr(binary.lhs)
776 op(FOp.CmpNull)
777 }
778 else
779 {
780 expr(binary.lhs)
781 expr(binary.rhs)
782 op(FOp.CmpSame)
783 }
784 }
785
786 private Void notSame(BinaryExpr binary)
787 {
788 if (binary.lhs.id === ExprId.nullLiteral)
789 {
790 expr(binary.rhs)
791 op(FOp.CmpNotNull)
792 }
793 else if (binary.rhs.id === ExprId.nullLiteral)
794 {
795 expr(binary.lhs)
796 op(FOp.CmpNotNull)
797 }
798 else
799 {
800 expr(binary.lhs)
801 expr(binary.rhs)
802 op(FOp.CmpNotSame)
803 }
804 }
805
806 //////////////////////////////////////////////////////////////////////////
807 // CondExpr
808 //////////////////////////////////////////////////////////////////////////
809
810 private Void cond(CondExpr expr, Cond cond)
811 {
812 switch (expr.id)
813 {
814 case ExprId.boolOr: or(expr, cond)
815 case ExprId.boolAnd: and(expr, cond)
816 default: throw Err.make(expr.id.toStr)
817 }
818 }
819
820 private Void or(CondExpr expr, Cond cond)
821 {
822 // if cond is null this is a top level expr which means
823 // the result is to push true or false onto the stack;
824 // otherwise our only job is to do the various jumps if
825 // true or fall-thru if true (used with if statement)
826 // NOTE: this code could be further optimized because
827 // it doesn't optimize "a && b || c && c"
828 topLevel := cond == null
829 if (topLevel) cond = Cond.make
830
831 // perform short circuit logical-or
832 expr.operands.each |Expr operand, Int i|
833 {
834 this.expr(operand)
835 if (i < expr.operands.size-1)
836 cond.jumpTrues.add(jump(FOp.JumpTrue))
837 else
838 cond.jumpFalses.add(jump(FOp.JumpFalse))
839 }
840
841 // if top level push true/false onto stack
842 if (topLevel) condEnd(cond)
843 }
844
845 private Void and(CondExpr expr, Cond cond)
846 {
847 // if cond is null this is a top level expr which means
848 // the result is to push true or false onto the stack;
849 // otherwise our only job is to do the various jumps if
850 // true or fall-thru if true (used with if statement)
851 // NOTE: this code could be further optimized because
852 // it doesn't optimize "a && b || c && c"
853 topLevel := cond == null
854 if (topLevel) cond = Cond.make
855
856 // perform short circuit logical-and
857 expr.operands.each |Expr operand|
858 {
859 this.expr(operand)
860 cond.jumpFalses.add(jump(FOp.JumpFalse))
861 }
862
863 // if top level push true/false onto stack
864 if (topLevel) condEnd(cond)
865 }
866
867 private Void condEnd(Cond cond)
868 {
869 // true if always fall-thru
870 cond.jumpTrues.each |Int pos| { backpatch(pos) }
871 op(FOp.LoadTrue)
872 end := jump(FOp.Jump)
873
874 // false
875 cond.jumpFalses.each |Int pos| { backpatch(pos) }
876 op(FOp.LoadFalse)
877
878 backpatch(end)
879 }
880
881 //////////////////////////////////////////////////////////////////////////
882 // Type Checks
883 //////////////////////////////////////////////////////////////////////////
884
885 private Void isExpr(TypeCheckExpr tc)
886 {
887 expr(tc.target)
888 op(FOp.Is, fpod.addTypeRef(tc.check))
889 }
890
891 private Void asExpr(TypeCheckExpr tc)
892 {
893 expr(tc.target)
894 op(FOp.As, fpod.addTypeRef(tc.check))
895 }
896
897 private Void cast(TypeCheckExpr tc)
898 {
899 expr(tc.target)
900 op(FOp.Cast, fpod.addTypeRef(tc.check))
901 }
902
903 //////////////////////////////////////////////////////////////////////////
904 // Ternary
905 //////////////////////////////////////////////////////////////////////////
906
907 private Void ternary(TernaryExpr ternary)
908 {
909 expr(ternary.condition)
910 falseLabel := jump(FOp.JumpFalse)
911 expr(ternary.trueExpr)
912 endLabel := jump(FOp.Jump)
913 backpatch(falseLabel)
914 expr(ternary.falseExpr)
915 backpatch(endLabel)
916 }
917
918 //////////////////////////////////////////////////////////////////////////
919 // WithBlock
920 //////////////////////////////////////////////////////////////////////////
921
922 private Void withBlock(WithBlockExpr withBlock)
923 {
924 expr(withBlock.base)
925 withBlock.subs.each |Expr sub|
926 {
927 op(FOp.Dup)
928 expr(sub)
929 if (sub.leave) throw Err.make("should never leave with expr " + sub.location)
930 }
931 }
932
933 //////////////////////////////////////////////////////////////////////////
934 // Assign
935 //////////////////////////////////////////////////////////////////////////
936
937 **
938 ** Simple assignment using =
939 **
940 private Void assign(BinaryExpr expr)
941 {
942 switch (expr.lhs.id)
943 {
944 case ExprId.localVar: assignLocalVar(expr)
945 case ExprId.field: assignField(expr)
946 default: throw err("Internal compiler error", expr.location)
947 }
948 }
949
950 //////////////////////////////////////////////////////////////////////////
951 // Local Var
952 //////////////////////////////////////////////////////////////////////////
953
954 private Void loadLocalVar(LocalVarExpr var)
955 {
956 op(FOp.LoadVar, var.register)
957 }
958
959 private Void storeLocalVar(LocalVarExpr var)
960 {
961 op(FOp.StoreVar, var.register);
962 }
963
964 private Void assignLocalVar(BinaryExpr assign)
965 {
966 expr(assign.rhs)
967 if (assign.leave) op(FOp.Dup)
968 storeLocalVar((LocalVarExpr)assign.lhs)
969 }
970
971 //////////////////////////////////////////////////////////////////////////
972 // Field
973 //////////////////////////////////////////////////////////////////////////
974
975 private Void loadField(FieldExpr fexpr, Bool dupTarget := false)
976 {
977 field := fexpr.field
978
979 if (fexpr.target != null)
980 {
981 expr(fexpr.target);
982 if (dupTarget) op(FOp.Dup)
983 }
984
985 if (fexpr.useAccessor)
986 {
987 getter := field.getter // if null then bug in useAccessor
988 index := fpod.addMethodRef(getter)
989 if (field.parent.isMixin)
990 {
991 if (getter.isStatic)
992 op(FOp.CallMixinStatic, index)
993 else
994 op(FOp.CallMixinVirtual, index)
995 }
996 else
997 {
998 if (getter.isStatic)
999 op(FOp.CallStatic, index)
1000 else if (fexpr.target.id == ExprId.superExpr)
1001 op(FOp.CallNonVirtual, index)
1002 else
1003 op(FOp.CallVirtual, index)
1004 }
1005
1006 if (field.isCovariant)
1007 {
1008 op(FOp.Cast, fpod.addTypeRef(field.fieldType))
1009 }
1010 }
1011 else
1012 {
1013 index := fpod.addFieldRef(field)
1014 if (field.parent.isMixin)
1015 {
1016 if (field.isStatic)
1017 op(FOp.LoadMixinStatic, index)
1018 else
1019 throw err("LoadMixinInstance", fexpr.location)
1020 }
1021 else
1022 {
1023 if (field.isStatic)
1024 op(FOp.LoadStatic, index)
1025 else
1026 op(FOp.LoadInstance, index)
1027 }
1028 }
1029 }
1030
1031 private Void assignField(BinaryExpr assign)
1032 {
1033 lhs := (FieldExpr)assign.lhs
1034 isInstanceField := !lhs.field.isStatic; // used to determine how to duplicate
1035
1036 if (lhs .target != null) expr(lhs.target)
1037 expr(assign.rhs);
1038 if (assign.leave)
1039 {
1040 op(FOp.Dup)
1041 if (isInstanceField)
1042 op(FOp.StoreVar, assign.tempVar.register)
1043 }
1044 storeField(lhs)
1045 if (assign.leave && isInstanceField)
1046 {
1047 op(FOp.LoadVar, assign.tempVar.register)
1048 }
1049 }
1050
1051 private Void storeField(FieldExpr fexpr)
1052 {
1053 field := fexpr.field
1054 if (fexpr.useAccessor)
1055 {
1056 setter := field.setter // if null then bug in useAccessor
1057 index := fpod.addMethodRef(setter)
1058
1059 if (field.parent.isMixin) // TODO
1060 {
1061 if (setter.isStatic)
1062 op(FOp.CallMixinStatic, index)
1063 else
1064 op(FOp.CallMixinVirtual, index)
1065 }
1066 else
1067 {
1068 if (setter.isStatic)
1069 op(FOp.CallStatic, index)
1070 else if (fexpr.target.id == ExprId.superExpr)
1071 op(FOp.CallNonVirtual, index)
1072 else
1073 op(FOp.CallVirtual, index)
1074 }
1075 }
1076 else
1077 {
1078 index := fpod.addFieldRef(field)
1079
1080 if (field.parent.isMixin)
1081 {
1082 if (field.isStatic)
1083 op(FOp.StoreMixinStatic, index)
1084 else
1085 throw err("StoreMixinInstance", fexpr.location)
1086 }
1087 else
1088 {
1089 if (field.isStatic)
1090 op(FOp.StoreStatic, index)
1091 else
1092 op(FOp.StoreInstance, index)
1093 }
1094 }
1095 }
1096
1097 //////////////////////////////////////////////////////////////////////////
1098 // Call
1099 //////////////////////////////////////////////////////////////////////////
1100
1101 private Void call(CallExpr call, Bool leave := call.leave)
1102 {
1103 if (call.isDynamic)
1104 {
1105 dynamicCall(call)
1106 }
1107 else
1108 {
1109 prepareCall(call)
1110 invokeCall(call, leave)
1111 }
1112 }
1113
1114 private Void dynamicCall(CallExpr call)
1115 {
1116 // target
1117 expr(call.target)
1118
1119 // name str literal
1120 op(FOp.LoadStr, fpod.strs.add(call.name))
1121
1122 // args Obj[]
1123 op(FOp.LoadInt, fpod.ints.add(call.args.size))
1124 op(FOp.CallNew, fpod.addMethodRef(ns.listMakeObj))
1125 add := fpod.addMethodRef(ns.listAdd)
1126 call.args.each |Expr arg|
1127 {
1128 expr(arg)
1129 op(FOp.CallVirtual, add)
1130 }
1131
1132 // Obj.trap
1133 op(FOp.CallVirtual, fpod.addMethodRef(ns.objTrap))
1134
1135 // pop return if no leave
1136 if (!call.leave) op(FOp.Pop)
1137 }
1138
1139 private Void prepareCall(CallExpr call)
1140 {
1141 if (call.target != null) expr(call.target)
1142 call.args.each |Expr arg| { expr(arg) }
1143 }
1144
1145 private Void invokeCall(CallExpr call, Bool leave := call.leave)
1146 {
1147 m := call.method
1148 index := fpod.addMethodRef(m, call.args.size)
1149
1150 // write CallVirtual, CallNonVirtual, CallStatic, CallNew, or CallCtor;
1151 // note that if a constructor call has a target (this or super), then it
1152 // is a CallCtor instance call because we don't want to allocate
1153 // a new instance
1154 if (m.parent.isMixin)
1155 {
1156 if (m.isStatic)
1157 op(FOp.CallMixinStatic, index)
1158 else if (call.target.id == ExprId.superExpr)
1159 op(FOp.CallMixinNonVirtual, index)
1160 else
1161 op(FOp.CallMixinVirtual, index)
1162 }
1163 else if (m.isStatic)
1164 {
1165 op(FOp.CallStatic, index)
1166 }
1167 else if (m.isCtor)
1168 {
1169 if (call.target == null || call.target.id == ExprId.staticTarget)
1170 op(FOp.CallNew, index)
1171 else
1172 op(FOp.CallCtor, index)
1173 }
1174 else
1175 {
1176 // because CallNonVirtual maps to Java's invokespecial, we can't
1177 // use it for calls outside of the class (consider it like calling
1178 // protected method)
1179 targetId := call.target.id
1180 if (targetId == ExprId.superExpr || (targetId == ExprId.thisExpr && !m.isVirtual))
1181 op(FOp.CallNonVirtual, index)
1182 else
1183 op(FOp.CallVirtual, index)
1184 }
1185
1186 // if we are leaving a value on the stack of a method which
1187 // has a parameterized return value or is covariant, then we
1188 // need to insert a cast operation
1189 // Int.toStr -> non-generic - no cast
1190 // Str[].toStr -> return isn't parameterized - no cast
1191 // Str[].get() -> actual return is Obj, but we want Str - cast
1192 // covariant -> actual call is against inheritedReturnType
1193 if (leave)
1194 {
1195 if (m.isParameterized)
1196 {
1197 ret := m.generic.returnType
1198 if (ret.isGenericParameter)
1199 op(FOp.Cast, fpod.addTypeRef(m.returnType))
1200 }
1201 else if (m.isCovariant)
1202 {
1203 op(FOp.Cast, fpod.addTypeRef(m.returnType))
1204 }
1205 }
1206
1207 // if the method left a value on the stack, and we
1208 // aren't going to use it, then pop it off
1209 if (!leave)
1210 {
1211 // note we need to use the actual method signature (not parameterized)
1212 x := m.isParameterized ? m.generic : m
1213 if (!x.returnType.isVoid || x.isCtor)
1214 op(FOp.Pop)
1215 }
1216 }
1217
1218 //////////////////////////////////////////////////////////////////////////
1219 // Shortcut
1220 //////////////////////////////////////////////////////////////////////////
1221
1222 private Void shortcut(ShortcutExpr call)
1223 {
1224 // handle comparisions as special opcodes
1225 switch (call.opToken)
1226 {
1227 case Token.eq: prepareCall(call); op(FOp.CmpEQ); return
1228 case Token.notEq: prepareCall(call); op(FOp.CmpNE); return
1229 case Token.cmp: prepareCall(call); op(FOp.Cmp); return
1230 case Token.lt: prepareCall(call); op(FOp.CmpLT); return
1231 case Token.ltEq: prepareCall(call); op(FOp.CmpLE); return
1232 case Token.gt: prepareCall(call); op(FOp.CmpGT); return
1233 case Token.gtEq: prepareCall(call); op(FOp.CmpGE); return
1234 }
1235
1236 // always check string concat first since it can
1237 // have string on either left or right hand side
1238 if (call.isStrConcat)
1239 {
1240 addStr(call, true)
1241 return
1242 }
1243
1244 // if assignment we need to do a bunch of special processing
1245 if (call.isAssign)
1246 {
1247 shortcutAssign(call)
1248 return
1249 }
1250
1251 // just process as normal call
1252 this.call(call)
1253 }
1254
1255 **
1256 ** This method is used for complex assignments: prefix/postfix
1257 ** increment and special dual assignment operators like "+=".
1258 **
1259 private Void shortcutAssign(ShortcutExpr c)
1260 {
1261 var := c.target
1262 leaveUsingTemp := false
1263
1264 // load the variable
1265 switch (var.id)
1266 {
1267 case ExprId.localVar:
1268 loadLocalVar((LocalVarExpr)var)
1269 case ExprId.field:
1270 fexpr := (FieldExpr)var
1271 loadField(fexpr, true) // dup target on stack for upcoming set
1272 leaveUsingTemp = !fexpr.field.isStatic // used to determine how to duplicate
1273 case ExprId.shortcut:
1274 // since .NET sucks when it comes to stack manipulation,
1275 // we use two scratch locals to get the stack into the
1276 // following format:
1277 // index \ used for get
1278 // target /
1279 // index \ used for set
1280 // target /
1281 index := (IndexedAssignExpr)c
1282 get := (ShortcutExpr)index.target
1283 expr(get.target) // target
1284 op(FOp.Dup)
1285 op(FOp.StoreVar, index.scratchA.register)
1286 expr(get.args[0]) // index expr
1287 op(FOp.Dup)
1288 op(FOp.StoreVar, index.scratchB.register)
1289 op(FOp.LoadVar, index.scratchA.register)
1290 op(FOp.LoadVar, index.scratchB.register)
1291 invokeCall(get, true)
1292 leaveUsingTemp = true
1293 default:
1294 throw err("Internal error", var.location)
1295 }
1296
1297 // if postfix leave, duplicate value before we preform computation
1298 if (c.leave && c.isPostfixLeave)
1299 {
1300 op(FOp.Dup)
1301 if (leaveUsingTemp)
1302 op(FOp.StoreVar, c.tempVar.register)
1303 }
1304
1305 // load args and invoke call
1306 c.args.each |Expr arg| { expr(arg) }
1307 invokeCall(c, true)
1308
1309 // if prefix, duplicate after we've done computation
1310 if (c.leave && !c.isPostfixLeave)
1311 {
1312 op(FOp.Dup)
1313 if (leaveUsingTemp)
1314 op(FOp.StoreVar, c.tempVar.register)
1315 }
1316
1317 // save the variable back
1318 switch (var.id)
1319 {
1320 case ExprId.localVar:
1321 storeLocalVar((LocalVarExpr)var)
1322 case ExprId.field:
1323 storeField((FieldExpr)var)
1324 case ExprId.shortcut:
1325 set := (CMethod)c->setMethod
1326 op(FOp.CallVirtual, fpod.addMethodRef(set, 2))
1327 if (!set.returnType.isVoid) op(FOp.Pop)
1328 }
1329
1330 // if field leave, then load back from temp local
1331 if (c.leave && leaveUsingTemp)
1332 op(FOp.LoadVar, c.tempVar.register)
1333 }
1334
1335 //////////////////////////////////////////////////////////////////////////
1336 // Strings
1337 //////////////////////////////////////////////////////////////////////////
1338
1339 **
1340 ** Assemble code to build a string using sys::StrBuf.
1341 **
1342 private Void addStr(ShortcutExpr expr, Bool topLevel)
1343 {
1344 if (topLevel)
1345 op(FOp.CallNew, fpod.addMethodRef(ns.strBufMake, 0))
1346
1347 lhs := expr.target
1348 rhs := expr.args.first
1349
1350 lhsShortcut := lhs as ShortcutExpr
1351 if (lhsShortcut != null && lhsShortcut.isStrConcat)
1352 {
1353 addStr(lhsShortcut, false)
1354 }
1355 else
1356 {
1357 if (!isEmptyStrLiteral(lhs))
1358 {
1359 this.expr(lhs)
1360 op(FOp.CallVirtual, fpod.addMethodRef(ns.strBufAdd))
1361 }
1362 }
1363
1364 if (!isEmptyStrLiteral(rhs))
1365 {
1366 this.expr(rhs)
1367 op(FOp.CallVirtual, fpod.addMethodRef(ns.strBufAdd))
1368 }
1369
1370 if (topLevel) op(FOp.CallVirtual, fpod.addMethodRef(ns.strBufToStr))
1371 }
1372
1373 private Bool isEmptyStrLiteral(Expr expr)
1374 {
1375 return expr.id === ExprId.strLiteral && expr->val == ""
1376 }
1377
1378 //////////////////////////////////////////////////////////////////////////
1379 // Code Buffer
1380 //////////////////////////////////////////////////////////////////////////
1381
1382 **
1383 ** Append a opcode with option two byte argument.
1384 **
1385 Void op(FOp op, Int arg := null)
1386 {
1387 code.write(op.ordinal)
1388 if (arg != null) code.writeI2(arg)
1389 }
1390
1391 //////////////////////////////////////////////////////////////////////////
1392 // Jumps
1393 //////////////////////////////////////////////////////////////////////////
1394
1395 **
1396 ** Get the current location as a mark to use for backwards jump.
1397 **
1398 private Int mark()
1399 {
1400 return code.size
1401 }
1402
1403 **
1404 ** Add the specified jump opcode and two bytes for the jump
1405 ** location. If a backward jump then pass the mark; if a
1406 ** a forward jump we return the code pos to backpatch the
1407 ** mark later.
1408 **
1409 private Int jump(FOp op, Int mark := 0xffff)
1410 {
1411 this.op(op, mark)
1412 return code.size-2
1413 }
1414
1415 **
1416 ** Backpacth the mark of forward jump using the given
1417 ** pos which was returned by jump(). If mark is defaulted,
1418 ** then we use the current instruction as the mark.
1419 **
1420 private Void backpatch(Int pos, Int mark := code.size)
1421 {
1422 orig := code.pos
1423 code.seek(pos).writeI2(mark)
1424 code.seek(orig)
1425 }
1426
1427 //////////////////////////////////////////////////////////////////////////
1428 // Utils
1429 //////////////////////////////////////////////////////////////////////////
1430
1431 **
1432 ** Finish writing out the exception handling table
1433 **
1434 Buf finishCode()
1435 {
1436 // if we had to return from a protected region, then now we
1437 // need to generate the actual return instructions and backpatch
1438 // all the leaves
1439 if (leavesToReturn != null)
1440 {
1441 leavesToReturn.each |Int pos| { backpatch(pos) }
1442 if (returnLocal != null)
1443 {
1444 op(FOp.LoadVar, returnLocal.register)
1445 op(FOp.ReturnObj)
1446 }
1447 else
1448 {
1449 op(FOp.ReturnVoid)
1450 }
1451 }
1452
1453 // check final size
1454 if (code.size >= 0x7fff) throw err("Method too big", location)
1455 return code
1456 }
1457
1458 **
1459 ** Finish writing out the exception handling table
1460 **
1461 Buf finishErrTable()
1462 {
1463 errTable.seek(0).writeI2(errCount)
1464 return errTable
1465 }
1466
1467 **
1468 ** Finish writing out the line number table
1469 **
1470 Buf finishLines()
1471 {
1472 lines.seek(0).writeI2(lineCount)
1473 return lines
1474 }
1475
1476 **
1477 ** Map the opcode we are getting ready to add to the specified line number
1478 **
1479 private Void line(Location loc)
1480 {
1481 line := loc.line
1482 if (line == null || lastLine == line) return
1483 lineCount++
1484 lines.writeI2(code.size)
1485 lines.writeI2(line)
1486 lastLine = line
1487 }
1488
1489 //////////////////////////////////////////////////////////////////////////
1490 // Fields
1491 //////////////////////////////////////////////////////////////////////////
1492
1493 Location location
1494 FPod fpod
1495 Buf code
1496 Buf errTable
1497 Int errCount
1498 Buf lines
1499 Int lineCount
1500 Int lastLine := -1
1501 Loop[] loopStack
1502
1503 // protected region fields
1504 ProtectedRegion[] protectedRegions // stack of protection regions
1505 Int[] leavesToReturn // list of Leave positions to backpatch
1506 MethodVar returnLocal // where we stash return value
1507 }
1508
1509 **************************************************************************
1510 ** Loop
1511 **************************************************************************
1512
1513 class Loop
1514 {
1515 new make(Stmt stmt) { this.stmt = stmt }
1516
1517 Stmt stmt // WhileStmt or ForStmt
1518 Int[] breaks := Int[,] // backpatch positions
1519 Int[] continues := Int[,] // backpatch positions
1520 ProtectedRegion[] protectedRegions := ProtectedRegion[,] // stack
1521 }
1522
1523 **************************************************************************
1524 ** ProtectedRegion
1525 **************************************************************************
1526
1527 class ProtectedRegion
1528 {
1529 new make(TryStmt stmt)
1530 {
1531 hasFinally = stmt.finallyBlock != null
1532 if (hasFinally) jumpFinallys = Int[,]
1533 }
1534
1535 Bool hasFinally // does this region have a finally
1536 Int[] jumpFinallys // list of JumpFinally positions to backpatch
1537 }
1538
1539 **************************************************************************
1540 ** Cond
1541 **************************************************************************
1542
1543 class Cond
1544 {
1545 Int[] jumpTrues := Int[,] // backpatch positions
1546 Int[] jumpFalses := Int[,] // backpatch positions
1547 }