mirror of
https://github.com/darlinghq/darling-JavaScriptCore.git
synced 2025-04-08 18:01:37 +00:00
422 lines
12 KiB
Ruby
422 lines
12 KiB
Ruby
#!/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
|
|
}
|