blob: 0fe0fc6a7adda6fb416408213d3339e9d62b581d [file] [log] [blame]
#!/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)
}
}
}
}
}