#!/usr/bin/env ruby # Copyright (C) 2011, 2016 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 'optparse' require "parser" require "self_hash" require "settings" require "transform" class Assembler def initialize(outp) @outp = outp @state = :cpp resetAsm end def resetAsm @commentState = :none @comment = nil @internalComment = nil @annotation = nil @codeOrigin = nil @numLocalLabels = 0 @numGlobalLabels = 0 @deferredActions = [] @deferredNextLabelActions = [] @count = 0 @newlineSpacerState = :none @lastlabel = "" end def enterAsm @outp.puts "OFFLINE_ASM_BEGIN" if !$emitWinAsm if !$emitWinAsm @outp.puts "OFFLINE_ASM_GLOBAL_LABEL(llintPCRangeStart)" else putsProc("llintPCRangeStart", "") putsProcEndIfNeeded end @state = :asm SourceFile.outputDotFileList(@outp) if $enableDebugAnnotations end def leaveAsm putsProcEndIfNeeded if $emitWinAsm if !$emitWinAsm @outp.puts "OFFLINE_ASM_GLOBAL_LABEL(llintPCRangeEnd)" else putsProc("llintPCRangeEnd", "") putsProcEndIfNeeded end putsLastComment (@deferredNextLabelActions + @deferredActions).each { | action | action.call() } @outp.puts "OFFLINE_ASM_END" if !$emitWinAsm @state = :cpp end def deferAction(&proc) @deferredActions << proc end def deferNextLabelAction(&proc) @deferredNextLabelActions << proc end def newUID @count += 1 @count end def inAsm resetAsm 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 = $commentPrefix + " " + 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 if !$emitWinAsm @outp.puts(formatDump(" \"\\t" + line.join('') + "\\n\"", lastComment)) else @outp.puts(formatDump(" " + line.join(''), lastComment)) end 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 putsProc(label, comment) raise unless $emitWinAsm @outp.puts(formatDump("#{label} PROC PUBLIC", comment)) @lastlabel = label end def putsProcEndIfNeeded raise unless $emitWinAsm if @lastlabel != "" @outp.puts("#{@lastlabel} ENDP") end @lastlabel = "" end def putsLabel(labelName, isGlobal) raise unless @state == :asm @deferredNextLabelActions.each { | action | action.call() } @deferredNextLabelActions = [] @numGlobalLabels += 1 putsProcEndIfNeeded if $emitWinAsm and isGlobal putsNewlineSpacerIfAppropriate(:global) @internalComment = $enableLabelCountComments ? "Global Label #{@numGlobalLabels}" : nil if isGlobal if !$emitWinAsm @outp.puts(formatDump("OFFLINE_ASM_GLOBAL_LABEL(#{labelName})", lastComment)) else putsProc(labelName, lastComment) end elsif /\Allint_op_/.match(labelName) if !$emitWinAsm @outp.puts(formatDump("OFFLINE_ASM_OPCODE_LABEL(op_#{$~.post_match})", lastComment)) else label = "llint_" + "op_#{$~.post_match}" @outp.puts(formatDump(" _#{label}:", lastComment)) end else if !$emitWinAsm @outp.puts(formatDump("OFFLINE_ASM_GLUE_LABEL(#{labelName})", lastComment)) else @outp.puts(formatDump(" _#{labelName}:", lastComment)) end 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 if !$emitWinAsm @outp.puts(formatDump(" OFFLINE_ASM_LOCAL_LABEL(#{labelName})", lastComment)) else @outp.puts(formatDump(" #{labelName}:", lastComment)) end end def self.externLabelReference(labelName) if !$emitWinAsm "\" LOCAL_REFERENCE(#{labelName}) \"" else "#{labelName}" end end def self.labelReference(labelName) if !$emitWinAsm "\" LOCAL_LABEL_STRING(#{labelName}) \"" else "_#{labelName}" end end def self.localLabelReference(labelName) if !$emitWinAsm "\" LOCAL_LABEL_STRING(#{labelName}) \"" else "#{labelName}" end 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 " " + $commentPrefix + " #{@codeOrigin}" @outp.puts " " + $commentPrefix + " #{text}" end @codeOrigin = nil @commentState = :many when :many @outp.puts $commentPrefix + " #{text}" if $enableCodeOriginComments else raise end end def comment(text) @comment = text end def annotation(text) @annotation = text end def debugAnnotation(text) @outp.puts text end end IncludeFile.processIncludeOptions() asmFile = ARGV.shift offsetsFile = ARGV.shift outputFlnm = ARGV.shift $options = {} OptionParser.new do |opts| opts.banner = "Usage: asm.rb asmFile offsetsFile outputFileName [--assembler=]" # This option is currently only used to specify the masm assembler opts.on("--assembler=[ASM]", "Specify an assembler to use.") do |assembler| $options[:assembler] = assembler end end.parse! begin configurationList = offsetsAndConfigurationIndex(offsetsFile) rescue MissingMagicValuesException $stderr.puts "offlineasm: No magic values found. Skipping assembly file generation." exit 1 end # The MS compiler doesn't accept DWARF2 debug annotations. if isMSVC $enableDebugAnnotations = false end $emitWinAsm = isMSVC ? outputFlnm.index(".asm") != nil : false $commentPrefix = $emitWinAsm ? ";" : "//" inputHash = $commentPrefix + " offlineasm input hash: " + parseHash(asmFile) + " " + Digest::SHA1.hexdigest(configurationList.map{|v| (v[0] + [v[1]]).join(' ')}.join(' ')) + " " + selfHash + " " + Digest::SHA1.hexdigest($options.has_key?(:assembler) ? $options[:assembler] : "") 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) settingsCombinations = computeSettingsCombinations(ast) configurationList.each { | configuration | offsetsList = configuration[0] configIndex = configuration[1] forSettings(settingsCombinations[configIndex], ast) { | concreteSettings, lowLevelAST, backend | # There could be multiple backends we are generating for, but the C_LOOP is # always by itself so this check to turn off $enableDebugAnnotations won't # affect the generation for any other backend. if backend == "C_LOOP" $enableDebugAnnotations = false end lowLevelAST = lowLevelAST.demacroify({}) lowLevelAST = lowLevelAST.resolve(buildOffsetsMap(lowLevelAST, offsetsList)) lowLevelAST.validate emitCodeInConfiguration(concreteSettings, lowLevelAST, backend) { $currentSettings = concreteSettings $asm.inAsm { lowLevelAST.lower(backend) } } } } }