1# Copyright (C) 2011, 2012 Apple Inc. All rights reserved. 2# Copyright (C) 2013 University of Szeged. All rights reserved. 3# 4# Redistribution and use in source and binary forms, with or without 5# modification, are permitted provided that the following conditions 6# are met: 7# 1. Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# 2. Redistributions in binary form must reproduce the above copyright 10# notice, this list of conditions and the following disclaimer in the 11# documentation and/or other materials provided with the distribution. 12# 13# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23# THE POSSIBILITY OF SUCH DAMAGE. 24 25require "config" 26require "ast" 27require "opt" 28require "risc" 29 30def isARMv7 31 case $activeBackend 32 when "ARMv7" 33 true 34 when "ARMv7_TRADITIONAL", "ARM" 35 false 36 else 37 raise "bad value for $activeBackend: #{$activeBackend}" 38 end 39end 40 41def isARMv7Traditional 42 case $activeBackend 43 when "ARMv7_TRADITIONAL" 44 true 45 when "ARMv7", "ARM" 46 false 47 else 48 raise "bad value for $activeBackend: #{$activeBackend}" 49 end 50end 51 52class Node 53 def armSingle 54 doubleOperand = armOperand 55 raise "Bogus register name #{doubleOperand}" unless doubleOperand =~ /^d/ 56 "s" + ($~.post_match.to_i * 2).to_s 57 end 58end 59 60class SpecialRegister 61 def armOperand 62 @name 63 end 64end 65 66ARM_EXTRA_GPRS = [SpecialRegister.new("r6"), SpecialRegister.new("r10"), SpecialRegister.new("r12")] 67ARM_EXTRA_FPRS = [SpecialRegister.new("d7")] 68ARM_SCRATCH_FPR = SpecialRegister.new("d6") 69 70def armMoveImmediate(value, register) 71 # Currently we only handle the simple cases, and fall back to mov/movt for the complex ones. 72 if value.is_a? String 73 $asm.puts "mov #{register.armOperand}, (#{value})" 74 elsif value >= 0 && value < 256 75 $asm.puts "mov #{register.armOperand}, \##{value}" 76 elsif (~value) >= 0 && (~value) < 256 77 $asm.puts "mvn #{register.armOperand}, \##{~value}" 78 elsif isARMv7 or isARMv7Traditional 79 $asm.puts "movw #{register.armOperand}, \##{value & 0xffff}" 80 if (value & 0xffff0000) != 0 81 $asm.puts "movt #{register.armOperand}, \##{(value >> 16) & 0xffff}" 82 end 83 else 84 $asm.puts "ldr #{register.armOperand}, =#{value}" 85 end 86end 87 88class RegisterID 89 def armOperand 90 case name 91 when "t0", "a0", "r0" 92 "r0" 93 when "t1", "a1", "r1" 94 "r1" 95 when "t2", "a2" 96 "r2" 97 when "a3" 98 "r3" 99 when "t3" 100 "r4" 101 when "t4" 102 "r8" 103 when "t5" 104 "r9" 105 when "cfr" 106 isARMv7 ? "r7" : "r11" 107 when "lr" 108 "lr" 109 when "sp" 110 "sp" 111 when "pc" 112 "pc" 113 else 114 raise "Bad register #{name} for ARM at #{codeOriginString}" 115 end 116 end 117end 118 119class FPRegisterID 120 def armOperand 121 case name 122 when "ft0", "fr" 123 "d0" 124 when "ft1" 125 "d1" 126 when "ft2" 127 "d2" 128 when "ft3" 129 "d3" 130 when "ft4" 131 "d4" 132 when "ft5" 133 "d5" 134 else 135 raise "Bad register #{name} for ARM at #{codeOriginString}" 136 end 137 end 138end 139 140class Immediate 141 def armOperand 142 raise "Invalid immediate #{value} at #{codeOriginString}" if value < 0 or value > 255 143 "\##{value}" 144 end 145end 146 147class Address 148 def armOperand 149 raise "Bad offset at #{codeOriginString}" if offset.value < -0xff or offset.value > 0xfff 150 "[#{base.armOperand}, \##{offset.value}]" 151 end 152end 153 154class BaseIndex 155 def armOperand 156 raise "Bad offset at #{codeOriginString}" if offset.value != 0 157 "[#{base.armOperand}, #{index.armOperand}, lsl \##{scaleShift}]" 158 end 159end 160 161class AbsoluteAddress 162 def armOperand 163 raise "Unconverted absolute address at #{codeOriginString}" 164 end 165end 166 167# 168# Lea support. 169# 170 171class Address 172 def armEmitLea(destination) 173 if destination == base 174 $asm.puts "adds #{destination.armOperand}, \##{offset.value}" 175 else 176 $asm.puts "adds #{destination.armOperand}, #{base.armOperand}, \##{offset.value}" 177 end 178 end 179end 180 181class BaseIndex 182 def armEmitLea(destination) 183 raise "Malformed BaseIndex, offset should be zero at #{codeOriginString}" unless offset.value == 0 184 $asm.puts "add #{destination.armOperand}, #{base.armOperand}, #{index.armOperand}, lsl \##{scaleShift}" 185 end 186end 187 188# FIXME: we could support AbsoluteAddress for lea, but we don't. 189 190# 191# Actual lowering code follows. 192# 193 194class Sequence 195 def getModifiedListARM 196 raise unless $activeBackend == "ARM" 197 getModifiedListARMCommon 198 end 199 200 def getModifiedListARMv7 201 raise unless $activeBackend == "ARMv7" 202 getModifiedListARMCommon 203 end 204 205 def getModifiedListARMv7_TRADITIONAL 206 raise unless $activeBackend == "ARMv7_TRADITIONAL" 207 getModifiedListARMCommon 208 end 209 210 def getModifiedListARMCommon 211 result = @list 212 result = riscLowerSimpleBranchOps(result) 213 result = riscLowerHardBranchOps(result) 214 result = riscLowerShiftOps(result) 215 result = riscLowerMalformedAddresses(result) { 216 | node, address | 217 if address.is_a? BaseIndex 218 address.offset.value == 0 219 elsif address.is_a? Address 220 (-0xff..0xfff).include? address.offset.value 221 else 222 false 223 end 224 } 225 result = riscLowerMalformedAddressesDouble(result) 226 result = riscLowerMisplacedImmediates(result, ["storeb", "storei", "storep", "storeq"]) 227 result = riscLowerMalformedImmediates(result, 0..0xff) 228 result = riscLowerMisplacedAddresses(result) 229 result = riscLowerRegisterReuse(result) 230 result = assignRegistersToTemporaries(result, :gpr, ARM_EXTRA_GPRS) 231 result = assignRegistersToTemporaries(result, :fpr, ARM_EXTRA_FPRS) 232 return result 233 end 234end 235 236def armOperands(operands) 237 operands.map{|v| v.armOperand}.join(", ") 238end 239 240def armFlippedOperands(operands) 241 armOperands([operands[-1]] + operands[0..-2]) 242end 243 244def emitArmCompact(opcode2, opcode3, operands) 245 if operands.size == 3 246 $asm.puts "#{opcode3} #{armFlippedOperands(operands)}" 247 else 248 raise unless operands.size == 2 249 raise unless operands[1].register? 250 if operands[0].immediate? 251 $asm.puts "#{opcode3} #{operands[1].armOperand}, #{operands[1].armOperand}, #{operands[0].armOperand}" 252 else 253 $asm.puts "#{opcode2} #{armFlippedOperands(operands)}" 254 end 255 end 256end 257 258def emitArm(opcode, operands) 259 if operands.size == 3 260 $asm.puts "#{opcode} #{armFlippedOperands(operands)}" 261 else 262 raise unless operands.size == 2 263 $asm.puts "#{opcode} #{operands[1].armOperand}, #{operands[1].armOperand}, #{operands[0].armOperand}" 264 end 265end 266 267def emitArmDoubleBranch(branchOpcode, operands) 268 $asm.puts "vcmpe.f64 #{armOperands(operands[0..1])}" 269 $asm.puts "vmrs apsr_nzcv, fpscr" 270 $asm.puts "#{branchOpcode} #{operands[2].asmLabel}" 271end 272 273def emitArmTest(operands) 274 value = operands[0] 275 case operands.size 276 when 2 277 mask = Immediate.new(codeOrigin, -1) 278 when 3 279 mask = operands[1] 280 else 281 raise "Expected 2 or 3 operands but got #{operands.size} at #{codeOriginString}" 282 end 283 284 if mask.immediate? and mask.value == -1 285 $asm.puts "tst #{value.armOperand}, #{value.armOperand}" 286 else 287 $asm.puts "tst #{value.armOperand}, #{mask.armOperand}" 288 end 289end 290 291def emitArmCompare(operands, code) 292 $asm.puts "movs #{operands[2].armOperand}, \#0" 293 $asm.puts "cmp #{operands[0].armOperand}, #{operands[1].armOperand}" 294 $asm.puts "it #{code}" 295 $asm.puts "mov#{code} #{operands[2].armOperand}, \#1" 296end 297 298def emitArmTestSet(operands, code) 299 $asm.puts "movs #{operands[-1].armOperand}, \#0" 300 emitArmTest(operands) 301 $asm.puts "it #{code}" 302 $asm.puts "mov#{code} #{operands[-1].armOperand}, \#1" 303end 304 305class Instruction 306 def lowerARM 307 raise unless $activeBackend == "ARM" 308 lowerARMCommon 309 end 310 311 def lowerARMv7 312 raise unless $activeBackend == "ARMv7" 313 lowerARMCommon 314 end 315 316 def lowerARMv7_TRADITIONAL 317 raise unless $activeBackend == "ARMv7_TRADITIONAL" 318 lowerARMCommon 319 end 320 321 def lowerARMCommon 322 $asm.codeOrigin codeOriginString if $enableCodeOriginComments 323 $asm.annotation annotation if $enableInstrAnnotations 324 325 case opcode 326 when "addi", "addp", "addis", "addps" 327 if opcode == "addis" or opcode == "addps" 328 suffix = "s" 329 else 330 suffix = "" 331 end 332 if operands.size == 3 and operands[0].immediate? 333 raise unless operands[1].register? 334 raise unless operands[2].register? 335 if operands[0].value == 0 and suffix.empty? 336 unless operands[1] == operands[2] 337 $asm.puts "mov #{operands[2].armOperand}, #{operands[1].armOperand}" 338 end 339 else 340 $asm.puts "adds #{operands[2].armOperand}, #{operands[1].armOperand}, #{operands[0].armOperand}" 341 end 342 elsif operands.size == 3 and operands[0].register? 343 raise unless operands[1].register? 344 raise unless operands[2].register? 345 $asm.puts "adds #{armFlippedOperands(operands)}" 346 else 347 if operands[0].immediate? 348 unless Immediate.new(nil, 0) == operands[0] 349 $asm.puts "adds #{armFlippedOperands(operands)}" 350 end 351 else 352 $asm.puts "add#{suffix} #{armFlippedOperands(operands)}" 353 end 354 end 355 when "andi", "andp" 356 emitArmCompact("ands", "and", operands) 357 when "ori", "orp" 358 emitArmCompact("orrs", "orr", operands) 359 when "oris" 360 emitArmCompact("orrs", "orrs", operands) 361 when "xori", "xorp" 362 emitArmCompact("eors", "eor", operands) 363 when "lshifti", "lshiftp" 364 emitArmCompact("lsls", "lsls", operands) 365 when "rshifti", "rshiftp" 366 emitArmCompact("asrs", "asrs", operands) 367 when "urshifti", "urshiftp" 368 emitArmCompact("lsrs", "lsrs", operands) 369 when "muli", "mulp" 370 emitArm("mul", operands) 371 when "subi", "subp", "subis" 372 emitArmCompact("subs", "subs", operands) 373 when "negi", "negp" 374 $asm.puts "rsbs #{operands[0].armOperand}, #{operands[0].armOperand}, \#0" 375 when "noti" 376 $asm.puts "mvns #{operands[0].armOperand}, #{operands[0].armOperand}" 377 when "loadi", "loadis", "loadp" 378 $asm.puts "ldr #{armFlippedOperands(operands)}" 379 when "storei", "storep" 380 $asm.puts "str #{armOperands(operands)}" 381 when "loadb" 382 $asm.puts "ldrb #{armFlippedOperands(operands)}" 383 when "loadbs" 384 $asm.puts "ldrsb.w #{armFlippedOperands(operands)}" 385 when "storeb" 386 $asm.puts "strb #{armOperands(operands)}" 387 when "loadh" 388 $asm.puts "ldrh #{armFlippedOperands(operands)}" 389 when "loadhs" 390 $asm.puts "ldrsh.w #{armFlippedOperands(operands)}" 391 when "storeh" 392 $asm.puts "strh #{armOperands(operands)}" 393 when "loadd" 394 $asm.puts "vldr.64 #{armFlippedOperands(operands)}" 395 when "stored" 396 $asm.puts "vstr.64 #{armOperands(operands)}" 397 when "addd" 398 emitArm("vadd.f64", operands) 399 when "divd" 400 emitArm("vdiv.f64", operands) 401 when "subd" 402 emitArm("vsub.f64", operands) 403 when "muld" 404 emitArm("vmul.f64", operands) 405 when "sqrtd" 406 $asm.puts "vsqrt.f64 #{armFlippedOperands(operands)}" 407 when "ci2d" 408 $asm.puts "vmov #{operands[1].armSingle}, #{operands[0].armOperand}" 409 $asm.puts "vcvt.f64.s32 #{operands[1].armOperand}, #{operands[1].armSingle}" 410 when "bdeq" 411 emitArmDoubleBranch("beq", operands) 412 when "bdneq" 413 $asm.puts "vcmpe.f64 #{armOperands(operands[0..1])}" 414 $asm.puts "vmrs apsr_nzcv, fpscr" 415 isUnordered = LocalLabel.unique("bdneq") 416 $asm.puts "bvs #{LocalLabelReference.new(codeOrigin, isUnordered).asmLabel}" 417 $asm.puts "bne #{operands[2].asmLabel}" 418 isUnordered.lower("ARM") 419 when "bdgt" 420 emitArmDoubleBranch("bgt", operands) 421 when "bdgteq" 422 emitArmDoubleBranch("bge", operands) 423 when "bdlt" 424 emitArmDoubleBranch("bmi", operands) 425 when "bdlteq" 426 emitArmDoubleBranch("bls", operands) 427 when "bdequn" 428 $asm.puts "vcmpe.f64 #{armOperands(operands[0..1])}" 429 $asm.puts "vmrs apsr_nzcv, fpscr" 430 $asm.puts "bvs #{operands[2].asmLabel}" 431 $asm.puts "beq #{operands[2].asmLabel}" 432 when "bdnequn" 433 emitArmDoubleBranch("bne", operands) 434 when "bdgtun" 435 emitArmDoubleBranch("bhi", operands) 436 when "bdgtequn" 437 emitArmDoubleBranch("bpl", operands) 438 when "bdltun" 439 emitArmDoubleBranch("blt", operands) 440 when "bdltequn" 441 emitArmDoubleBranch("ble", operands) 442 when "btd2i" 443 # FIXME: may be a good idea to just get rid of this instruction, since the interpreter 444 # currently does not use it. 445 raise "ARM does not support this opcode yet, #{codeOrigin}" 446 when "td2i" 447 $asm.puts "vcvt.s32.f64 #{ARM_SCRATCH_FPR.armSingle}, #{operands[0].armOperand}" 448 $asm.puts "vmov #{operands[1].armOperand}, #{ARM_SCRATCH_FPR.armSingle}" 449 when "bcd2i" 450 $asm.puts "vcvt.s32.f64 #{ARM_SCRATCH_FPR.armSingle}, #{operands[0].armOperand}" 451 $asm.puts "vmov #{operands[1].armOperand}, #{ARM_SCRATCH_FPR.armSingle}" 452 $asm.puts "vcvt.f64.s32 #{ARM_SCRATCH_FPR.armOperand}, #{ARM_SCRATCH_FPR.armSingle}" 453 emitArmDoubleBranch("bne", [ARM_SCRATCH_FPR, operands[0], operands[2]]) 454 $asm.puts "tst #{operands[1].armOperand}, #{operands[1].armOperand}" 455 $asm.puts "beq #{operands[2].asmLabel}" 456 when "movdz" 457 # FIXME: either support this or remove it. 458 raise "ARM does not support this opcode yet, #{codeOrigin}" 459 when "pop" 460 operands.each { 461 | op | 462 $asm.puts "pop { #{op.armOperand} }" 463 } 464 when "push" 465 operands.each { 466 | op | 467 $asm.puts "push { #{op.armOperand} }" 468 } 469 when "popCalleeSaves" 470 if isARMv7 471 $asm.puts "pop {r4-r6, r8-r11}" 472 else 473 $asm.puts "pop {r4-r10}" 474 end 475 when "pushCalleeSaves" 476 if isARMv7 477 $asm.puts "push {r4-r6, r8-r11}" 478 else 479 $asm.puts "push {r4-r10}" 480 end 481 when "move" 482 if operands[0].immediate? 483 armMoveImmediate(operands[0].value, operands[1]) 484 else 485 $asm.puts "mov #{armFlippedOperands(operands)}" 486 end 487 when "mvlbl" 488 $asm.puts "movw #{operands[1].armOperand}, \#:lower16:#{operands[0].value}" 489 $asm.puts "movt #{operands[1].armOperand}, \#:upper16:#{operands[0].value}" 490 when "nop" 491 $asm.puts "nop" 492 when "bieq", "bpeq", "bbeq" 493 if Immediate.new(nil, 0) == operands[0] 494 $asm.puts "tst #{operands[1].armOperand}, #{operands[1].armOperand}" 495 elsif Immediate.new(nil, 0) == operands[1] 496 $asm.puts "tst #{operands[0].armOperand}, #{operands[0].armOperand}" 497 else 498 $asm.puts "cmp #{armOperands(operands[0..1])}" 499 end 500 $asm.puts "beq #{operands[2].asmLabel}" 501 when "bineq", "bpneq", "bbneq" 502 if Immediate.new(nil, 0) == operands[0] 503 $asm.puts "tst #{operands[1].armOperand}, #{operands[1].armOperand}" 504 elsif Immediate.new(nil, 0) == operands[1] 505 $asm.puts "tst #{operands[0].armOperand}, #{operands[0].armOperand}" 506 else 507 $asm.puts "cmp #{armOperands(operands[0..1])}" 508 end 509 $asm.puts "bne #{operands[2].asmLabel}" 510 when "bia", "bpa", "bba" 511 $asm.puts "cmp #{armOperands(operands[0..1])}" 512 $asm.puts "bhi #{operands[2].asmLabel}" 513 when "biaeq", "bpaeq", "bbaeq" 514 $asm.puts "cmp #{armOperands(operands[0..1])}" 515 $asm.puts "bhs #{operands[2].asmLabel}" 516 when "bib", "bpb", "bbb" 517 $asm.puts "cmp #{armOperands(operands[0..1])}" 518 $asm.puts "blo #{operands[2].asmLabel}" 519 when "bibeq", "bpbeq", "bbbeq" 520 $asm.puts "cmp #{armOperands(operands[0..1])}" 521 $asm.puts "bls #{operands[2].asmLabel}" 522 when "bigt", "bpgt", "bbgt" 523 $asm.puts "cmp #{armOperands(operands[0..1])}" 524 $asm.puts "bgt #{operands[2].asmLabel}" 525 when "bigteq", "bpgteq", "bbgteq" 526 $asm.puts "cmp #{armOperands(operands[0..1])}" 527 $asm.puts "bge #{operands[2].asmLabel}" 528 when "bilt", "bplt", "bblt" 529 $asm.puts "cmp #{armOperands(operands[0..1])}" 530 $asm.puts "blt #{operands[2].asmLabel}" 531 when "bilteq", "bplteq", "bblteq" 532 $asm.puts "cmp #{armOperands(operands[0..1])}" 533 $asm.puts "ble #{operands[2].asmLabel}" 534 when "btiz", "btpz", "btbz" 535 emitArmTest(operands) 536 $asm.puts "beq #{operands[-1].asmLabel}" 537 when "btinz", "btpnz", "btbnz" 538 emitArmTest(operands) 539 $asm.puts "bne #{operands[-1].asmLabel}" 540 when "btis", "btps", "btbs" 541 emitArmTest(operands) 542 $asm.puts "bmi #{operands[-1].asmLabel}" 543 when "jmp" 544 if operands[0].label? 545 $asm.puts "b #{operands[0].asmLabel}" 546 else 547 $asm.puts "mov pc, #{operands[0].armOperand}" 548 end 549 if not isARMv7 and not isARMv7Traditional 550 $asm.puts ".ltorg" 551 end 552 when "call" 553 if operands[0].label? 554 $asm.puts "blx #{operands[0].asmLabel}" 555 else 556 $asm.puts "blx #{operands[0].armOperand}" 557 end 558 when "break" 559 $asm.puts "bkpt #0" 560 when "ret" 561 $asm.puts "bx lr" 562 when "cieq", "cpeq", "cbeq" 563 emitArmCompare(operands, "eq") 564 when "cineq", "cpneq", "cbneq" 565 emitArmCompare(operands, "ne") 566 when "cia", "cpa", "cba" 567 emitArmCompare(operands, "hi") 568 when "ciaeq", "cpaeq", "cbaeq" 569 emitArmCompare(operands, "hs") 570 when "cib", "cpb", "cbb" 571 emitArmCompare(operands, "lo") 572 when "cibeq", "cpbeq", "cbbeq" 573 emitArmCompare(operands, "ls") 574 when "cigt", "cpgt", "cbgt" 575 emitArmCompare(operands, "gt") 576 when "cigteq", "cpgteq", "cbgteq" 577 emitArmCompare(operands, "ge") 578 when "cilt", "cplt", "cblt" 579 emitArmCompare(operands, "lt") 580 when "cilteq", "cplteq", "cblteq" 581 emitArmCompare(operands, "le") 582 when "tis", "tbs", "tps" 583 emitArmTestSet(operands, "mi") 584 when "tiz", "tbz", "tpz" 585 emitArmTestSet(operands, "eq") 586 when "tinz", "tbnz", "tpnz" 587 emitArmTestSet(operands, "ne") 588 when "peek" 589 $asm.puts "ldr #{operands[1].armOperand}, [sp, \##{operands[0].value * 4}]" 590 when "poke" 591 $asm.puts "str #{operands[1].armOperand}, [sp, \##{operands[0].value * 4}]" 592 when "fii2d" 593 $asm.puts "vmov #{operands[2].armOperand}, #{operands[0].armOperand}, #{operands[1].armOperand}" 594 when "fd2ii" 595 $asm.puts "vmov #{operands[1].armOperand}, #{operands[2].armOperand}, #{operands[0].armOperand}" 596 when "bo" 597 $asm.puts "bvs #{operands[0].asmLabel}" 598 when "bs" 599 $asm.puts "bmi #{operands[0].asmLabel}" 600 when "bz" 601 $asm.puts "beq #{operands[0].asmLabel}" 602 when "bnz" 603 $asm.puts "bne #{operands[0].asmLabel}" 604 when "leai", "leap" 605 operands[0].armEmitLea(operands[1]) 606 when "smulli" 607 raise "Wrong number of arguments to smull in #{self.inspect} at #{codeOriginString}" unless operands.length == 4 608 $asm.puts "smull #{operands[2].armOperand}, #{operands[3].armOperand}, #{operands[0].armOperand}, #{operands[1].armOperand}" 609 when "memfence" 610 $asm.puts "dmb sy" 611 when "clrbp" 612 $asm.puts "bic #{operands[2].armOperand}, #{operands[0].armOperand}, #{operands[1].armOperand}" 613 else 614 lowerDefault 615 end 616 end 617end 618 619