| #!/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 | 
 | variants = ARGV.shift.split(/[,\s]+/) | 
 |  | 
 | $options = {} | 
 | OptionParser.new do |opts| | 
 |     opts.banner = "Usage: asm.rb asmFile offsetsFile outputFileName [--assembler=<ASM>]" | 
 |     # 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 = offsetsAndConfigurationIndexForVariants(offsetsFile, variants) | 
 | 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 | 
 |     lastLine = nil | 
 |     File.open(outputFlnm, "r") { | 
 |         | file | | 
 |         file.each_line { | 
 |             | line | | 
 |             line = line.chomp | 
 |             unless line.empty? | 
 |                 lastLine = line | 
 |             end | 
 |         } | 
 |     } | 
 |     if lastLine and lastLine == inputHash | 
 |         # Nothing changed. | 
 |         exit 0 | 
 |     end | 
 | end | 
 |  | 
 | File.open(outputFlnm, "w") { | 
 |     | outp | | 
 |     $output = outp | 
 |  | 
 |     $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" || backend == "C_LOOP_WIN" | 
 |                 $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) | 
 |                 } | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     $output.fsync | 
 |     $output.puts inputHash | 
 | } |