diff options
author | Simon Hausmann <simon.hausmann@nokia.com> | 2012-02-24 16:36:50 +0100 |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2012-02-24 16:36:50 +0100 |
commit | ad0d549d4cc13433f77c1ac8f0ab379c83d93f28 (patch) | |
tree | b34b0daceb7c8e7fdde4b4ec43650ab7caadb0a9 /Source/JavaScriptCore/offlineasm | |
parent | 03e12282df9aa1e1fb05a8b90f1cfc2e08764cec (diff) | |
download | qtwebkit-ad0d549d4cc13433f77c1ac8f0ab379c83d93f28.tar.gz |
Imported WebKit commit bb52bf3c0119e8a128cd93afe5572413a8617de9 (http://svn.webkit.org/repository/webkit/trunk@108790)
Diffstat (limited to 'Source/JavaScriptCore/offlineasm')
-rw-r--r-- | Source/JavaScriptCore/offlineasm/armv7.rb | 1032 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/asm.rb | 176 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/ast.rb | 1039 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/backends.rb | 96 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/generate_offset_extractor.rb | 146 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/instructions.rb | 217 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/offsets.rb | 173 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/opt.rb | 134 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/parser.rb | 586 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/registers.rb | 60 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/self_hash.rb | 46 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/settings.rb | 205 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/transform.rb | 342 | ||||
-rw-r--r-- | Source/JavaScriptCore/offlineasm/x86.rb | 681 |
14 files changed, 4933 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/offlineasm/armv7.rb b/Source/JavaScriptCore/offlineasm/armv7.rb new file mode 100644 index 000000000..eb8df6869 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/armv7.rb @@ -0,0 +1,1032 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "ast" +require "opt" + +class Node + def armV7Single + doubleOperand = armV7Operand + raise "Bogus register name #{doubleOperand}" unless doubleOperand =~ /^d/ + "s" + ($~.post_match.to_i * 2).to_s + end +end + +class SpecialRegister < NoChildren + def initialize(name) + @name = name + end + + def armV7Operand + @name + end + + def address? + false + end + + def label? + false + end + + def immediate? + false + end + + def register? + true + end +end + +ARMv7_EXTRA_GPRS = [SpecialRegister.new("r9"), SpecialRegister.new("r8"), SpecialRegister.new("r3")] +ARMv7_EXTRA_FPRS = [SpecialRegister.new("d7")] +ARMv7_SCRATCH_FPR = SpecialRegister.new("d8") + +def armV7MoveImmediate(value, register) + # Currently we only handle the simple cases, and fall back to mov/movt for the complex ones. + if value >= 0 && value < 256 + $asm.puts "movw #{register.armV7Operand}, \##{value}" + elsif (~value) >= 0 && (~value) < 256 + $asm.puts "mvn #{register.armV7Operand}, \##{~value}" + else + $asm.puts "movw #{register.armV7Operand}, \##{value & 0xffff}" + if (value & 0xffff0000) != 0 + $asm.puts "movt #{register.armV7Operand}, \##{value >> 16}" + end + end +end + +class RegisterID + def armV7Operand + case name + when "t0", "a0", "r0" + "r0" + when "t1", "a1", "r1" + "r1" + when "t2", "a2" + "r2" + when "a3" + "r3" + when "t3" + "r4" + when "t4" + "r7" + when "cfr" + "r5" + when "lr" + "lr" + when "sp" + "sp" + else + raise "Bad register #{name} for ARMv7 at #{codeOriginString}" + end + end +end + +class FPRegisterID + def armV7Operand + case name + when "ft0", "fr" + "d0" + when "ft1" + "d1" + when "ft2" + "d2" + when "ft3" + "d3" + when "ft4" + "d4" + when "ft5" + "d5" + else + raise "Bad register #{name} for ARMv7 at #{codeOriginString}" + end + end +end + +class Immediate + def armV7Operand + raise "Invalid immediate #{value} at #{codeOriginString}" if value < 0 or value > 255 + "\##{value}" + end +end + +class Address + def armV7Operand + raise "Bad offset at #{codeOriginString}" if offset.value < -0xff or offset.value > 0xfff + "[#{base.armV7Operand}, \##{offset.value}]" + end +end + +class BaseIndex + def armV7Operand + raise "Bad offset at #{codeOriginString}" if offset.value != 0 + "[#{base.armV7Operand}, #{index.armV7Operand}, lsl \##{scaleShift}]" + end +end + +class AbsoluteAddress + def armV7Operand + raise "Unconverted absolute address at #{codeOriginString}" + end +end + +# +# Lowering of branch ops. For example: +# +# baddiz foo, bar, baz +# +# will become: +# +# addi foo, bar +# bz baz +# + +def armV7LowerBranchOps(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + case node.opcode + when /^b(addi|subi|ori|addp)/ + op = $1 + branch = "b" + $~.post_match + + case op + when "addi", "addp" + op = "addis" + when "subi" + op = "subis" + when "ori" + op = "oris" + end + + newList << Instruction.new(node.codeOrigin, op, node.operands[0..-2]) + newList << Instruction.new(node.codeOrigin, branch, [node.operands[-1]]) + when "bmulio" + tmp1 = Tmp.new(node.codeOrigin, :gpr) + tmp2 = Tmp.new(node.codeOrigin, :gpr) + newList << Instruction.new(node.codeOrigin, "smulli", [node.operands[0], node.operands[1], node.operands[1], tmp1]) + newList << Instruction.new(node.codeOrigin, "rshifti", [node.operands[-2], Immediate.new(node.codeOrigin, 31), tmp2]) + newList << Instruction.new(node.codeOrigin, "bineq", [tmp1, tmp2, node.operands[-1]]) + when /^bmuli/ + condition = $~.post_match + newList << Instruction.new(node.codeOrigin, "muli", node.operands[0..-2]) + newList << Instruction.new(node.codeOrigin, "bti" + condition, [node.operands[-2], node.operands[-1]]) + else + newList << node + end + else + newList << node + end + } + newList +end + +# +# Lowering of shift ops. For example: +# +# lshifti foo, bar +# +# will become: +# +# andi foo, 31, tmp +# lshifti tmp, bar +# + +def armV7SanitizeShift(operand, list) + return operand if operand.immediate? + + tmp = Tmp.new(operand.codeOrigin, :gpr) + list << Instruction.new(operand.codeOrigin, "andi", [operand, Immediate.new(operand.codeOrigin, 31), tmp]) + tmp +end + +def armV7LowerShiftOps(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + case node.opcode + when "lshifti", "rshifti", "urshifti" + if node.operands.size == 2 + newList << Instruction.new(node.codeOrigin, node.opcode, [armV7SanitizeShift(node.operands[0], newList), node.operands[1]]) + else + newList << Instruction.new(node.codeOrigin, node.opcode, [node.operands[0], armV7SanitizeShift(node.operands[1], newList), node.operands[2]]) + raise "Wrong number of operands for shift at #{node.codeOriginString}" unless node.operands.size == 3 + end + else + newList << node + end + else + newList << node + end + } + newList +end + +# +# Lowering of malformed addresses. For example: +# +# loadp 10000[foo], bar +# +# will become: +# +# move 10000, tmp +# addp foo, tmp +# loadp 0[tmp], bar +# + +class Node + def armV7LowerMalformedAddressesRecurse(list) + mapChildren { + | node | + node.armV7LowerMalformedAddressesRecurse(list) + } + end +end + +class Address + def armV7LowerMalformedAddressesRecurse(list) + if offset.value < -0xff or offset.value > 0xfff + tmp = Tmp.new(codeOrigin, :gpr) + list << Instruction.new(codeOrigin, "move", [offset, tmp]) + list << Instruction.new(codeOrigin, "addp", [base, tmp]) + Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, 0)) + else + self + end + end +end + +class BaseIndex + def armV7LowerMalformedAddressesRecurse(list) + if offset.value != 0 + tmp = Tmp.new(codeOrigin, :gpr) + list << Instruction.new(codeOrigin, "move", [offset, tmp]) + list << Instruction.new(codeOrigin, "addp", [base, tmp]) + BaseIndex.new(codeOrigin, tmp, index, scale, Immediate.new(codeOrigin, 0)) + else + self + end + end +end + +class AbsoluteAddress + def armV7LowerMalformedAddressesRecurse(list) + tmp = Tmp.new(codeOrigin, :gpr) + list << Instruction.new(codeOrigin, "move", [address, tmp]) + Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, 0)) + end +end + +def armV7LowerMalformedAddresses(list) + newList = [] + list.each { + | node | + newList << node.armV7LowerMalformedAddressesRecurse(newList) + } + newList +end + +# +# Lowering of malformed addresses in double loads and stores. For example: +# +# loadd [foo, bar, 8], baz +# +# becomes: +# +# leap [foo, bar, 8], tmp +# loadd [tmp], baz +# + +class Node + def armV7DoubleAddress(list) + self + end +end + +class BaseIndex + def armV7DoubleAddress(list) + tmp = Tmp.new(codeOrigin, :gpr) + list << Instruction.new(codeOrigin, "leap", [self, tmp]) + Address.new(codeOrigin, tmp, Immediate.new(codeOrigin, 0)) + end +end + +def armV7LowerMalformedAddressesDouble(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + case node.opcode + when "loadd" + newList << Instruction.new(node.codeOrigin, "loadd", [node.operands[0].armV7DoubleAddress(newList), node.operands[1]]) + when "stored" + newList << Instruction.new(node.codeOrigin, "stored", [node.operands[0], node.operands[1].armV7DoubleAddress(newList)]) + else + newList << node + end + else + newList << node + end + } + newList +end + +# +# Lowering of misplaced immediates. For example: +# +# storei 0, [foo] +# +# will become: +# +# move 0, tmp +# storei tmp, [foo] +# + +def armV7LowerMisplacedImmediates(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + case node.opcode + when "storei", "storep" + operands = node.operands + newOperands = [] + operands.each { + | operand | + if operand.is_a? Immediate + tmp = Tmp.new(operand.codeOrigin, :gpr) + newList << Instruction.new(operand.codeOrigin, "move", [operand, tmp]) + newOperands << tmp + else + newOperands << operand + end + } + newList << Instruction.new(node.codeOrigin, node.opcode, newOperands) + else + newList << node + end + else + newList << node + end + } + newList +end + +# +# Lowering of malformed immediates except when used in a "move" instruction. +# For example: +# +# addp 642641, foo +# +# will become: +# +# move 642641, tmp +# addp tmp, foo +# + +class Node + def armV7LowerMalformedImmediatesRecurse(list) + mapChildren { + | node | + node.armV7LowerMalformedImmediatesRecurse(list) + } + end +end + +class Address + def armV7LowerMalformedImmediatesRecurse(list) + self + end +end + +class BaseIndex + def armV7LowerMalformedImmediatesRecurse(list) + self + end +end + +class AbsoluteAddress + def armV7LowerMalformedImmediatesRecurse(list) + self + end +end + +class Immediate + def armV7LowerMalformedImmediatesRecurse(list) + if value < 0 or value > 255 + tmp = Tmp.new(codeOrigin, :gpr) + list << Instruction.new(codeOrigin, "move", [self, tmp]) + tmp + else + self + end + end +end + +def armV7LowerMalformedImmediates(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + case node.opcode + when "move" + newList << node + when "addi", "addp", "addis", "subi", "subp", "subis" + if node.operands[0].is_a? Immediate and + node.operands[0].value < 0 and + node.operands[0].value >= 255 and + node.operands.size == 2 + if node.opcode =~ /add/ + newOpcode = "sub" + node.opcode[-1..-1] + else + newOpcode = "add" + node.opcode[-1..-1] + end + newList << Instruction.new(node.codeOrigin, newOpcode, + [Immediate.new(-node.operands[0].value)] + node.operands[1..-1]) + else + newList << node.armV7LowerMalformedImmediatesRecurse(newList) + end + when "muli" + if node.operands[0].is_a? Immediate + tmp = Tmp.new(codeOrigin, :gpr) + newList << Instruction.new(node.codeOrigin, "move", [node.operands[0], tmp]) + newList << Instruction.new(node.codeOrigin, "muli", [tmp] + node.operands[1..-1]) + else + newList << node.armV7LowerMalformedImmediatesRecurse(newList) + end + else + newList << node.armV7LowerMalformedImmediatesRecurse(newList) + end + else + newList << node + end + } + newList +end + +# +# Lowering of misplaced addresses. For example: +# +# addi foo, [bar] +# +# will become: +# +# loadi [bar], tmp +# addi foo, tmp +# storei tmp, [bar] +# +# Another example: +# +# addi [foo], bar +# +# will become: +# +# loadi [foo], tmp +# addi tmp, bar +# + +def armV7AsRegister(preList, postList, operand, suffix, needStore) + return operand unless operand.address? + + tmp = Tmp.new(operand.codeOrigin, if suffix == "d" then :fpr else :gpr end) + preList << Instruction.new(operand.codeOrigin, "load" + suffix, [operand, tmp]) + if needStore + postList << Instruction.new(operand.codeOrigin, "store" + suffix, [tmp, operand]) + end + tmp +end + +def armV7AsRegisters(preList, postList, operands, suffix) + newOperands = [] + operands.each_with_index { + | operand, index | + newOperands << armV7AsRegister(preList, postList, operand, suffix, index == operands.size - 1) + } + newOperands +end + +def armV7LowerMisplacedAddresses(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + postInstructions = [] + case node.opcode + when "addi", "addp", "addis", "andi", "andp", "lshifti", "muli", "negi", "noti", "ori", "oris", + "orp", "rshifti", "urshifti", "subi", "subp", "subis", "xori", "xorp", /^bi/, /^bp/, /^bti/, + /^btp/, /^ci/, /^cp/, /^ti/ + newList << Instruction.new(node.codeOrigin, + node.opcode, + armV7AsRegisters(newList, postInstructions, node.operands, "i")) + when "bbeq", "bbneq", "bba", "bbaeq", "bbb", "bbbeq", "btbo", "btbz", "btbnz", "tbz", "tbnz", + "tbo" + newList << Instruction.new(node.codeOrigin, + node.opcode, + armV7AsRegisters(newList, postInstructions, node.operands, "b")) + when "bbgt", "bbgteq", "bblt", "bblteq", "btbs", "tbs" + newList << Instruction.new(node.codeOrigin, + node.opcode, + armV7AsRegisters(newList, postInstructions, node.operands, "bs")) + when "addd", "divd", "subd", "muld", "sqrtd", /^bd/ + newList << Instruction.new(node.codeOrigin, + node.opcode, + armV7AsRegisters(newList, postInstructions, node.operands, "d")) + when "jmp", "call" + newList << Instruction.new(node.codeOrigin, + node.opcode, + [armV7AsRegister(newList, postInstructions, node.operands[0], "p", false)]) + else + newList << node + end + newList += postInstructions + else + newList << node + end + } + newList +end + +# +# Lowering of register reuse in compare instructions. For example: +# +# cieq t0, t1, t0 +# +# will become: +# +# mov tmp, t0 +# cieq tmp, t1, t0 +# + +def armV7LowerRegisterReuse(list) + newList = [] + list.each { + | node | + if node.is_a? Instruction + case node.opcode + when "cieq", "cineq", "cia", "ciaeq", "cib", "cibeq", "cigt", "cigteq", "cilt", "cilteq", + "cpeq", "cpneq", "cpa", "cpaeq", "cpb", "cpbeq", "cpgt", "cpgteq", "cplt", "cplteq", + "tio", "tis", "tiz", "tinz", "tbo", "tbs", "tbz", "tbnz" + if node.operands.size == 2 + if node.operands[0] == node.operands[1] + tmp = Tmp.new(node.codeOrigin, :gpr) + newList << Instruction.new(node.codeOrigin, "move", [node.operands[0], tmp]) + newList << Instruction.new(node.codeOrigin, node.opcode, [tmp, node.operands[1]]) + else + newList << node + end + else + raise "Wrong number of arguments at #{node.codeOriginString}" unless node.operands.size == 3 + if node.operands[0] == node.operands[2] + tmp = Tmp.new(node.codeOrigin, :gpr) + newList << Instruction.new(node.codeOrigin, "move", [node.operands[0], tmp]) + newList << Instruction.new(node.codeOrigin, node.opcode, [tmp, node.operands[1], node.operands[2]]) + elsif node.operands[1] == node.operands[2] + tmp = Tmp.new(node.codeOrigin, :gpr) + newList << Instruction.new(node.codeOrigin, "move", [node.operands[1], tmp]) + newList << Instruction.new(node.codeOrigin, node.opcode, [node.operands[0], tmp, node.operands[2]]) + else + newList << node + end + end + else + newList << node + end + else + newList << node + end + } + newList +end + +# +# Lea support. +# + +class Address + def armV7EmitLea(destination) + if destination == base + $asm.puts "adds #{destination.armV7Operand}, \##{offset.value}" + else + $asm.puts "adds #{destination.armV7Operand}, #{base.armV7Operand}, \##{offset.value}" + end + end +end + +class BaseIndex + def armV7EmitLea(destination) + raise "Malformed BaseIndex, offset should be zero at #{codeOriginString}" unless offset.value == 0 + $asm.puts "add.w #{destination.armV7Operand}, #{base.armV7Operand}, #{index.armV7Operand}, lsl \##{scaleShift}" + end +end + +# FIXME: we could support AbsoluteAddress for lea, but we don't. + +# +# Actual lowering code follows. +# + +class Sequence + def lowerARMv7 + myList = @list + + # Verify that we will only see instructions and labels. + myList.each { + | node | + unless node.is_a? Instruction or + node.is_a? Label or + node.is_a? LocalLabel or + node.is_a? Skip + raise "Unexpected #{node.inspect} at #{node.codeOrigin}" + end + } + + myList = armV7LowerBranchOps(myList) + myList = armV7LowerShiftOps(myList) + myList = armV7LowerMalformedAddresses(myList) + myList = armV7LowerMalformedAddressesDouble(myList) + myList = armV7LowerMisplacedImmediates(myList) + myList = armV7LowerMalformedImmediates(myList) + myList = armV7LowerMisplacedAddresses(myList) + myList = armV7LowerRegisterReuse(myList) + myList = assignRegistersToTemporaries(myList, :gpr, ARMv7_EXTRA_GPRS) + myList = assignRegistersToTemporaries(myList, :fpr, ARMv7_EXTRA_FPRS) + myList.each { + | node | + node.lower("ARMv7") + } + end +end + +def armV7Operands(operands) + operands.map{|v| v.armV7Operand}.join(", ") +end + +def armV7FlippedOperands(operands) + armV7Operands([operands[-1]] + operands[0..-2]) +end + +def emitArmV7Compact(opcode2, opcode3, operands) + if operands.size == 3 + $asm.puts "#{opcode3} #{armV7FlippedOperands(operands)}" + else + raise unless operands.size == 2 + raise unless operands[1].is_a? RegisterID + if operands[0].is_a? Immediate + $asm.puts "#{opcode3} #{operands[1].armV7Operand}, #{operands[1].armV7Operand}, #{operands[0].armV7Operand}" + else + $asm.puts "#{opcode2} #{armV7FlippedOperands(operands)}" + end + end +end + +def emitArmV7(opcode, operands) + if operands.size == 3 + $asm.puts "#{opcode} #{armV7FlippedOperands(operands)}" + else + raise unless operands.size == 2 + $asm.puts "#{opcode} #{operands[1].armV7Operand}, #{operands[1].armV7Operand}, #{operands[0].armV7Operand}" + end +end + +def emitArmV7DoubleBranch(branchOpcode, operands) + $asm.puts "vcmpe.f64 #{armV7Operands(operands[0..1])}" + $asm.puts "vmrs apsr_nzcv, fpscr" + $asm.puts "#{branchOpcode} #{operands[2].asmLabel}" +end + +def emitArmV7Test(operands) + value = operands[0] + case operands.size + when 2 + mask = Immediate.new(codeOrigin, -1) + when 3 + mask = operands[1] + else + raise "Expected 2 or 3 operands but got #{operands.size} at #{codeOriginString}" + end + + if mask.is_a? Immediate and mask.value == -1 + $asm.puts "tst #{value.armV7Operand}, #{value.armV7Operand}" + elsif mask.is_a? Immediate + $asm.puts "tst.w #{value.armV7Operand}, #{mask.armV7Operand}" + else + $asm.puts "tst #{value.armV7Operand}, #{mask.armV7Operand}" + end +end + +def emitArmV7Compare(operands, code) + $asm.puts "movs #{operands[2].armV7Operand}, \#0" + $asm.puts "cmp #{operands[0].armV7Operand}, #{operands[1].armV7Operand}" + $asm.puts "it #{code}" + $asm.puts "mov#{code} #{operands[2].armV7Operand}, \#1" +end + +def emitArmV7TestSet(operands, code) + $asm.puts "movs #{operands[-1].armV7Operand}, \#0" + emitArmV7Test(operands) + $asm.puts "it #{code}" + $asm.puts "mov#{code} #{operands[-1].armV7Operand}, \#1" +end + +class Instruction + def lowerARMv7 + $asm.comment codeOriginString + case opcode + when "addi", "addp", "addis" + if opcode == "addis" + suffix = "s" + else + suffix = "" + end + if operands.size == 3 and operands[0].is_a? Immediate + raise unless operands[1].is_a? RegisterID + raise unless operands[2].is_a? RegisterID + if operands[0].value == 0 and suffix.empty? + unless operands[1] == operands[2] + $asm.puts "mov #{operands[2].armV7Operand}, #{operands[1].armV7Operand}" + end + else + $asm.puts "adds #{operands[2].armV7Operand}, #{operands[1].armV7Operand}, #{operands[0].armV7Operand}" + end + elsif operands.size == 3 and operands[0].is_a? RegisterID + raise unless operands[1].is_a? RegisterID + raise unless operands[2].is_a? RegisterID + $asm.puts "adds #{armV7FlippedOperands(operands)}" + else + if operands[0].is_a? Immediate + unless Immediate.new(nil, 0) == operands[0] + $asm.puts "adds #{armV7FlippedOperands(operands)}" + end + else + $asm.puts "add#{suffix} #{armV7FlippedOperands(operands)}" + end + end + when "andi", "andp" + emitArmV7Compact("ands", "and", operands) + when "ori", "orp" + emitArmV7Compact("orrs", "orr", operands) + when "oris" + emitArmV7Compact("orrs", "orrs", operands) + when "xori", "xorp" + emitArmV7Compact("eors", "eor", operands) + when "lshifti" + emitArmV7Compact("lsls", "lsls", operands) + when "rshifti" + emitArmV7Compact("asrs", "asrs", operands) + when "urshifti" + emitArmV7Compact("lsrs", "lsrs", operands) + when "muli" + if operands.size == 2 or operands[0] == operands[2] or operands[1] == operands[2] + emitArmV7("muls", operands) + else + $asm.puts "mov #{operands[2].armV7Operand}, #{operands[0].armV7Operand}" + $asm.puts "muls #{operands[2].armV7Operand}, #{operands[2].armV7Operand}, #{operands[1].armV7Operand}" + end + when "subi", "subp", "subis" + emitArmV7Compact("subs", "subs", operands) + when "negi" + $asm.puts "rsbs #{operands[0].armV7Operand}, #{operands[0].armV7Operand}, \#0" + when "noti" + $asm.puts "mvns #{operands[0].armV7Operand}, #{operands[0].armV7Operand}" + when "loadi", "loadp" + $asm.puts "ldr #{armV7FlippedOperands(operands)}" + when "storei", "storep" + $asm.puts "str #{armV7Operands(operands)}" + when "loadb" + $asm.puts "ldrb #{armV7FlippedOperands(operands)}" + when "loadbs" + $asm.puts "ldrsb.w #{armV7FlippedOperands(operands)}" + when "storeb" + $asm.puts "strb #{armV7Operands(operands)}" + when "loadh" + $asm.puts "ldrh #{armV7FlippedOperands(operands)}" + when "loadhs" + $asm.puts "ldrsh.w #{armV7FlippedOperands(operands)}" + when "storeh" + $asm.puts "strh #{armV7Operands(operands)}" + when "loadd" + $asm.puts "vldr.64 #{armV7FlippedOperands(operands)}" + when "stored" + $asm.puts "vstr.64 #{armV7Operands(operands)}" + when "addd" + emitArmV7("vadd.f64", operands) + when "divd" + emitArmV7("vdiv.f64", operands) + when "subd" + emitArmV7("vsub.f64", operands) + when "muld" + emitArmV7("vmul.f64", operands) + when "sqrtd" + $asm.puts "vsqrt.f64 #{armV7FlippedOperands(operands)}" + when "ci2d" + $asm.puts "vmov #{operands[1].armV7Single}, #{operands[0].armV7Operand}" + $asm.puts "vcvt.f64.s32 #{operands[1].armV7Operand}, #{operands[1].armV7Single}" + when "bdeq" + emitArmV7DoubleBranch("beq", operands) + when "bdneq" + $asm.puts "vcmpe.f64 #{armV7Operands(operands[0..1])}" + $asm.puts "vmrs apsr_nzcv, fpscr" + isUnordered = LocalLabel.unique("bdneq") + $asm.puts "bvs #{LabelReference.new(codeOrigin, isUnordered).asmLabel}" + $asm.puts "bne #{operands[2].asmLabel}" + isUnordered.lower("ARMv7") + when "bdgt" + emitArmV7DoubleBranch("bgt", operands) + when "bdgteq" + emitArmV7DoubleBranch("bge", operands) + when "bdlt" + emitArmV7DoubleBranch("bmi", operands) + when "bdlteq" + emitArmV7DoubleBranch("bls", operands) + when "bdequn" + $asm.puts "vcmpe.f64 #{armV7Operands(operands[0..1])}" + $asm.puts "vmrs apsr_nzcv, fpscr" + $asm.puts "bvs #{operands[2].asmLabel}" + $asm.puts "beq #{operands[2].asmLabel}" + when "bdnequn" + emitArmV7DoubleBranch("bne", operands) + when "bdgtun" + emitArmV7DoubleBranch("bhi", operands) + when "bdgtequn" + emitArmV7DoubleBranch("bpl", operands) + when "bdltun" + emitArmV7DoubleBranch("blt", operands) + when "bdltequn" + emitArmV7DoubleBranch("ble", operands) + when "btd2i" + # FIXME: may be a good idea to just get rid of this instruction, since the interpreter + # currently does not use it. + raise "ARMv7 does not support this opcode yet, #{codeOrigin}" + when "td2i" + $asm.puts "vcvt.s32.f64 #{ARMv7_SCRATCH_FPR.armV7Single}, #{operands[0].armV7Operand}" + $asm.puts "vmov #{operands[1].armV7Operand}, #{ARMv7_SCRATCH_FPR.armV7Single}" + when "bcd2i" + $asm.puts "vcvt.s32.f64 #{ARMv7_SCRATCH_FPR.armV7Single}, #{operands[0].armV7Operand}" + $asm.puts "vmov #{operands[1].armV7Operand}, #{ARMv7_SCRATCH_FPR.armV7Single}" + $asm.puts "vcvt.f64.s32 #{ARMv7_SCRATCH_FPR.armV7Operand}, #{ARMv7_SCRATCH_FPR.armV7Single}" + emitArmV7DoubleBranch("bne", [ARMv7_SCRATCH_FPR, operands[0], operands[2]]) + $asm.puts "tst #{operands[1].armV7Operand}, #{operands[1].armV7Operand}" + $asm.puts "beq #{operands[2].asmLabel}" + when "movdz" + # FIXME: either support this or remove it. + raise "ARMv7 does not support this opcode yet, #{codeOrigin}" + when "pop" + $asm.puts "pop #{operands[0].armV7Operand}" + when "push" + $asm.puts "push #{operands[0].armV7Operand}" + when "move", "sxi2p", "zxi2p" + if operands[0].is_a? Immediate + armV7MoveImmediate(operands[0].value, operands[1]) + else + $asm.puts "mov #{armV7FlippedOperands(operands)}" + end + when "nop" + $asm.puts "nop" + when "bieq", "bpeq", "bbeq" + if Immediate.new(nil, 0) == operands[0] + $asm.puts "tst #{operands[1].armV7Operand}, #{operands[1].armV7Operand}" + elsif Immediate.new(nil, 0) == operands[1] + $asm.puts "tst #{operands[0].armV7Operand}, #{operands[0].armV7Operand}" + else + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + end + $asm.puts "beq #{operands[2].asmLabel}" + when "bineq", "bpneq", "bbneq" + if Immediate.new(nil, 0) == operands[0] + $asm.puts "tst #{operands[1].armV7Operand}, #{operands[1].armV7Operand}" + elsif Immediate.new(nil, 0) == operands[1] + $asm.puts "tst #{operands[0].armV7Operand}, #{operands[0].armV7Operand}" + else + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + end + $asm.puts "bne #{operands[2].asmLabel}" + when "bia", "bpa", "bba" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "bhi #{operands[2].asmLabel}" + when "biaeq", "bpaeq", "bbaeq" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "bhs #{operands[2].asmLabel}" + when "bib", "bpb", "bbb" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "blo #{operands[2].asmLabel}" + when "bibeq", "bpbeq", "bbbeq" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "bls #{operands[2].asmLabel}" + when "bigt", "bpgt", "bbgt" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "bgt #{operands[2].asmLabel}" + when "bigteq", "bpgteq", "bbgteq" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "bge #{operands[2].asmLabel}" + when "bilt", "bplt", "bblt" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "blt #{operands[2].asmLabel}" + when "bilteq", "bplteq", "bblteq" + $asm.puts "cmp #{armV7Operands(operands[0..1])}" + $asm.puts "ble #{operands[2].asmLabel}" + when "btiz", "btpz", "btbz" + emitArmV7Test(operands) + $asm.puts "beq #{operands[-1].asmLabel}" + when "btinz", "btpnz", "btbnz" + emitArmV7Test(operands) + $asm.puts "bne #{operands[-1].asmLabel}" + when "btio", "btpo", "btbo" + emitArmV7Test(operands) + $asm.puts "bvs #{operands[-1].asmLabel}" + when "btis", "btps", "btbs" + emitArmV7Test(operands) + $asm.puts "bmi #{operands[-1].asmLabel}" + when "jmp" + if operands[0].label? + $asm.puts "b #{operands[0].asmLabel}" + else + $asm.puts "mov pc, #{operands[0].armV7Operand}" + end + when "call" + if operands[0].label? + $asm.puts "blx #{operands[0].asmLabel}" + else + $asm.puts "blx #{operands[0].armV7Operand}" + end + when "break" + $asm.puts "bkpt" + when "ret" + $asm.puts "bx lr" + when "cieq", "cpeq" + emitArmV7Compare(operands, "eq") + when "cineq", "cpneq" + emitArmV7Compare(operands, "ne") + when "cia", "cpa" + emitArmV7Compare(operands, "hi") + when "ciaeq", "cpaeq" + emitArmV7Compare(operands, "hs") + when "cib", "cpb" + emitArmV7Compare(operands, "lo") + when "cibeq", "cpbeq" + emitArmV7Compare(operands, "ls") + when "cigt", "cpgt" + emitArmV7Compare(operands, "gt") + when "cigteq", "cpgteq" + emitArmV7Compare(operands, "ge") + when "cilt", "cplt" + emitArmV7Compare(operands, "lt") + when "cilteq", "cplteq" + emitArmV7Compare(operands, "le") + when "tio", "tbo" + emitArmV7TestSet(operands, "vs") + when "tis", "tbs" + emitArmV7TestSet(operands, "mi") + when "tiz", "tbz" + emitArmV7TestSet(operands, "eq") + when "tinz", "tbnz" + emitArmV7TestSet(operands, "ne") + when "peek" + $asm.puts "ldr #{operands[1].armV7Operand}, [sp, \##{operands[0].value * 4}]" + when "poke" + $asm.puts "str #{operands[1].armV7Operand}, [sp, \##{operands[0].value * 4}]" + when "fii2d" + $asm.puts "vmov #{operands[2].armV7Operand}, #{operands[0].armV7Operand}, #{operands[1].armV7Operand}" + when "fd2ii" + $asm.puts "vmov #{operands[1].armV7Operand}, #{operands[2].armV7Operand}, #{operands[0].armV7Operand}" + when "bo" + $asm.puts "bvs #{operands[0].asmLabel}" + when "bs" + $asm.puts "bmi #{operands[0].asmLabel}" + when "bz" + $asm.puts "beq #{operands[0].asmLabel}" + when "bnz" + $asm.puts "bne #{operands[0].asmLabel}" + when "leai", "leap" + operands[0].armV7EmitLea(operands[1]) + when "smulli" + raise "Wrong number of arguments to smull in #{self.inspect} at #{codeOriginString}" unless operands.length == 4 + $asm.puts "smull #{operands[2].armV7Operand}, #{operands[3].armV7Operand}, #{operands[0].armV7Operand}, #{operands[1].armV7Operand}" + else + raise "Unhandled opcode #{opcode} at #{codeOriginString}" + end + end +end + diff --git a/Source/JavaScriptCore/offlineasm/asm.rb b/Source/JavaScriptCore/offlineasm/asm.rb new file mode 100644 index 000000000..a93a8c5dd --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/asm.rb @@ -0,0 +1,176 @@ +#!/usr/bin/env ruby + +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +$: << File.dirname(__FILE__) + +require "backends" +require "digest/sha1" +require "offsets" +require "parser" +require "self_hash" +require "settings" +require "transform" + +class Assembler + def initialize(outp) + @outp = outp + @state = :cpp + @commentState = :none + @comment = nil + end + + def enterAsm + @outp.puts "asm (" + @state = :asm + end + + def leaveAsm + putsLastComment + @outp.puts ");" + @state = :cpp + end + + def inAsm + enterAsm + yield + leaveAsm + end + + def lastComment + if @comment + result = "// #{@comment}" + else + result = "" + end + @commentState = :none + @comment = nil + result + end + + def putsLastComment + comment = lastComment + unless comment.empty? + @outp.puts comment + end + end + + def puts(*line) + raise unless @state == :asm + @outp.puts("\"\\t" + line.join('') + "\\n\" #{lastComment}") + end + + def print(line) + raise unless @state == :asm + @outp.print("\"" + line + "\"") + end + + def putsLabel(labelName) + raise unless @state == :asm + @outp.puts("OFFLINE_ASM_GLOBAL_LABEL(#{labelName}) #{lastComment}") + end + + def putsLocalLabel(labelName) + raise unless @state == :asm + @outp.puts("LOCAL_LABEL_STRING(#{labelName}) \":\\n\" #{lastComment}") + end + + def self.labelReference(labelName) + "\" SYMBOL_STRING(#{labelName}) \"" + end + + def self.localLabelReference(labelName) + "\" LOCAL_LABEL_STRING(#{labelName}) \"" + end + + def comment(text) + case @commentState + when :none + @comment = text + @commentState = :one + when :one + @outp.puts "// #{@comment}" + @outp.puts "// #{text}" + @comment = nil + @commentState = :many + when :many + @outp.puts "// #{text}" + else + raise + end + end +end + +asmFile = ARGV.shift +offsetsFile = ARGV.shift +outputFlnm = ARGV.shift + +$stderr.puts "offlineasm: Parsing #{asmFile} and #{offsetsFile} and creating assembly file #{outputFlnm}." + +configurationList = offsetsAndConfigurationIndex(offsetsFile) +inputData = IO::read(asmFile) + +inputHash = + "// offlineasm input hash: " + Digest::SHA1.hexdigest(inputData) + + " " + Digest::SHA1.hexdigest(configurationList.map{|v| (v[0] + [v[1]]).join(' ')}.join(' ')) + + " " + selfHash + +if FileTest.exist? outputFlnm + File.open(outputFlnm, "r") { + | inp | + firstLine = inp.gets + if firstLine and firstLine.chomp == inputHash + $stderr.puts "offlineasm: Nothing changed." + exit 0 + end + } +end + +File.open(outputFlnm, "w") { + | outp | + $output = outp + $output.puts inputHash + + $asm = Assembler.new($output) + + ast = parse(lex(inputData)) + + configurationList.each { + | configuration | + offsetsList = configuration[0] + configIndex = configuration[1] + forSettings(computeSettingsCombinations(ast)[configIndex], ast) { + | concreteSettings, lowLevelAST, backend | + lowLevelAST = lowLevelAST.resolve(*buildOffsetsMap(lowLevelAST, offsetsList)) + emitCodeInConfiguration(concreteSettings, lowLevelAST, backend) { + $asm.inAsm { + lowLevelAST.lower(backend) + } + } + } + } +} + +$stderr.puts "offlineasm: Assembly file #{outputFlnm} successfully generated." + diff --git a/Source/JavaScriptCore/offlineasm/ast.rb b/Source/JavaScriptCore/offlineasm/ast.rb new file mode 100644 index 000000000..f67b0fc60 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/ast.rb @@ -0,0 +1,1039 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +# +# Base utility types for the AST. +# + +# Valid methods for Node: +# +# node.children -> Returns an array of immediate children. +# +# node.descendents -> Returns an array of all strict descendants (children +# and children of children, transitively). +# +# node.flatten -> Returns an array containing the strict descendants and +# the node itself. +# +# node.filter(type) -> Returns an array containing those elements in +# node.flatten that are of the given type (is_a? type returns true). +# +# node.mapChildren{|v| ...} -> Returns a new node with all children +# replaced according to the given block. +# +# Examples: +# +# node.filter(Setting).uniq -> Returns all of the settings that the AST's +# IfThenElse blocks depend on. +# +# node.filter(StructOffset).uniq -> Returns all of the structure offsets +# that the AST depends on. + +class Node + attr_reader :codeOrigin + + def initialize(codeOrigin) + @codeOrigin = codeOrigin + end + + def codeOriginString + "line number #{@codeOrigin}" + end + + def descendants + children.collect{|v| v.flatten}.flatten + end + + def flatten + [self] + descendants + end + + def filter(type) + flatten.select{|v| v.is_a? type} + end +end + +class NoChildren < Node + def initialize(codeOrigin) + super(codeOrigin) + end + + def children + [] + end + + def mapChildren + self + end +end + +class StructOffsetKey + attr_reader :struct, :field + + def initialize(struct, field) + @struct = struct + @field = field + end + + def hash + @struct.hash + @field.hash * 3 + end + + def eql?(other) + @struct == other.struct and @field == other.field + end +end + +# +# AST nodes. +# + +class StructOffset < NoChildren + attr_reader :struct, :field + + def initialize(codeOrigin, struct, field) + super(codeOrigin) + @struct = struct + @field = field + end + + @@mapping = {} + + def self.forField(codeOrigin, struct, field) + key = StructOffsetKey.new(struct, field) + + unless @@mapping[key] + @@mapping[key] = StructOffset.new(codeOrigin, struct, field) + end + @@mapping[key] + end + + def dump + "#{struct}::#{field}" + end + + def <=>(other) + if @struct != other.struct + return @struct <=> other.struct + end + @field <=> other.field + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class Sizeof < NoChildren + attr_reader :struct + + def initialize(codeOrigin, struct) + super(codeOrigin) + @struct = struct + end + + @@mapping = {} + + def self.forName(codeOrigin, struct) + unless @@mapping[struct] + @@mapping[struct] = Sizeof.new(codeOrigin, struct) + end + @@mapping[struct] + end + + def dump + "sizeof #{@struct}" + end + + def <=>(other) + @struct <=> other.struct + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class Immediate < NoChildren + attr_reader :value + + def initialize(codeOrigin, value) + super(codeOrigin) + @value = value + raise "Bad immediate value #{value.inspect} at #{codeOriginString}" unless value.is_a? Integer + end + + def dump + "#{value}" + end + + def ==(other) + other.is_a? Immediate and other.value == @value + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class AddImmediates < Node + attr_reader :left, :right + + def initialize(codeOrigin, left, right) + super(codeOrigin) + @left = left + @right = right + end + + def children + [@left, @right] + end + + def mapChildren + AddImmediates.new(codeOrigin, (yield @left), (yield @right)) + end + + def dump + "(#{left.dump} + #{right.dump})" + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class SubImmediates < Node + attr_reader :left, :right + + def initialize(codeOrigin, left, right) + super(codeOrigin) + @left = left + @right = right + end + + def children + [@left, @right] + end + + def mapChildren + SubImmediates.new(codeOrigin, (yield @left), (yield @right)) + end + + def dump + "(#{left.dump} - #{right.dump})" + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class MulImmediates < Node + attr_reader :left, :right + + def initialize(codeOrigin, left, right) + super(codeOrigin) + @left = left + @right = right + end + + def children + [@left, @right] + end + + def mapChildren + MulImmediates.new(codeOrigin, (yield @left), (yield @right)) + end + + def dump + "(#{left.dump} * #{right.dump})" + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class NegImmediate < Node + attr_reader :child + + def initialize(codeOrigin, child) + super(codeOrigin) + @child = child + end + + def children + [@child] + end + + def mapChildren + NegImmediate.new(codeOrigin, (yield @child)) + end + + def dump + "(-#{@child.dump})" + end + + def address? + false + end + + def label? + false + end + + def immediate? + true + end + + def register? + false + end +end + +class RegisterID < NoChildren + attr_reader :name + + def initialize(codeOrigin, name) + super(codeOrigin) + @name = name + end + + @@mapping = {} + + def self.forName(codeOrigin, name) + unless @@mapping[name] + @@mapping[name] = RegisterID.new(codeOrigin, name) + end + @@mapping[name] + end + + def dump + name + end + + def address? + false + end + + def label? + false + end + + def immediate? + false + end + + def register? + true + end +end + +class FPRegisterID < NoChildren + attr_reader :name + + def initialize(codeOrigin, name) + super(codeOrigin) + @name = name + end + + @@mapping = {} + + def self.forName(codeOrigin, name) + unless @@mapping[name] + @@mapping[name] = FPRegisterID.new(codeOrigin, name) + end + @@mapping[name] + end + + def dump + name + end + + def address? + false + end + + def label? + false + end + + def immediate? + false + end + + def register? + true + end +end + +class Variable < NoChildren + attr_reader :name + + def initialize(codeOrigin, name) + super(codeOrigin) + @name = name + end + + @@mapping = {} + + def self.forName(codeOrigin, name) + unless @@mapping[name] + @@mapping[name] = Variable.new(codeOrigin, name) + end + @@mapping[name] + end + + def dump + name + end +end + +class Address < Node + attr_reader :base, :offset + + def initialize(codeOrigin, base, offset) + super(codeOrigin) + @base = base + @offset = offset + raise "Bad base for address #{base.inspect} at #{codeOriginString}" unless base.is_a? Variable or base.register? + raise "Bad offset for address #{offset.inspect} at #{codeOriginString}" unless offset.is_a? Variable or offset.immediate? + end + + def children + [@base, @offset] + end + + def mapChildren + Address.new(codeOrigin, (yield @base), (yield @offset)) + end + + def dump + "#{offset.dump}[#{base.dump}]" + end + + def address? + true + end + + def label? + false + end + + def immediate? + false + end + + def register? + false + end +end + +class BaseIndex < Node + attr_reader :base, :index, :scale, :offset + + def initialize(codeOrigin, base, index, scale, offset) + super(codeOrigin) + @base = base + @index = index + @scale = scale + raise unless [1, 2, 4, 8].member? @scale + @offset = offset + end + + def scaleShift + case scale + when 1 + 0 + when 2 + 1 + when 4 + 2 + when 8 + 3 + else + raise "Bad scale at #{codeOriginString}" + end + end + + def children + [@base, @index, @offset] + end + + def mapChildren + BaseIndex.new(codeOrigin, (yield @base), (yield @index), @scale, (yield @offset)) + end + + def dump + "#{offset.dump}[#{base.dump}, #{index.dump}, #{scale}]" + end + + def address? + true + end + + def label? + false + end + + def immediate? + false + end + + def register? + false + end +end + +class AbsoluteAddress < NoChildren + attr_reader :address + + def initialize(codeOrigin, address) + super(codeOrigin) + @address = address + end + + def dump + "#{address.dump}[]" + end + + def address? + true + end + + def label? + false + end + + def immediate? + false + end + + def register? + false + end +end + +class Instruction < Node + attr_reader :opcode, :operands + + def initialize(codeOrigin, opcode, operands) + super(codeOrigin) + @opcode = opcode + @operands = operands + end + + def children + operands + end + + def mapChildren(&proc) + Instruction.new(codeOrigin, @opcode, @operands.map(&proc)) + end + + def dump + "\t" + opcode.to_s + " " + operands.collect{|v| v.dump}.join(", ") + end +end + +class Error < NoChildren + def initialize(codeOrigin) + super(codeOrigin) + end + + def dump + "\terror" + end +end + +class ConstDecl < Node + attr_reader :variable, :value + + def initialize(codeOrigin, variable, value) + super(codeOrigin) + @variable = variable + @value = value + end + + def children + [@variable, @value] + end + + def mapChildren + ConstDecl.new(codeOrigin, (yield @variable), (yield @value)) + end + + def dump + "const #{@variable.dump} = #{@value.dump}" + end +end + +$labelMapping = {} + +class Label < NoChildren + attr_reader :name + + def initialize(codeOrigin, name) + super(codeOrigin) + @name = name + end + + def self.forName(codeOrigin, name) + if $labelMapping[name] + raise "Label name collision: #{name}" unless $labelMapping[name].is_a? Label + else + $labelMapping[name] = Label.new(codeOrigin, name) + end + $labelMapping[name] + end + + def dump + "#{name}:" + end +end + +class LocalLabel < NoChildren + attr_reader :name + + def initialize(codeOrigin, name) + super(codeOrigin) + @name = name + end + + @@uniqueNameCounter = 0 + + def self.forName(codeOrigin, name) + if $labelMapping[name] + raise "Label name collision: #{name}" unless $labelMapping[name].is_a? LocalLabel + else + $labelMapping[name] = LocalLabel.new(codeOrigin, name) + end + $labelMapping[name] + end + + def self.unique(comment) + newName = "_#{comment}" + if $labelMapping[newName] + while $labelMapping[newName = "_#{@@uniqueNameCounter}_#{comment}"] + @@uniqueNameCounter += 1 + end + end + forName(nil, newName) + end + + def cleanName + if name =~ /^\./ + "_" + name[1..-1] + else + name + end + end + + def dump + "#{name}:" + end +end + +class LabelReference < Node + attr_reader :label + + def initialize(codeOrigin, label) + super(codeOrigin) + @label = label + end + + def children + [@label] + end + + def mapChildren + LabelReference.new(codeOrigin, (yield @label)) + end + + def name + label.name + end + + def dump + label.name + end + + def address? + false + end + + def label? + true + end +end + +class LocalLabelReference < NoChildren + attr_reader :label + + def initialize(codeOrigin, label) + super(codeOrigin) + @label = label + end + + def children + [@label] + end + + def mapChildren + LocalLabelReference.new(codeOrigin, (yield @label)) + end + + def name + label.name + end + + def dump + label.name + end + + def address? + false + end + + def label? + true + end +end + +class Sequence < Node + attr_reader :list + + def initialize(codeOrigin, list) + super(codeOrigin) + @list = list + end + + def children + list + end + + def mapChildren(&proc) + Sequence.new(codeOrigin, @list.map(&proc)) + end + + def dump + list.collect{|v| v.dump}.join("\n") + end +end + +class True < NoChildren + def initialize + super(nil) + end + + @@instance = True.new + + def self.instance + @@instance + end + + def value + true + end + + def dump + "true" + end +end + +class False < NoChildren + def initialize + super(nil) + end + + @@instance = False.new + + def self.instance + @@instance + end + + def value + false + end + + def dump + "false" + end +end + +class TrueClass + def asNode + True.instance + end +end + +class FalseClass + def asNode + False.instance + end +end + +class Setting < NoChildren + attr_reader :name + + def initialize(codeOrigin, name) + super(codeOrigin) + @name = name + end + + @@mapping = {} + + def self.forName(codeOrigin, name) + unless @@mapping[name] + @@mapping[name] = Setting.new(codeOrigin, name) + end + @@mapping[name] + end + + def dump + name + end +end + +class And < Node + attr_reader :left, :right + + def initialize(codeOrigin, left, right) + super(codeOrigin) + @left = left + @right = right + end + + def children + [@left, @right] + end + + def mapChildren + And.new(codeOrigin, (yield @left), (yield @right)) + end + + def dump + "(#{left.dump} and #{right.dump})" + end +end + +class Or < Node + attr_reader :left, :right + + def initialize(codeOrigin, left, right) + super(codeOrigin) + @left = left + @right = right + end + + def children + [@left, @right] + end + + def mapChildren + Or.new(codeOrigin, (yield @left), (yield @right)) + end + + def dump + "(#{left.dump} or #{right.dump})" + end +end + +class Not < Node + attr_reader :child + + def initialize(codeOrigin, child) + super(codeOrigin) + @child = child + end + + def children + [@left, @right] + end + + def mapChildren + Not.new(codeOrigin, (yield @child)) + end + + def dump + "(not #{child.dump})" + end +end + +class Skip < NoChildren + def initialize(codeOrigin) + super(codeOrigin) + end + + def dump + "\tskip" + end +end + +class IfThenElse < Node + attr_reader :predicate, :thenCase + attr_accessor :elseCase + + def initialize(codeOrigin, predicate, thenCase) + super(codeOrigin) + @predicate = predicate + @thenCase = thenCase + @elseCase = Skip.new(codeOrigin) + end + + def children + if @elseCase + [@predicate, @thenCase, @elseCase] + else + [@predicate, @thenCase] + end + end + + def mapChildren + IfThenElse.new(codeOrigin, (yield @predicate), (yield @thenCase), (yield @elseCase)) + end + + def dump + "if #{predicate.dump}\n" + thenCase.dump + "\nelse\n" + elseCase.dump + "\nend" + end +end + +class Macro < Node + attr_reader :name, :variables, :body + + def initialize(codeOrigin, name, variables, body) + super(codeOrigin) + @name = name + @variables = variables + @body = body + end + + def children + @variables + [@body] + end + + def mapChildren + Macro.new(codeOrigin, @name, @variables.map{|v| yield v}, (yield @body)) + end + + def dump + "macro #{name}(" + variables.collect{|v| v.dump}.join(", ") + ")\n" + body.dump + "\nend" + end +end + +class MacroCall < Node + attr_reader :name, :operands + + def initialize(codeOrigin, name, operands) + super(codeOrigin) + @name = name + @operands = operands + raise unless @operands + @operands.each{|v| raise unless v} + end + + def children + @operands + end + + def mapChildren(&proc) + MacroCall.new(codeOrigin, @name, @operands.map(&proc)) + end + + def dump + "\t#{name}(" + operands.collect{|v| v.dump}.join(", ") + ")" + end +end + diff --git a/Source/JavaScriptCore/offlineasm/backends.rb b/Source/JavaScriptCore/offlineasm/backends.rb new file mode 100644 index 000000000..2c65b517d --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/backends.rb @@ -0,0 +1,96 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "armv7" +require "ast" +require "x86" + +BACKENDS = + [ + "X86", + "ARMv7" + ] + +# Keep the set of working backends separate from the set of backends that might be +# supported. This is great because the BACKENDS list is almost like a reserved +# words list, in that it causes settings resolution to treat those words specially. +# Hence this lets us set aside the name of a backend we might want to support in +# the future while not actually supporting the backend yet. +WORKING_BACKENDS = + [ + "X86", + "ARMv7" + ] + +BACKEND_PATTERN = Regexp.new('\\A(' + BACKENDS.join(')|(') + ')\\Z') + +class Node + def lower(name) + send("lower" + name) + end +end + +# Overrides for lower() for those nodes that are backend-agnostic + +class Label + def lower(name) + $asm.putsLabel(self.name[1..-1]) + end +end + +class LocalLabel + def lower(name) + $asm.putsLocalLabel "_offlineasm_#{self.name[1..-1]}" + end +end + +class LabelReference + def asmLabel + Assembler.labelReference(name[1..-1]) + end +end + +class LocalLabelReference + def asmLabel + Assembler.localLabelReference("_offlineasm_"+name[1..-1]) + end +end + +class Skip + def lower(name) + end +end + +class Sequence + def lower(name) + if respond_to? "lower#{name}" + send("lower#{name}") + else + @list.each { + | node | + node.lower(name) + } + end + end +end + diff --git a/Source/JavaScriptCore/offlineasm/generate_offset_extractor.rb b/Source/JavaScriptCore/offlineasm/generate_offset_extractor.rb new file mode 100644 index 000000000..8bdf4505d --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/generate_offset_extractor.rb @@ -0,0 +1,146 @@ +#!/usr/bin/env ruby + +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +$: << File.dirname(__FILE__) + +require "backends" +require "digest/sha1" +require "offsets" +require "parser" +require "self_hash" +require "settings" +require "transform" + +inputFlnm = ARGV.shift +outputFlnm = ARGV.shift + +$stderr.puts "offlineasm: Parsing #{inputFlnm} and creating offset extractor #{outputFlnm}." + +def emitMagicNumber + OFFSET_MAGIC_NUMBERS.each { + | number | + $output.puts "#{number}," + } +end + +inputData = IO::read(inputFlnm) +inputHash = "// offlineasm input hash: #{Digest::SHA1.hexdigest(inputData)} #{selfHash}" + +if FileTest.exist? outputFlnm + File.open(outputFlnm, "r") { + | inp | + firstLine = inp.gets + if firstLine and firstLine.chomp == inputHash + $stderr.puts "offlineasm: Nothing changed." + exit 0 + end + } +end + +originalAST = parse(lex(inputData)) + +# +# Optimize the AST to make configuration extraction faster. This reduces the AST to a form +# that only contains the things that matter for our purposes: offsets, sizes, and if +# statements. +# + +class Node + def offsetsPruneTo(sequence) + children.each { + | child | + child.offsetsPruneTo(sequence) + } + end + + def offsetsPrune + result = Sequence.new(codeOrigin, []) + offsetsPruneTo(result) + result + end +end + +class IfThenElse + def offsetsPruneTo(sequence) + ifThenElse = IfThenElse.new(codeOrigin, predicate, thenCase.offsetsPrune) + ifThenElse.elseCase = elseCase.offsetsPrune + sequence.list << ifThenElse + end +end + +class StructOffset + def offsetsPruneTo(sequence) + sequence.list << self + end +end + +class Sizeof + def offsetsPruneTo(sequence) + sequence.list << self + end +end + +prunedAST = originalAST.offsetsPrune + +File.open(outputFlnm, "w") { + | outp | + $output = outp + outp.puts inputHash + length = 0 + emitCodeInAllConfigurations(prunedAST) { + | settings, ast, backend, index | + offsetsList = ast.filter(StructOffset).uniq.sort + sizesList = ast.filter(Sizeof).uniq.sort + length += OFFSET_HEADER_MAGIC_NUMBERS.size + (OFFSET_MAGIC_NUMBERS.size + 1) * (1 + offsetsList.size + sizesList.size) + } + outp.puts "static const unsigned extractorTable[#{length}] = {" + emitCodeInAllConfigurations(prunedAST) { + | settings, ast, backend, index | + OFFSET_HEADER_MAGIC_NUMBERS.each { + | number | + $output.puts "#{number}," + } + + offsetsList = ast.filter(StructOffset).uniq.sort + sizesList = ast.filter(Sizeof).uniq.sort + + emitMagicNumber + outp.puts "#{index}," + offsetsList.each { + | offset | + emitMagicNumber + outp.puts "OFFLINE_ASM_OFFSETOF(#{offset.struct}, #{offset.field})," + } + sizesList.each { + | offset | + emitMagicNumber + outp.puts "sizeof(#{offset.struct})," + } + } + outp.puts "};" +} + +$stderr.puts "offlineasm: offset extractor #{outputFlnm} successfully generated." + diff --git a/Source/JavaScriptCore/offlineasm/instructions.rb b/Source/JavaScriptCore/offlineasm/instructions.rb new file mode 100644 index 000000000..497b47371 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/instructions.rb @@ -0,0 +1,217 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +# Interesting invariant, which we take advantage of: branching instructions +# always begin with "b", and no non-branching instructions begin with "b". +# Terminal instructions are "jmp" and "ret". + +MACRO_INSTRUCTIONS = + [ + "addi", + "andi", + "lshifti", + "muli", + "negi", + "noti", + "ori", + "rshifti", + "urshifti", + "subi", + "xori", + "loadi", + "loadb", + "loadbs", + "loadh", + "loadhs", + "storei", + "storeb", + "loadd", + "moved", + "stored", + "addd", + "divd", + "subd", + "muld", + "sqrtd", + "ci2d", + "fii2d", # usage: fii2d <gpr with least significant bits>, <gpr with most significant bits>, <fpr> + "fd2ii", # usage: fd2ii <fpr>, <gpr with least significant bits>, <gpr with most significant bits> + "bdeq", + "bdneq", + "bdgt", + "bdgteq", + "bdlt", + "bdlteq", + "bdequn", + "bdnequn", + "bdgtun", + "bdgtequn", + "bdltun", + "bdltequn", + "btd2i", + "td2i", + "bcd2i", + "movdz", + "pop", + "push", + "move", + "sxi2p", + "zxi2p", + "nop", + "bieq", + "bineq", + "bia", + "biaeq", + "bib", + "bibeq", + "bigt", + "bigteq", + "bilt", + "bilteq", + "bbeq", + "bbneq", + "bba", + "bbaeq", + "bbb", + "bbbeq", + "bbgt", + "bbgteq", + "bblt", + "bblteq", + "btio", + "btis", + "btiz", + "btinz", + "btbo", + "btbs", + "btbz", + "btbnz", + "jmp", + "baddio", + "baddis", + "baddiz", + "baddinz", + "bsubio", + "bsubis", + "bsubiz", + "bsubinz", + "bmulio", + "bmulis", + "bmuliz", + "bmulinz", + "borio", + "boris", + "boriz", + "borinz", + "break", + "call", + "ret", + "cieq", + "cineq", + "cia", + "ciaeq", + "cib", + "cibeq", + "cigt", + "cigteq", + "cilt", + "cilteq", + "tio", + "tis", + "tiz", + "tinz", + "tbo", + "tbs", + "tbz", + "tbnz", + "peek", + "poke", + "bpeq", + "bpneq", + "bpa", + "bpaeq", + "bpb", + "bpbeq", + "bpgt", + "bpgteq", + "bplt", + "bplteq", + "addp", + "andp", + "orp", + "subp", + "xorp", + "loadp", + "cpeq", + "cpneq", + "cpa", + "cpaeq", + "cpb", + "cpbeq", + "cpgt", + "cpgteq", + "cplt", + "cplteq", + "storep", + "btpo", + "btps", + "btpz", + "btpnz", + "baddpo", + "baddps", + "baddpz", + "baddpnz", + "bo", + "bs", + "bz", + "bnz", + "leai", + "leap", + ] + +X86_INSTRUCTIONS = + [ + "cdqi", + "idivi" + ] + +ARMv7_INSTRUCTIONS = + [ + "smulli", + "addis", + "subis", + "oris" + ] + +INSTRUCTIONS = MACRO_INSTRUCTIONS + X86_INSTRUCTIONS + ARMv7_INSTRUCTIONS + +INSTRUCTION_PATTERN = Regexp.new('\\A((' + INSTRUCTIONS.join(')|(') + '))\\Z') + +def isBranch(instruction) + instruction =~ /^b/ +end + +def hasFallThrough(instruction) + instruction != "ret" and instruction != "jmp" +end + diff --git a/Source/JavaScriptCore/offlineasm/offsets.rb b/Source/JavaScriptCore/offlineasm/offsets.rb new file mode 100644 index 000000000..21e1706d2 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/offsets.rb @@ -0,0 +1,173 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "ast" + +OFFSET_HEADER_MAGIC_NUMBERS = [ 0x9e43fd66, 0x4379bfba ] +OFFSET_MAGIC_NUMBERS = [ 0xec577ac7, 0x0ff5e755 ] + +# +# offsetsList(ast) +# sizesList(ast) +# +# Returns a list of offsets and sizes used by the AST. +# + +def offsetsList(ast) + ast.filter(StructOffset).uniq.sort +end + +def sizesList(ast) + ast.filter(Sizeof).uniq.sort +end + +# +# offsetsAndConfigurationIndex(ast, file) -> +# [[offsets, index], ...] +# +# Parses the offsets from a file and returns a list of offsets and the +# index of the configuration that is valid in this build target. +# + +def offsetsAndConfigurationIndex(file) + endiannessMarkerBytes = nil + result = [] + + def readInt(endianness, bytes) + if endianness == :little + # Little endian + (bytes[0] << 0 | + bytes[1] << 8 | + bytes[2] << 16 | + bytes[3] << 24) + else + # Big endian + (bytes[0] << 24 | + bytes[1] << 16 | + bytes[2] << 8 | + bytes[3] << 0) + end + end + + def prepareMagic(endianness, numbers) + magicBytes = [] + numbers.each { + | number | + currentBytes = [] + 4.times { + currentBytes << (number & 0xff) + number >>= 8 + } + if endianness == :big + currentBytes.reverse! + end + magicBytes += currentBytes + } + magicBytes + end + + fileBytes = [] + + File.open(file, "r") { + | inp | + loop { + byte = inp.getbyte + break unless byte + fileBytes << byte + } + } + + def sliceByteArrays(byteArray, pattern) + result = [] + lastSlicePoint = 0 + (byteArray.length - pattern.length + 1).times { + | index | + foundOne = true + pattern.length.times { + | subIndex | + if byteArray[index + subIndex] != pattern[subIndex] + foundOne = false + break + end + } + if foundOne + result << byteArray[lastSlicePoint...index] + lastSlicePoint = index + pattern.length + end + } + + result << byteArray[lastSlicePoint...(byteArray.length)] + + result + end + + [:little, :big].each { + | endianness | + headerMagicBytes = prepareMagic(endianness, OFFSET_HEADER_MAGIC_NUMBERS) + magicBytes = prepareMagic(endianness, OFFSET_MAGIC_NUMBERS) + + bigArray = sliceByteArrays(fileBytes, headerMagicBytes) + unless bigArray.size <= 1 + bigArray[1..-1].each { + | configArray | + array = sliceByteArrays(configArray, magicBytes) + index = readInt(endianness, array[1]) + offsets = [] + array[2..-1].each { + | data | + offsets << readInt(endianness, data) + } + result << [offsets, index] + } + end + } + + raise unless result.length >= 1 + raise if result.map{|v| v[1]}.uniq.size < result.map{|v| v[1]}.size + + result +end + +# +# buildOffsetsMap(ast, offsetsList) -> [offsets, sizes] +# +# Builds a mapping between StructOffset nodes and their values. +# + +def buildOffsetsMap(ast, offsetsList) + offsetsMap = {} + sizesMap = {} + astOffsetsList = offsetsList(ast) + astSizesList = sizesList(ast) + raise unless astOffsetsList.size + astSizesList.size == offsetsList.size + offsetsList(ast).each_with_index { + | structOffset, index | + offsetsMap[structOffset] = offsetsList.shift + } + sizesList(ast).each_with_index { + | sizeof, index | + sizesMap[sizeof] = offsetsList.shift + } + [offsetsMap, sizesMap] +end + diff --git a/Source/JavaScriptCore/offlineasm/opt.rb b/Source/JavaScriptCore/offlineasm/opt.rb new file mode 100644 index 000000000..3170d3ae1 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/opt.rb @@ -0,0 +1,134 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "ast" + +# +# "Optimization" passes. These are used to lower the representation for +# backends that cannot handle some of our higher-level instructions. +# + +# +# A temporary - a variable that will be allocated to a register after we're +# done. +# + +class Node + def replaceTemporariesWithRegisters(kind) + mapChildren { + | node | + node.replaceTemporariesWithRegisters(kind) + } + end +end + +class Tmp < NoChildren + attr_reader :firstMention, :lastMention + attr_reader :kind + attr_accessor :register + + def initialize(codeOrigin, kind) + super(codeOrigin) + @kind = kind + end + + def dump + "$tmp#{object_id}" + end + + def mention!(position) + if not @firstMention or position < @firstMention + @firstMention = position + end + if not @lastMention or position > @lastMention + @lastMention = position + end + end + + def replaceTemporariesWithRegisters(kind) + if @kind == kind + raise "Did not allocate register to temporary at #{codeOriginString}" unless @register + @register + else + self + end + end + + def address? + false + end + + def label? + false + end + + def immediate? + false + end + + def register? + true + end +end + +# Assign registers to temporaries, by finding which temporaries interfere +# with each other. Note that this relies on temporary live ranges not crossing +# basic block boundaries. + +def assignRegistersToTemporaries(list, kind, registers) + list.each_with_index { + | node, index | + node.filter(Tmp).uniq.each { + | tmp | + if tmp.kind == kind + tmp.mention! index + end + } + } + + freeRegisters = registers.dup + list.each_with_index { + | node, index | + tmpList = node.filter(Tmp).uniq + tmpList.each { + | tmp | + if tmp.kind == kind and tmp.firstMention == index + raise "Could not allocate register to temporary at #{node.codeOriginString}" if freeRegisters.empty? + tmp.register = freeRegisters.pop + end + } + tmpList.each { + | tmp | + if tmp.kind == kind and tmp.lastMention == index + freeRegisters.push tmp.register + raise "Register allocation inconsistency at #{node.codeOriginString}" if freeRegisters.size > registers.size + end + } + } + + list.map { + | node | + node.replaceTemporariesWithRegisters(kind) + } +end + diff --git a/Source/JavaScriptCore/offlineasm/parser.rb b/Source/JavaScriptCore/offlineasm/parser.rb new file mode 100644 index 000000000..f0e4b0045 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/parser.rb @@ -0,0 +1,586 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "ast" +require "instructions" +require "registers" + +class Token + attr_reader :codeOrigin, :string + + def initialize(codeOrigin, string) + @codeOrigin = codeOrigin + @string = string + end + + def ==(other) + if other.is_a? Token + @string == other.string + else + @string == other + end + end + + def =~(other) + @string =~ other + end + + def to_s + "#{@string.inspect} at line #{codeOrigin}" + end + + def parseError(*comment) + if comment.empty? + raise "Parse error: #{to_s}" + else + raise "Parse error: #{to_s}: #{comment[0]}" + end + end +end + +# +# The lexer. Takes a string and returns an array of tokens. +# + +def lex(str) + result = [] + lineNumber = 1 + while not str.empty? + case str + when /\A\#([^\n]*)/ + # comment, ignore + when /\A\n/ + result << Token.new(lineNumber, $&) + lineNumber += 1 + when /\A[a-zA-Z]([a-zA-Z0-9_]*)/ + result << Token.new(lineNumber, $&) + when /\A\.([a-zA-Z0-9_]*)/ + result << Token.new(lineNumber, $&) + when /\A_([a-zA-Z0-9_]*)/ + result << Token.new(lineNumber, $&) + when /\A([ \t]+)/ + # whitespace, ignore + when /\A0x([0-9a-fA-F]+)/ + result << Token.new(lineNumber, $&.hex.to_s) + when /\A0([0-7]+)/ + result << Token.new(lineNumber, $&.oct.to_s) + when /\A([0-9]+)/ + result << Token.new(lineNumber, $&) + when /\A::/ + result << Token.new(lineNumber, $&) + when /\A[:,\(\)\[\]=\+\-*]/ + result << Token.new(lineNumber, $&) + else + raise "Lexer error at line number #{lineNumber}, unexpected sequence #{str[0..20].inspect}" + end + str = $~.post_match + end + result +end + +# +# Token identification. +# + +def isRegister(token) + token =~ REGISTER_PATTERN +end + +def isInstruction(token) + token =~ INSTRUCTION_PATTERN +end + +def isKeyword(token) + token =~ /\A((true)|(false)|(if)|(then)|(else)|(elsif)|(end)|(and)|(or)|(not)|(macro)|(const)|(sizeof)|(error))\Z/ or + token =~ REGISTER_PATTERN or + token =~ INSTRUCTION_PATTERN +end + +def isIdentifier(token) + token =~ /\A[a-zA-Z]([a-zA-Z0-9_]*)\Z/ and not isKeyword(token) +end + +def isLabel(token) + token =~ /\A_([a-zA-Z0-9_]*)\Z/ +end + +def isLocalLabel(token) + token =~ /\A\.([a-zA-Z0-9_]*)\Z/ +end + +def isVariable(token) + isIdentifier(token) or isRegister(token) +end + +def isInteger(token) + token =~ /\A[0-9]/ +end + +# +# The parser. Takes an array of tokens and returns an AST. Methods +# other than parse(tokens) are not for public consumption. +# + +class Parser + def initialize(tokens) + @tokens = tokens + @idx = 0 + end + + def parseError(*comment) + if @tokens[@idx] + @tokens[@idx].parseError(*comment) + else + if comment.empty? + raise "Parse error at end of file" + else + raise "Parse error at end of file: #{comment[0]}" + end + end + end + + def consume(regexp) + if regexp + parseError unless @tokens[@idx] =~ regexp + else + parseError unless @idx == @tokens.length + end + @idx += 1 + end + + def skipNewLine + while @tokens[@idx] == "\n" + @idx += 1 + end + end + + def parsePredicateAtom + if @tokens[@idx] == "not" + @idx += 1 + parsePredicateAtom + elsif @tokens[@idx] == "(" + @idx += 1 + skipNewLine + result = parsePredicate + parseError unless @tokens[@idx] == ")" + @idx += 1 + result + elsif @tokens[@idx] == "true" + result = True.instance + @idx += 1 + result + elsif @tokens[@idx] == "false" + result = False.instance + @idx += 1 + result + elsif isIdentifier @tokens[@idx] + result = Setting.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string) + @idx += 1 + result + else + parseError + end + end + + def parsePredicateAnd + result = parsePredicateAtom + while @tokens[@idx] == "and" + codeOrigin = @tokens[@idx].codeOrigin + @idx += 1 + skipNewLine + right = parsePredicateAtom + result = And.new(codeOrigin, result, right) + end + result + end + + def parsePredicate + # some examples of precedence: + # not a and b -> (not a) and b + # a and b or c -> (a and b) or c + # a or b and c -> a or (b and c) + + result = parsePredicateAnd + while @tokens[@idx] == "or" + codeOrigin = @tokens[@idx].codeOrigin + @idx += 1 + skipNewLine + right = parsePredicateAnd + result = Or.new(codeOrigin, result, right) + end + result + end + + def parseVariable + if isRegister(@tokens[@idx]) + if @tokens[@idx] =~ FPR_PATTERN + result = FPRegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string) + else + result = RegisterID.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string) + end + elsif isIdentifier(@tokens[@idx]) + result = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string) + else + parseError + end + @idx += 1 + result + end + + def parseAddress(offset) + parseError unless @tokens[@idx] == "[" + codeOrigin = @tokens[@idx].codeOrigin + + # Three possibilities: + # [] -> AbsoluteAddress + # [a] -> Address + # [a,b] -> BaseIndex with scale = 1 + # [a,b,c] -> BaseIndex + + @idx += 1 + if @tokens[@idx] == "]" + @idx += 1 + return AbsoluteAddress.new(codeOrigin, offset) + end + a = parseVariable + if @tokens[@idx] == "]" + result = Address.new(codeOrigin, a, offset) + else + parseError unless @tokens[@idx] == "," + @idx += 1 + b = parseVariable + if @tokens[@idx] == "]" + result = BaseIndex.new(codeOrigin, a, b, 1, offset) + else + parseError unless @tokens[@idx] == "," + @idx += 1 + parseError unless ["1", "2", "4", "8"].member? @tokens[@idx].string + c = @tokens[@idx].string.to_i + @idx += 1 + parseError unless @tokens[@idx] == "]" + result = BaseIndex.new(codeOrigin, a, b, c, offset) + end + end + @idx += 1 + result + end + + def parseColonColon + skipNewLine + codeOrigin = @tokens[@idx].codeOrigin + parseError unless isIdentifier @tokens[@idx] + names = [@tokens[@idx].string] + @idx += 1 + while @tokens[@idx] == "::" + @idx += 1 + parseError unless isIdentifier @tokens[@idx] + names << @tokens[@idx].string + @idx += 1 + end + raise if names.empty? + [codeOrigin, names] + end + + def parseExpressionAtom + skipNewLine + if @tokens[@idx] == "-" + @idx += 1 + NegImmediate.new(@tokens[@idx - 1].codeOrigin, parseExpressionAtom) + elsif @tokens[@idx] == "(" + @idx += 1 + result = parseExpression + parseError unless @tokens[@idx] == ")" + @idx += 1 + result + elsif isInteger @tokens[@idx] + result = Immediate.new(@tokens[@idx].codeOrigin, @tokens[@idx].string.to_i) + @idx += 1 + result + elsif isIdentifier @tokens[@idx] + codeOrigin, names = parseColonColon + if names.size > 1 + StructOffset.forField(codeOrigin, names[0..-2].join('::'), names[-1]) + else + Variable.forName(codeOrigin, names[0]) + end + elsif isRegister @tokens[@idx] + parseVariable + elsif @tokens[@idx] == "sizeof" + @idx += 1 + codeOrigin, names = parseColonColon + Sizeof.forName(codeOrigin, names.join('::')) + else + parseError + end + end + + def parseExpressionMul + skipNewLine + result = parseExpressionAtom + while @tokens[@idx] == "*" + if @tokens[@idx] == "*" + @idx += 1 + result = MulImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionAtom) + else + raise + end + end + result + end + + def couldBeExpression + @tokens[@idx] == "-" or @tokens[@idx] == "sizeof" or isInteger(@tokens[@idx]) or isVariable(@tokens[@idx]) or @tokens[@idx] == "(" + end + + def parseExpression + skipNewLine + result = parseExpressionMul + while @tokens[@idx] == "+" or @tokens[@idx] == "-" + if @tokens[@idx] == "+" + @idx += 1 + result = AddImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul) + elsif @tokens[@idx] == "-" + @idx += 1 + result = SubImmediates.new(@tokens[@idx - 1].codeOrigin, result, parseExpressionMul) + else + raise + end + end + result + end + + def parseOperand(comment) + skipNewLine + if couldBeExpression + expr = parseExpression + if @tokens[@idx] == "[" + parseAddress(expr) + else + expr + end + elsif @tokens[@idx] == "[" + parseAddress(Immediate.new(@tokens[@idx].codeOrigin, 0)) + elsif isLabel @tokens[@idx] + result = LabelReference.new(@tokens[@idx].codeOrigin, Label.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)) + @idx += 1 + result + elsif isLocalLabel @tokens[@idx] + result = LocalLabelReference.new(@tokens[@idx].codeOrigin, LocalLabel.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string)) + @idx += 1 + result + else + parseError(comment) + end + end + + def parseMacroVariables + skipNewLine + consume(/\A\(\Z/) + variables = [] + loop { + skipNewLine + if @tokens[@idx] == ")" + @idx += 1 + break + elsif isIdentifier(@tokens[@idx]) + variables << Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string) + @idx += 1 + skipNewLine + if @tokens[@idx] == ")" + @idx += 1 + break + elsif @tokens[@idx] == "," + @idx += 1 + else + parseError + end + else + parseError + end + } + variables + end + + def parseSequence(final, comment) + firstCodeOrigin = @tokens[@idx].codeOrigin + list = [] + loop { + if (@idx == @tokens.length and not final) or (final and @tokens[@idx] =~ final) + break + elsif @tokens[@idx] == "\n" + # ignore + @idx += 1 + elsif @tokens[@idx] == "const" + @idx += 1 + parseError unless isVariable @tokens[@idx] + variable = Variable.forName(@tokens[@idx].codeOrigin, @tokens[@idx].string) + @idx += 1 + parseError unless @tokens[@idx] == "=" + @idx += 1 + value = parseOperand("while inside of const #{variable.name}") + list << ConstDecl.new(@tokens[@idx].codeOrigin, variable, value) + elsif @tokens[@idx] == "error" + list << Error.new(@tokens[@idx].codeOrigin) + @idx += 1 + elsif @tokens[@idx] == "if" + codeOrigin = @tokens[@idx].codeOrigin + @idx += 1 + skipNewLine + predicate = parsePredicate + consume(/\A((then)|(\n))\Z/) + skipNewLine + ifThenElse = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\"")) + list << ifThenElse + while @tokens[@idx] == "elsif" + codeOrigin = @tokens[@idx].codeOrigin + @idx += 1 + skipNewLine + predicate = parsePredicate + consume(/\A((then)|(\n))\Z/) + skipNewLine + elseCase = IfThenElse.new(codeOrigin, predicate, parseSequence(/\A((else)|(end)|(elsif))\Z/, "while inside of \"if #{predicate.dump}\"")) + ifThenElse.elseCase = elseCase + ifThenElse = elseCase + end + if @tokens[@idx] == "else" + @idx += 1 + ifThenElse.elseCase = parseSequence(/\Aend\Z/, "while inside of else case for \"if #{predicate.dump}\"") + @idx += 1 + else + parseError unless @tokens[@idx] == "end" + @idx += 1 + end + elsif @tokens[@idx] == "macro" + codeOrigin = @tokens[@idx].codeOrigin + @idx += 1 + skipNewLine + parseError unless isIdentifier(@tokens[@idx]) + name = @tokens[@idx].string + @idx += 1 + variables = parseMacroVariables + body = parseSequence(/\Aend\Z/, "while inside of macro #{name}") + @idx += 1 + list << Macro.new(codeOrigin, name, variables, body) + elsif isInstruction @tokens[@idx] + codeOrigin = @tokens[@idx].codeOrigin + name = @tokens[@idx].string + @idx += 1 + if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final) + # Zero operand instruction, and it's the last one. + list << Instruction.new(codeOrigin, name, []) + break + elsif @tokens[@idx] == "\n" + # Zero operand instruction. + list << Instruction.new(codeOrigin, name, []) + @idx += 1 + else + # It's definitely an instruction, and it has at least one operand. + operands = [] + endOfSequence = false + loop { + operands << parseOperand("while inside of instruction #{name}") + if (not final and @idx == @tokens.size) or (final and @tokens[@idx] =~ final) + # The end of the instruction and of the sequence. + endOfSequence = true + break + elsif @tokens[@idx] == "," + # Has another operand. + @idx += 1 + elsif @tokens[@idx] == "\n" + # The end of the instruction. + @idx += 1 + break + else + parseError("Expected a comma, newline, or #{final} after #{operands.last.dump}") + end + } + list << Instruction.new(codeOrigin, name, operands) + if endOfSequence + break + end + end + elsif isIdentifier @tokens[@idx] + codeOrigin = @tokens[@idx].codeOrigin + name = @tokens[@idx].string + @idx += 1 + if @tokens[@idx] == "(" + # Macro invocation. + @idx += 1 + operands = [] + skipNewLine + if @tokens[@idx] == ")" + @idx += 1 + else + loop { + skipNewLine + if @tokens[@idx] == "macro" + # It's a macro lambda! + codeOriginInner = @tokens[@idx].codeOrigin + @idx += 1 + variables = parseMacroVariables + body = parseSequence(/\Aend\Z/, "while inside of anonymous macro passed as argument to #{name}") + @idx += 1 + operands << Macro.new(codeOriginInner, nil, variables, body) + else + operands << parseOperand("while inside of macro call to #{name}") + end + skipNewLine + if @tokens[@idx] == ")" + @idx += 1 + break + elsif @tokens[@idx] == "," + @idx += 1 + else + parseError "Unexpected #{@tokens[@idx].string.inspect} while parsing invocation of macro #{name}" + end + } + end + list << MacroCall.new(codeOrigin, name, operands) + else + parseError "Expected \"(\" after #{name}" + end + elsif isLabel @tokens[@idx] or isLocalLabel @tokens[@idx] + codeOrigin = @tokens[@idx].codeOrigin + name = @tokens[@idx].string + @idx += 1 + parseError unless @tokens[@idx] == ":" + # It's a label. + if isLabel name + list << Label.forName(codeOrigin, name) + else + list << LocalLabel.forName(codeOrigin, name) + end + @idx += 1 + else + parseError "Expecting terminal #{final} #{comment}" + end + } + Sequence.new(firstCodeOrigin, list) + end +end + +def parse(tokens) + parser = Parser.new(tokens) + parser.parseSequence(nil, "") +end + diff --git a/Source/JavaScriptCore/offlineasm/registers.rb b/Source/JavaScriptCore/offlineasm/registers.rb new file mode 100644 index 000000000..75fae4192 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/registers.rb @@ -0,0 +1,60 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +GPRS = + [ + "t0", + "t1", + "t2", + "t3", + "t4", + "cfr", + "a0", + "a1", + "r0", + "r1", + "sp", + "lr" + ] + +FPRS = + [ + "ft0", + "ft1", + "ft2", + "ft3", + "ft4", + "ft5", + "fa0", + "fa1", + "fa2", + "fa3", + "fr" + ] + +REGISTERS = GPRS + FPRS + +GPR_PATTERN = Regexp.new('\\A((' + GPRS.join(')|(') + '))\\Z') +FPR_PATTERN = Regexp.new('\\A((' + FPRS.join(')|(') + '))\\Z') + +REGISTER_PATTERN = Regexp.new('\\A((' + REGISTERS.join(')|(') + '))\\Z') diff --git a/Source/JavaScriptCore/offlineasm/self_hash.rb b/Source/JavaScriptCore/offlineasm/self_hash.rb new file mode 100644 index 000000000..a7b51e112 --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/self_hash.rb @@ -0,0 +1,46 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "digest/sha1" +require "pathname" + +# +# selfHash -> SHA1 hexdigest +# +# Returns a hash of the offlineasm source code. This allows dependency +# tracking for not just changes in input, but also changes in the assembler +# itself. +# + +def selfHash + contents = "" + myPath = Pathname.new(__FILE__).dirname + Dir.foreach(myPath) { + | entry | + if entry =~ /\.rb$/ + contents += IO::read(myPath + entry) + end + } + return Digest::SHA1.hexdigest(contents) +end + diff --git a/Source/JavaScriptCore/offlineasm/settings.rb b/Source/JavaScriptCore/offlineasm/settings.rb new file mode 100644 index 000000000..34598181c --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/settings.rb @@ -0,0 +1,205 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "ast" +require "backends" +require "parser" +require "transform" + +# +# computeSettingsCombinations(ast) -> settingsCombiations +# +# Computes an array of settings maps, where a settings map constitutes +# a configuration for the assembly code being generated. The map +# contains key value pairs where keys are settings names (strings) and +# the values are booleans (true for enabled, false for disabled). +# + +def computeSettingsCombinations(ast) + settingsCombinations = [] + + def settingsCombinator(settingsCombinations, mapSoFar, remaining) + if remaining.empty? + settingsCombinations << mapSoFar + return + end + + newMap = mapSoFar.dup + newMap[remaining[0]] = true + settingsCombinator(settingsCombinations, newMap, remaining[1..-1]) + + newMap = mapSoFar.dup + newMap[remaining[0]] = false + settingsCombinator(settingsCombinations, newMap, remaining[1..-1]) + end + + settingsCombinator(settingsCombinations, {}, (ast.filter(Setting).uniq.collect{|v| v.name} + ["X86", "ARMv7"]).uniq) + + settingsCombinations +end + +# +# forSettings(concreteSettings, ast) { +# | concreteSettings, lowLevelAST, backend | ... } +# +# Determines if the settings combination is valid, and if so, calls +# the block with the information you need to generate code. +# + +def forSettings(concreteSettings, ast) + # Check which architectures this combinator claims to support. + numClaimedBackends = 0 + selectedBackend = nil + BACKENDS.each { + | backend | + isSupported = concreteSettings[backend] + raise unless isSupported != nil + numClaimedBackends += if isSupported then 1 else 0 end + if isSupported + selectedBackend = backend + end + } + + return if numClaimedBackends > 1 + + # Resolve the AST down to a low-level form (no macros or conditionals). + lowLevelAST = ast.resolveSettings(concreteSettings) + + yield concreteSettings, lowLevelAST, selectedBackend +end + +# +# forEachValidSettingsCombination(ast) { +# | concreteSettings, ast, backend, index | ... } +# +# forEachValidSettingsCombination(ast, settingsCombinations) { +# | concreteSettings, ast, backend, index | ... } +# +# Executes the given block for each valid settings combination in the +# settings map. The ast passed into the block is resolved +# (ast.resolve) against the settings. +# +# The first form will call computeSettingsCombinations(ast) for you. +# + +def forEachValidSettingsCombination(ast, *optionalSettingsCombinations) + raise if optionalSettingsCombinations.size > 1 + + if optionalSettingsCombinations.empty? + settingsCombinations = computeSettingsCombinations(ast) + else + settingsCombinations = optionalSettingsCombiations[0] + end + + settingsCombinations.each_with_index { + | concreteSettings, index | + forSettings(concreteSettings, ast) { + | concreteSettings_, lowLevelAST, backend | + yield concreteSettings, lowLevelAST, backend, index + } + } +end + +# +# cppSettingsTest(concreteSettings) +# +# Returns the C++ code used to test if we are in a configuration that +# corresponds to the given concrete settings. +# + +def cppSettingsTest(concreteSettings) + "#if " + concreteSettings.to_a.collect{ + | pair | + (if pair[1] + "" + else + "!" + end) + "OFFLINE_ASM_" + pair[0] + }.join(" && ") +end + +# +# isASTErroneous(ast) +# +# Tests to see if the AST claims that there is an error - i.e. if the +# user's code, after settings resolution, has Error nodes. +# + +def isASTErroneous(ast) + not ast.filter(Error).empty? +end + +# +# assertConfiguration(concreteSettings) +# +# Emits a check that asserts that we're using the given configuration. +# + +def assertConfiguration(concreteSettings) + $output.puts cppSettingsTest(concreteSettings) + $output.puts "#else" + $output.puts "#error \"Configuration mismatch.\"" + $output.puts "#endif" +end + +# +# emitCodeInConfiguration(concreteSettings, ast, backend) { +# | concreteSettings, ast, backend | ... } +# +# Emits all relevant guards to see if the configuration holds and +# calls the block if the configuration is not erroneous. +# + +def emitCodeInConfiguration(concreteSettings, ast, backend) + $output.puts cppSettingsTest(concreteSettings) + + if isASTErroneous(ast) + $output.puts "#error \"Invalid configuration.\"" + elsif not WORKING_BACKENDS.include? backend + $output.puts "#error \"This backend is not supported yet.\"" + else + yield concreteSettings, ast, backend + end + + $output.puts "#endif" +end + +# +# emitCodeInAllConfigurations(ast) { +# | concreteSettings, ast, backend, index | ... } +# +# Emits guard codes for all valid configurations, and calls the block +# for those configurations that are valid and not erroneous. +# + +def emitCodeInAllConfigurations(ast) + forEachValidSettingsCombination(ast) { + | concreteSettings, lowLevelAST, backend, index | + $output.puts cppSettingsTest(concreteSettings) + yield concreteSettings, lowLevelAST, backend, index + $output.puts "#endif" + } +end + + + diff --git a/Source/JavaScriptCore/offlineasm/transform.rb b/Source/JavaScriptCore/offlineasm/transform.rb new file mode 100644 index 000000000..5f5024d9e --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/transform.rb @@ -0,0 +1,342 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +require "ast" + +# +# node.resolveSettings(settings) +# +# Construct a new AST that does not have any IfThenElse nodes by +# substituting concrete boolean values for each Setting. +# + +class Node + def resolveSettings(settings) + mapChildren { + | child | + child.resolveSettings(settings) + } + end +end + +class True + def resolveSettings(settings) + self + end +end + +class False + def resolveSettings(settings) + self + end +end + +class Setting + def resolveSettings(settings) + settings[@name].asNode + end +end + +class And + def resolveSettings(settings) + (@left.resolveSettings(settings).value and @right.resolveSettings(settings).value).asNode + end +end + +class Or + def resolveSettings(settings) + (@left.resolveSettings(settings).value or @right.resolveSettings(settings).value).asNode + end +end + +class Not + def resolveSettings(settings) + (not @child.resolveSettings(settings).value).asNode + end +end + +class IfThenElse + def resolveSettings(settings) + if @predicate.resolveSettings(settings).value + @thenCase.resolveSettings(settings) + else + @elseCase.resolveSettings(settings) + end + end +end + +class Sequence + def resolveSettings(settings) + newList = [] + @list.each { + | item | + item = item.resolveSettings(settings) + if item.is_a? Sequence + newList += item.list + else + newList << item + end + } + Sequence.new(codeOrigin, newList) + end +end + +# +# node.demacroify(macros) +# node.substitute(mapping) +# +# demacroify() constructs a new AST that does not have any Macro +# nodes, while substitute() replaces Variable nodes with the given +# nodes in the mapping. +# + +class Node + def demacroify(macros) + mapChildren { + | child | + child.demacroify(macros) + } + end + + def substitute(mapping) + mapChildren { + | child | + child.substitute(mapping) + } + end + + def substituteLabels(mapping) + mapChildren { + | child | + child.substituteLabels(mapping) + } + end +end + +class Macro + def substitute(mapping) + myMapping = {} + mapping.each_pair { + | key, value | + unless @variables.include? key + myMapping[key] = value + end + } + mapChildren { + | child | + child.substitute(myMapping) + } + end +end + +class Variable + def substitute(mapping) + if mapping[self] + mapping[self] + else + self + end + end +end + +class LocalLabel + def substituteLabels(mapping) + if mapping[self] + mapping[self] + else + self + end + end +end + +class Sequence + def substitute(constants) + newList = [] + myConstants = constants.dup + @list.each { + | item | + if item.is_a? ConstDecl + myConstants[item.variable] = item.value.substitute(myConstants) + else + newList << item.substitute(myConstants) + end + } + Sequence.new(codeOrigin, newList) + end + + def renameLabels(comment) + mapping = {} + + @list.each { + | item | + if item.is_a? LocalLabel + mapping[item] = LocalLabel.unique(if comment then comment + "_" else "" end + item.cleanName) + end + } + + substituteLabels(mapping) + end + + def demacroify(macros) + myMacros = macros.dup + @list.each { + | item | + if item.is_a? Macro + myMacros[item.name] = item + end + } + newList = [] + @list.each { + | item | + if item.is_a? Macro + # Ignore. + elsif item.is_a? MacroCall + mapping = {} + myMyMacros = myMacros.dup + raise "Could not find macro #{item.name} at #{item.codeOriginString}" unless myMacros[item.name] + raise "Argument count mismatch for call to #{item.name} at #{item.codeOriginString}" unless item.operands.size == myMacros[item.name].variables.size + item.operands.size.times { + | idx | + if item.operands[idx].is_a? Variable and myMacros[item.operands[idx].name] + myMyMacros[myMacros[item.name].variables[idx].name] = myMacros[item.operands[idx].name] + mapping[myMacros[item.name].variables[idx].name] = nil + elsif item.operands[idx].is_a? Macro + myMyMacros[myMacros[item.name].variables[idx].name] = item.operands[idx] + mapping[myMacros[item.name].variables[idx].name] = nil + else + myMyMacros[myMacros[item.name].variables[idx]] = nil + mapping[myMacros[item.name].variables[idx]] = item.operands[idx] + end + } + newList += myMacros[item.name].body.substitute(mapping).demacroify(myMyMacros).renameLabels(item.name).list + else + newList << item.demacroify(myMacros) + end + } + Sequence.new(codeOrigin, newList).substitute({}) + end +end + +# +# node.resolveOffsets(offsets, sizes) +# +# Construct a new AST that has offset values instead of symbolic +# offsets. +# + +class Node + def resolveOffsets(offsets, sizes) + mapChildren { + | child | + child.resolveOffsets(offsets, sizes) + } + end +end + +class StructOffset + def resolveOffsets(offsets, sizes) + if offsets[self] + Immediate.new(codeOrigin, offsets[self]) + else + self + end + end +end + +class Sizeof + def resolveOffsets(offsets, sizes) + if sizes[self] + Immediate.new(codeOrigin, sizes[self]) + else + puts "Could not find #{self.inspect} in #{sizes.keys.inspect}" + puts "sizes = #{sizes.inspect}" + self + end + end +end + +# +# node.fold +# +# Resolve constant references and compute arithmetic expressions. +# + +class Node + def fold + mapChildren { + | child | + child.fold + } + end +end + +class AddImmediates + def fold + @left = @left.fold + @right = @right.fold + return self unless @left.is_a? Immediate + return self unless @right.is_a? Immediate + Immediate.new(codeOrigin, @left.value + @right.value) + end +end + +class SubImmediates + def fold + @left = @left.fold + @right = @right.fold + return self unless @left.is_a? Immediate + return self unless @right.is_a? Immediate + Immediate.new(codeOrigin, @left.value - @right.value) + end +end + +class MulImmediates + def fold + @left = @left.fold + @right = @right.fold + return self unless @left.is_a? Immediate + return self unless @right.is_a? Immediate + Immediate.new(codeOrigin, @left.value * @right.value) + end +end + +class NegImmediate + def fold + @child = @child.fold + return self unless @child.is_a? Immediate + Immediate.new(codeOrigin, -@child.value) + end +end + +# +# node.resolveAfterSettings(offsets, sizes) +# +# Compile assembly against a set of offsets. +# + +class Node + def resolve(offsets, sizes) + demacroify({}).resolveOffsets(offsets, sizes).fold + end +end + diff --git a/Source/JavaScriptCore/offlineasm/x86.rb b/Source/JavaScriptCore/offlineasm/x86.rb new file mode 100644 index 000000000..b89f2d90c --- /dev/null +++ b/Source/JavaScriptCore/offlineasm/x86.rb @@ -0,0 +1,681 @@ +# Copyright (C) 2011 Apple Inc. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS +# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. + +class RegisterID + def supports8BitOnX86 + case name + when "t0", "a0", "r0", "t1", "a1", "r1", "t2", "t3" + true + when "t4", "cfr" + false + else + raise + end + end + + def x86Operand(kind) + case name + when "t0", "a0", "r0" + case kind + when :byte + "%al" + when :half + "%ax" + when :int + "%eax" + else + raise + end + when "t1", "a1", "r1" + case kind + when :byte + "%dl" + when :half + "%dx" + when :int + "%edx" + else + raise + end + when "t2" + case kind + when :byte + "%cl" + when :half + "%cx" + when :int + "%ecx" + else + raise + end + when "t3" + case kind + when :byte + "%bl" + when :half + "%bx" + when :int + "%ebx" + else + raise + end + when "t4" + case kind + when :byte + "%sil" + when :half + "%si" + when :int + "%esi" + else + raise + end + when "cfr" + case kind + when :byte + "%dil" + when :half + "%di" + when :int + "%edi" + else + raise + end + when "sp" + case kind + when :byte + "%spl" + when :half + "%sp" + when :int + "%esp" + else + raise + end + else + raise "Bad register #{name} for X86 at #{codeOriginString}" + end + end + def x86CallOperand(kind) + "*#{x86Operand(kind)}" + end +end + +class FPRegisterID + def x86Operand(kind) + raise unless kind == :double + case name + when "ft0", "fa0", "fr" + "%xmm0" + when "ft1", "fa1" + "%xmm1" + when "ft2", "fa2" + "%xmm2" + when "ft3", "fa3" + "%xmm3" + when "ft4" + "%xmm4" + when "ft5" + "%xmm5" + else + raise "Bad register #{name} for X86 at #{codeOriginString}" + end + end + def x86CallOperand(kind) + "*#{x86Operand(kind)}" + end +end + +class Immediate + def x86Operand(kind) + "$#{value}" + end + def x86CallOperand(kind) + "#{value}" + end +end + +class Address + def supports8BitOnX86 + true + end + + def x86Operand(kind) + "#{offset.value}(#{base.x86Operand(:int)})" + end + def x86CallOperand(kind) + "*#{x86Operand(kind)}" + end +end + +class BaseIndex + def supports8BitOnX86 + true + end + + def x86Operand(kind) + "#{offset.value}(#{base.x86Operand(:int)}, #{index.x86Operand(:int)}, #{scale})" + end + + def x86CallOperand(kind) + "*#{x86operand(kind)}" + end +end + +class AbsoluteAddress + def supports8BitOnX86 + true + end + + def x86Operand(kind) + "#{address.value}" + end + + def x86CallOperand(kind) + "*#{address.value}" + end +end + +class LabelReference + def x86CallOperand(kind) + asmLabel + end +end + +class LocalLabelReference + def x86CallOperand(kind) + asmLabel + end +end + +class Instruction + def x86Operands(*kinds) + raise unless kinds.size == operands.size + result = [] + kinds.size.times { + | idx | + result << operands[idx].x86Operand(kinds[idx]) + } + result.join(", ") + end + + def x86Suffix(kind) + case kind + when :byte + "b" + when :half + "w" + when :int + "l" + when :double + "sd" + else + raise + end + end + + def handleX86OpWithNumOperands(opcode, kind, numOperands) + if numOperands == 3 + if operands[0] == operands[2] + $asm.puts "#{opcode} #{operands[1].x86Operand(kind)}, #{operands[2].x86Operand(kind)}" + elsif operands[1] == operands[2] + $asm.puts "#{opcode} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}" + else + $asm.puts "mov#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}, #{operands[2].x86Operand(kind)}" + $asm.puts "#{opcode} #{operands[1].x86Operand(kind)}, #{operands[2].x86Operand(kind)}" + end + else + $asm.puts "#{opcode} #{operands[0].x86Operand(kind)}, #{operands[1].x86Operand(kind)}" + end + end + + def handleX86Op(opcode, kind) + handleX86OpWithNumOperands(opcode, kind, operands.size) + end + + def handleX86Shift(opcode, kind) + if operands[0].is_a? Immediate or operands[0] == RegisterID.forName(nil, "t2") + $asm.puts "#{opcode} #{operands[0].x86Operand(:byte)}, #{operands[1].x86Operand(kind)}" + else + $asm.puts "xchgl #{operands[0].x86Operand(:int)}, %ecx" + $asm.puts "#{opcode} %cl, #{operands[1].x86Operand(kind)}" + $asm.puts "xchgl #{operands[0].x86Operand(:int)}, %ecx" + end + end + + def handleX86DoubleBranch(branchOpcode, mode) + case mode + when :normal + $asm.puts "ucomisd #{operands[1].x86Operand(:double)}, #{operands[0].x86Operand(:double)}" + when :reverse + $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}" + else + raise mode.inspect + end + $asm.puts "#{branchOpcode} #{operands[2].asmLabel}" + end + + def handleX86IntCompare(opcodeSuffix, kind) + if operands[0].is_a? Immediate and operands[0].value == 0 and operands[1].is_a? RegisterID and (opcodeSuffix == "e" or opcodeSuffix == "ne") + $asm.puts "test#{x86Suffix(kind)} #{operands[1].x86Operand(kind)}" + elsif operands[1].is_a? Immediate and operands[1].value == 0 and operands[0].is_a? RegisterID and (opcodeSuffix == "e" or opcodeSuffix == "ne") + $asm.puts "test#{x86Suffix(kind)} #{operands[0].x86Operand(kind)}" + else + $asm.puts "cmp#{x86Suffix(kind)} #{operands[1].x86Operand(kind)}, #{operands[0].x86Operand(kind)}" + end + end + + def handleX86IntBranch(branchOpcode, kind) + handleX86IntCompare(branchOpcode[1..-1], kind) + $asm.puts "#{branchOpcode} #{operands[2].asmLabel}" + end + + def handleX86Set(setOpcode, operand) + if operand.supports8BitOnX86 + $asm.puts "#{setOpcode} #{operand.x86Operand(:byte)}" + $asm.puts "movzbl #{operand.x86Operand(:byte)}, #{operand.x86Operand(:int)}" + else + $asm.puts "xchgl #{operand.x86Operand(:int)}, %eax" + $asm.puts "#{setOpcode} %al" + $asm.puts "movzbl %al, %eax" + $asm.puts "xchgl #{operand.x86Operand(:int)}, %eax" + end + end + + def handleX86IntCompareSet(setOpcode, kind) + handleX86IntCompare(setOpcode[3..-1], kind) + handleX86Set(setOpcode, operands[2]) + end + + def handleX86Test(kind) + value = operands[0] + case operands.size + when 2 + mask = Immediate.new(codeOrigin, -1) + when 3 + mask = operands[1] + else + raise "Expected 2 or 3 operands, but got #{operands.size} at #{codeOriginString}" + end + + if mask.is_a? Immediate and mask.value == -1 + if value.is_a? RegisterID + $asm.puts "test#{x86Suffix(kind)} #{value.x86Operand(kind)}, #{value.x86Operand(kind)}" + else + $asm.puts "cmp#{x86Suffix(kind)} $0, #{value.x86Operand(kind)}" + end + else + $asm.puts "test#{x86Suffix(kind)} #{mask.x86Operand(kind)}, #{value.x86Operand(kind)}" + end + end + + def handleX86BranchTest(branchOpcode, kind) + handleX86Test(kind) + $asm.puts "#{branchOpcode} #{operands.last.asmLabel}" + end + + def handleX86SetTest(setOpcode, kind) + handleX86Test(kind) + handleX86Set(setOpcode, operands.last) + end + + def handleX86OpBranch(opcode, branchOpcode, kind) + handleX86OpWithNumOperands(opcode, kind, operands.size - 1) + case operands.size + when 4 + jumpTarget = operands[3] + when 3 + jumpTarget = operands[2] + else + raise self.inspect + end + $asm.puts "#{branchOpcode} #{jumpTarget.asmLabel}" + end + + def handleX86SubBranch(branchOpcode, kind) + if operands.size == 4 and operands[1] == operands[2] + $asm.puts "negl #{operands[2].x86Operand(:int)}" + $asm.puts "addl #{operands[0].x86Operand(:int)}, #{operands[2].x86Operand(:int)}" + else + handleX86OpWithNumOperands("sub#{x86Suffix(kind)}", kind, operands.size - 1) + end + case operands.size + when 4 + jumpTarget = operands[3] + when 3 + jumpTarget = operands[2] + else + raise self.inspect + end + $asm.puts "#{branchOpcode} #{jumpTarget.asmLabel}" + end + + def lowerX86 + $asm.comment codeOriginString + case opcode + when "addi", "addp" + if operands.size == 3 and operands[0].is_a? Immediate + raise unless operands[1].is_a? RegisterID + raise unless operands[2].is_a? RegisterID + if operands[0].value == 0 + unless operands[1] == operands[2] + $asm.puts "movl #{operands[1].x86Operand(:int)}, #{operands[2].x86Operand(:int)}" + end + else + $asm.puts "leal #{operands[0].value}(#{operands[1].x86Operand(:int)}), #{operands[2].x86Operand(:int)}" + end + elsif operands.size == 3 and operands[0].is_a? RegisterID + raise unless operands[1].is_a? RegisterID + raise unless operands[2].is_a? RegisterID + $asm.puts "leal (#{operands[0].x86Operand(:int)}, #{operands[1].x86Operand(:int)}), #{operands[2].x86Operand(:int)}" + else + unless Immediate.new(nil, 0) == operands[0] + $asm.puts "addl #{x86Operands(:int, :int)}" + end + end + when "andi", "andp" + handleX86Op("andl", :int) + when "lshifti" + handleX86Shift("sall", :int) + when "muli" + if operands.size == 3 and operands[0].is_a? Immediate + $asm.puts "imull #{x86Operands(:int, :int, :int)}" + else + # FIXME: could do some peephole in case the left operand is immediate and it's + # a power of two. + handleX86Op("imull", :int) + end + when "negi" + $asm.puts "negl #{x86Operands(:int)}" + when "noti" + $asm.puts "notl #{x86Operands(:int)}" + when "ori", "orp" + handleX86Op("orl", :int) + when "rshifti" + handleX86Shift("sarl", :int) + when "urshifti" + handleX86Shift("shrl", :int) + when "subi", "subp" + if operands.size == 3 and operands[1] == operands[2] + $asm.puts "negl #{operands[2].x86Operand(:int)}" + $asm.puts "addl #{operands[0].x86Operand(:int)}, #{operands[2].x86Operand(:int)}" + else + handleX86Op("subl", :int) + end + when "xori", "xorp" + handleX86Op("xorl", :int) + when "loadi", "storei", "loadp", "storep" + $asm.puts "movl #{x86Operands(:int, :int)}" + when "loadb" + $asm.puts "movzbl #{operands[0].x86Operand(:byte)}, #{operands[1].x86Operand(:int)}" + when "loadbs" + $asm.puts "movsbl #{operands[0].x86Operand(:byte)}, #{operands[1].x86Operand(:int)}" + when "loadh" + $asm.puts "movzwl #{operands[0].x86Operand(:half)}, #{operands[1].x86Operand(:int)}" + when "loadhs" + $asm.puts "movswl #{operands[0].x86Operand(:half)}, #{operands[1].x86Operand(:int)}" + when "storeb" + $asm.puts "movb #{x86Operands(:byte, :byte)}" + when "loadd", "moved", "stored" + $asm.puts "movsd #{x86Operands(:double, :double)}" + when "addd" + $asm.puts "addsd #{x86Operands(:double, :double)}" + when "divd" + $asm.puts "divsd #{x86Operands(:double, :double)}" + when "subd" + $asm.puts "subsd #{x86Operands(:double, :double)}" + when "muld" + $asm.puts "mulsd #{x86Operands(:double, :double)}" + when "sqrtd" + $asm.puts "sqrtsd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}" + when "ci2d" + $asm.puts "cvtsi2sd #{operands[0].x86Operand(:int)}, #{operands[1].x86Operand(:double)}" + when "bdeq" + isUnordered = LocalLabel.unique("bdeq") + $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}" + $asm.puts "jp #{LabelReference.new(codeOrigin, isUnordered).asmLabel}" + $asm.puts "je #{LabelReference.new(codeOrigin, operands[2]).asmLabel}" + isUnordered.lower("X86") + when "bdneq" + handleX86DoubleBranch("jne", :normal) + when "bdgt" + handleX86DoubleBranch("ja", :normal) + when "bdgteq" + handleX86DoubleBranch("jae", :normal) + when "bdlt" + handleX86DoubleBranch("ja", :reverse) + when "bdlteq" + handleX86DoubleBranch("jae", :reverse) + when "bdequn" + handleX86DoubleBranch("je", :normal) + when "bdnequn" + isUnordered = LocalLabel.unique("bdnequn") + isEqual = LocalLabel.unique("bdnequn") + $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:double)}" + $asm.puts "jp #{LabelReference.new(codeOrigin, isUnordered).asmLabel}" + $asm.puts "je #{LabelReference.new(codeOrigin, isEqual).asmLabel}" + isUnordered.lower("X86") + $asm.puts "jmp #{operands[2].asmLabel}" + isEqual.lower("X86") + when "bdgtun" + handleX86DoubleBranch("jb", :reverse) + when "bdgtequn" + handleX86DoubleBranch("jbe", :reverse) + when "bdltun" + handleX86DoubleBranch("jb", :normal) + when "bdltequn" + handleX86DoubleBranch("jbe", :normal) + when "btd2i" + $asm.puts "cvttsd2si #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}" + $asm.puts "cmpl $0x80000000 #{operands[1].x86Operand(:int)}" + $asm.puts "je #{operands[2].asmLabel}" + when "td2i" + $asm.puts "cvttsd2si #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}" + when "bcd2i" + $asm.puts "cvttsd2si #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}" + $asm.puts "testl #{operands[1].x86Operand(:int)}, #{operands[1].x86Operand(:int)}" + $asm.puts "je #{operands[2].asmLabel}" + $asm.puts "cvtsi2sd #{operands[1].x86Operand(:int)}, %xmm7" + $asm.puts "ucomisd #{operands[0].x86Operand(:double)}, %xmm7" + $asm.puts "jp #{operands[2].asmLabel}" + $asm.puts "jne #{operands[2].asmLabel}" + when "movdz" + $asm.puts "xorpd #{operands[0].x86Operand(:double)}, #{operands[0].x86Operand(:double)}" + when "pop" + $asm.puts "pop #{operands[0].x86Operand(:int)}" + when "push" + $asm.puts "push #{operands[0].x86Operand(:int)}" + when "move", "sxi2p", "zxi2p" + if Immediate.new(nil, 0) == operands[0] and operands[1].is_a? RegisterID + $asm.puts "xorl #{operands[1].x86Operand(:int)}, #{operands[1].x86Operand(:int)}" + elsif operands[0] != operands[1] + $asm.puts "movl #{x86Operands(:int, :int)}" + end + when "nop" + $asm.puts "nop" + when "bieq", "bpeq" + handleX86IntBranch("je", :int) + when "bineq", "bpneq" + handleX86IntBranch("jne", :int) + when "bia", "bpa" + handleX86IntBranch("ja", :int) + when "biaeq", "bpaeq" + handleX86IntBranch("jae", :int) + when "bib", "bpb" + handleX86IntBranch("jb", :int) + when "bibeq", "bpbeq" + handleX86IntBranch("jbe", :int) + when "bigt", "bpgt" + handleX86IntBranch("jg", :int) + when "bigteq", "bpgteq" + handleX86IntBranch("jge", :int) + when "bilt", "bplt" + handleX86IntBranch("jl", :int) + when "bilteq", "bplteq" + handleX86IntBranch("jle", :int) + when "bbeq" + handleX86IntBranch("je", :byte) + when "bbneq" + handleX86IntBranch("jne", :byte) + when "bba" + handleX86IntBranch("ja", :byte) + when "bbaeq" + handleX86IntBranch("jae", :byte) + when "bbb" + handleX86IntBranch("jb", :byte) + when "bbbeq" + handleX86IntBranch("jbe", :byte) + when "bbgt" + handleX86IntBranch("jg", :byte) + when "bbgteq" + handleX86IntBranch("jge", :byte) + when "bblt" + handleX86IntBranch("jl", :byte) + when "bblteq" + handleX86IntBranch("jlteq", :byte) + when "btio", "btpo" + handleX86BranchTest("jo", :int) + when "btis", "btps" + handleX86BranchTest("js", :int) + when "btiz", "btpz" + handleX86BranchTest("jz", :int) + when "btinz", "btpnz" + handleX86BranchTest("jnz", :int) + when "btbo" + handleX86BranchTest("jo", :byte) + when "btbs" + handleX86BranchTest("js", :byte) + when "btbz" + handleX86BranchTest("jz", :byte) + when "btbnz" + handleX86BranchTest("jnz", :byte) + when "jmp" + $asm.puts "jmp #{operands[0].x86CallOperand(:int)}" + when "baddio", "baddpo" + handleX86OpBranch("addl", "jo", :int) + when "baddis", "baddps" + handleX86OpBranch("addl", "js", :int) + when "baddiz", "baddpz" + handleX86OpBranch("addl", "jz", :int) + when "baddinz", "baddpnz" + handleX86OpBranch("addl", "jnz", :int) + when "bsubio" + handleX86SubBranch("jo", :int) + when "bsubis" + handleX86SubBranch("js", :int) + when "bsubiz" + handleX86SubBranch("jz", :int) + when "bsubinz" + handleX86SubBranch("jnz", :int) + when "bmulio" + handleX86OpBranch("imull", "jo", :int) + when "bmulis" + handleX86OpBranch("imull", "js", :int) + when "bmuliz" + handleX86OpBranch("imull", "jz", :int) + when "bmulinz" + handleX86OpBranch("imull", "jnz", :int) + when "borio" + handleX86OpBranch("orl", "jo", :int) + when "boris" + handleX86OpBranch("orl", "js", :int) + when "boriz" + handleX86OpBranch("orl", "jz", :int) + when "borinz" + handleX86OpBranch("orl", "jnz", :int) + when "break" + $asm.puts "int $3" + when "call" + $asm.puts "call #{operands[0].x86CallOperand(:int)}" + when "ret" + $asm.puts "ret" + when "cieq", "cpeq" + handleX86IntCompareSet("sete", :int) + when "cineq", "cpneq" + handleX86IntCompareSet("setne", :int) + when "cia", "cpa" + handleX86IntCompareSet("seta", :int) + when "ciaeq", "cpaeq" + handleX86IntCompareSet("setae", :int) + when "cib", "cpb" + handleX86IntCompareSet("setb", :int) + when "cibeq", "cpbeq" + handleX86IntCompareSet("setbe", :int) + when "cigt", "cpgt" + handleX86IntCompareSet("setg", :int) + when "cigteq", "cpgteq" + handleX86IntCompareSet("setge", :int) + when "cilt", "cplt" + handleX86IntCompareSet("setl", :int) + when "cilteq", "cplteq" + handleX86IntCompareSet("setle", :int) + when "tio" + handleX86SetTest("seto", :int) + when "tis" + handleX86SetTest("sets", :int) + when "tiz" + handleX86SetTest("setz", :int) + when "tinz" + handleX86SetTest("setnz", :int) + when "tbo" + handleX86SetTest("seto", :byte) + when "tbs" + handleX86SetTest("sets", :byte) + when "tbz" + handleX86SetTest("setz", :byte) + when "tbnz" + handleX86SetTest("setnz", :byte) + when "peek" + $asm.puts "movl #{operands[0].value * 4}(%esp), #{operands[1].x86Operand(:int)}" + when "poke" + $asm.puts "movl #{operands[0].x86Operand(:int)}, #{operands[1].value * 4}(%esp)" + when "cdqi" + $asm.puts "cdq" + when "idivi" + $asm.puts "idivl #{operands[0].x86Operand(:int)}" + when "fii2d" + $asm.puts "movd #{operands[0].x86Operand(:int)}, #{operands[2].x86Operand(:double)}" + $asm.puts "movd #{operands[1].x86Operand(:int)}, %xmm7" + $asm.puts "psllq $32, %xmm7" + $asm.puts "por %xmm7, #{operands[2].x86Operand(:double)}" + when "fd2ii" + $asm.puts "movd #{operands[0].x86Operand(:double)}, #{operands[1].x86Operand(:int)}" + $asm.puts "movsd #{operands[0].x86Operand(:double)}, %xmm7" + $asm.puts "psrlq $32, %xmm7" + $asm.puts "movsd %xmm7, #{operands[2].x86Operand(:int)}" + when "bo" + $asm.puts "jo #{operands[0].asmLabel}" + when "bs" + $asm.puts "js #{operands[0].asmLabel}" + when "bz" + $asm.puts "jz #{operands[0].asmLabel}" + when "bnz" + $asm.puts "jnz #{operands[0].asmLabel}" + when "leai", "leap" + $asm.puts "leal #{operands[0].x86Operand(:int)}, #{operands[1].x86Operand(:int)}" + else + raise "Bad opcode: #{opcode}" + end + end +end + |