#!/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 "config" 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 @internalComment = nil @annotation = nil @codeOrigin = nil @numLocalLabels = 0 @numGlobalLabels = 0 @newlineSpacerState = :none end def enterAsm @outp.puts "OFFLINE_ASM_BEGIN" @state = :asm end def leaveAsm putsLastComment @outp.puts "OFFLINE_ASM_END" @state = :cpp end def inAsm enterAsm yield leaveAsm end # Concatenates all the various components of the comment to dump. def lastComment separator = " " result = "" result = "#{@comment}" if @comment if @annotation and $enableInstrAnnotations result += separator if result != "" result += "#{@annotation}" end if @internalComment result += separator if result != "" result += "#{@internalComment}" end if @codeOrigin and $enableCodeOriginComments result += separator if result != "" result += "#{@codeOrigin}" end if result != "" result = "// " + result end # Reset all the components that we've just sent to be dumped. @commentState = :none @comment = nil @annotation = nil @codeOrigin = nil @internalComment = nil result end # Puts a C Statement in the output stream. def putc(*line) raise unless @state == :asm @outp.puts(formatDump(" " + line.join(''), lastComment)) end def formatDump(dumpStr, comment, commentColumns=$preferredCommentStartColumn) if comment.length > 0 "%-#{commentColumns}s %s" % [dumpStr, comment] else dumpStr end end # private method for internal use only. def putAnnotation(text) raise unless @state == :asm if $enableInstrAnnotations @outp.puts text @annotation = nil end end def putLocalAnnotation() putAnnotation " // #{@annotation}" if @annotation end def putGlobalAnnotation() putsNewlineSpacerIfAppropriate(:annotation) putAnnotation "// #{@annotation}" if @annotation end def putsLastComment comment = lastComment unless comment.empty? @outp.puts comment end end def puts(*line) raise unless @state == :asm @outp.puts(formatDump(" \"\\t" + line.join('') + "\\n\"", lastComment)) end def print(line) raise unless @state == :asm @outp.print("\"" + line + "\"") end def putsNewlineSpacerIfAppropriate(state) if @newlineSpacerState != state @outp.puts("\n") @newlineSpacerState = state end end def putsLabel(labelName) raise unless @state == :asm @numGlobalLabels += 1 putsNewlineSpacerIfAppropriate(:global) @internalComment = $enableLabelCountComments ? "Global Label #{@numGlobalLabels}" : nil if /\Allint_op_/.match(labelName) @outp.puts(formatDump("OFFLINE_ASM_OPCODE_LABEL(op_#{$~.post_match})", lastComment)) else @outp.puts(formatDump("OFFLINE_ASM_GLUE_LABEL(#{labelName})", lastComment)) end @newlineSpacerState = :none # After a global label, we can use another spacer. end def putsLocalLabel(labelName) raise unless @state == :asm @numLocalLabels += 1 @outp.puts("\n") @internalComment = $enableLabelCountComments ? "Local Label #{@numLocalLabels}" : nil @outp.puts(formatDump(" OFFLINE_ASM_LOCAL_LABEL(#{labelName})", lastComment)) end def self.labelReference(labelName) "\" LOCAL_REFERENCE(#{labelName}) \"" end def self.localLabelReference(labelName) "\" LOCAL_LABEL_STRING(#{labelName}) \"" end def self.cLabelReference(labelName) if /\Allint_op_/.match(labelName) "op_#{$~.post_match}" # strip opcodes of their llint_ prefix. else "#{labelName}" end end def self.cLocalLabelReference(labelName) "#{labelName}" end def codeOrigin(text) case @commentState when :none @codeOrigin = text @commentState = :one when :one if $enableCodeOriginComments @outp.puts " // #{@codeOrigin}" @outp.puts " // #{text}" end @codeOrigin = nil @commentState = :many when :many @outp.puts "// #{text}" if $enableCodeOriginComments else raise end end def comment(text) @comment = text end def annotation(text) @annotation = text end end asmFile = ARGV.shift offsetsFile = ARGV.shift outputFlnm = ARGV.shift $stderr.puts "offlineasm: Parsing #{asmFile} and #{offsetsFile} and creating assembly file #{outputFlnm}." begin configurationList = offsetsAndConfigurationIndex(offsetsFile) rescue MissingMagicValuesException $stderr.puts "offlineasm: No magic values found. Skipping assembly file generation." exit 0 end inputHash = "// offlineasm input hash: " + parseHash(asmFile) + " " + 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(asmFile) configurationList.each { | configuration | offsetsList = configuration[0] configIndex = configuration[1] forSettings(computeSettingsCombinations(ast)[configIndex], ast) { | concreteSettings, lowLevelAST, backend | lowLevelAST = lowLevelAST.resolve(*buildOffsetsMap(lowLevelAST, offsetsList)) lowLevelAST.validate emitCodeInConfiguration(concreteSettings, lowLevelAST, backend) { $asm.inAsm { lowLevelAST.lower(backend) } } } } } $stderr.puts "offlineasm: Assembly file #{outputFlnm} successfully generated."