|  | #!/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=<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 = 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" || 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) | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |