
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 }