| #!/usr/bin/env ruby |
| |
| # Copyright (C) 2011-2021 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 "shellwords" |
| require "transform" |
| |
| class Assembler |
| def initialize(outp) |
| @outp = outp |
| @state = :cpp |
| resetAsm |
| end |
| |
| def resetAsm |
| @comment = nil |
| @internalComment = nil |
| @annotation = nil |
| @codeOrigin = nil |
| @numLocalLabels = 0 |
| @numGlobalLabels = 0 |
| @deferredActions = [] |
| @deferredNextLabelActions = [] |
| @count = 0 |
| @debugAnnotationStr = nil |
| @lastDebugAnnotationStr = nil |
| |
| @newlineSpacerState = :none |
| @lastlabel = "" |
| end |
| |
| def enterAsm |
| @outp.puts "" |
| putStr "OFFLINE_ASM_BEGIN" if !$emitWinAsm |
| |
| if !$emitWinAsm |
| putStr "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 |
| putStr "OFFLINE_ASM_GLOBAL_LABEL(llintPCRangeEnd)" |
| else |
| putsProc("llintPCRangeEnd", "") |
| putsProcEndIfNeeded |
| end |
| putsLastComment |
| (@deferredNextLabelActions + @deferredActions).each { |
| | action | |
| action.call() |
| } |
| putStr "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 $enableCodeOriginComments and @codeOrigin and @codeOrigin != @lastCodeOrigin |
| @lastCodeOrigin = @codeOrigin |
| 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. |
| @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) |
| result = "" |
| if comment.length > 0 |
| result = "%-#{commentColumns}s %s" % [dumpStr, comment] |
| else |
| result = dumpStr |
| end |
| if $enableDebugAnnotations |
| if @debugAnnotationStr and @debugAnnotationStr != @lastDebugAnnotationStr |
| result = "%-#{$preferredDebugAnnotationColumns}s%s" % [@debugAnnotationStr, result] |
| else |
| result = "%-#{$preferredDebugAnnotationColumns}s%s" % ["", result] |
| end |
| @lastDebugAnnotationStr = @debugAnnotationStr |
| @debugAnnotationStr = nil |
| end |
| result |
| 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 putStr(str) |
| if $enableDebugAnnotations |
| @outp.puts "%-#{$preferredDebugAnnotationColumns}s%s" % ["", str] |
| else |
| @outp.puts str |
| end |
| end |
| |
| def puts(*line) |
| raise unless @state == :asm |
| if !$emitWinAsm |
| @outp.puts(formatDump(" \"" + 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 |
| if $emitELFDebugDirectives |
| deferNextLabelAction { |
| putStr(" \".size #{labelName} , . - #{labelName} \\n\"") |
| putStr(" \".type #{labelName} , function \\n\"") |
| } |
| 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) |
| @codeOrigin = text |
| end |
| |
| def comment(text) |
| @comment = text |
| end |
| |
| def annotation(text) |
| @annotation = text |
| end |
| |
| def debugAnnotation(text) |
| @debugAnnotationStr = 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>] [--webkit-additions-path=<path>] [--binary-format=<format>] [--depfile=<depfile>]" |
| # 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 |
| opts.on("--webkit-additions-path=PATH", "WebKitAdditions path.") do |path| |
| $options[:webkit_additions_path] = path |
| end |
| opts.on("--binary-format=FORMAT", "Specify the binary format used by the target system.") do |format| |
| $options[:binary_format] = format |
| end |
| opts.on("--depfile=DEPFILE", "Path to write Makefile-style discovered dependencies to.") do |path| |
| $options[:depfile] = path |
| 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 ? ";" : "//" |
| |
| # We want this in all ELF systems we support, except for C_LOOP (we'll disable it later on if we are building cloop) |
| $emitELFDebugDirectives = $options.has_key?(:binary_format) && $options[:binary_format] == "ELF" |
| |
| inputHash = |
| $commentPrefix + " offlineasm input hash: " + parseHash(asmFile, $options) + |
| " " + 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) and (not $options[:depfile] or FileTest.exist?($options[:depfile])) |
| 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) |
| |
| sources = Set.new |
| ast = parse(asmFile, $options, sources) |
| settingsCombinations = computeSettingsCombinations(ast) |
| |
| if $options[:depfile] |
| depfile = File.open($options[:depfile], "w") |
| depfile.print(Shellwords.escape(outputFlnm), ": ") |
| depfile.puts(Shellwords.join(sources.sort)) |
| end |
| |
| 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 |
| $preferredCommentStartColumn = 60 |
| $emitELFDebugDirectives = 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 |
| } |