1 | #!/usr/bin/env ruby
|
---|
2 |
|
---|
3 | # Copyright (C) 2011-2021 Apple Inc. All rights reserved.
|
---|
4 | #
|
---|
5 | # Redistribution and use in source and binary forms, with or without
|
---|
6 | # modification, are permitted provided that the following conditions
|
---|
7 | # are met:
|
---|
8 | # 1. Redistributions of source code must retain the above copyright
|
---|
9 | # notice, this list of conditions and the following disclaimer.
|
---|
10 | # 2. Redistributions in binary form must reproduce the above copyright
|
---|
11 | # notice, this list of conditions and the following disclaimer in the
|
---|
12 | # documentation and/or other materials provided with the distribution.
|
---|
13 | #
|
---|
14 | # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
|
---|
15 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
---|
16 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
---|
17 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
|
---|
18 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
---|
19 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
---|
20 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
---|
21 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
---|
22 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
---|
23 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
---|
24 | # THE POSSIBILITY OF SUCH DAMAGE.
|
---|
25 |
|
---|
26 | $: << File.dirname(__FILE__)
|
---|
27 |
|
---|
28 | require "config"
|
---|
29 | require "backends"
|
---|
30 | require "digest/sha1"
|
---|
31 | require "offsets"
|
---|
32 | require 'optparse'
|
---|
33 | require "parser"
|
---|
34 | require "self_hash"
|
---|
35 | require "settings"
|
---|
36 | require "shellwords"
|
---|
37 | require "transform"
|
---|
38 |
|
---|
39 | class Assembler
|
---|
40 | def initialize(outp)
|
---|
41 | @outp = outp
|
---|
42 | @state = :cpp
|
---|
43 | resetAsm
|
---|
44 | end
|
---|
45 |
|
---|
46 | def resetAsm
|
---|
47 | @comment = nil
|
---|
48 | @internalComment = nil
|
---|
49 | @annotation = nil
|
---|
50 | @codeOrigin = nil
|
---|
51 | @numLocalLabels = 0
|
---|
52 | @numGlobalLabels = 0
|
---|
53 | @deferredActions = []
|
---|
54 | @deferredNextLabelActions = []
|
---|
55 | @count = 0
|
---|
56 | @debugAnnotationStr = nil
|
---|
57 | @lastDebugAnnotationStr = nil
|
---|
58 |
|
---|
59 | @newlineSpacerState = :none
|
---|
60 | @lastlabel = ""
|
---|
61 | end
|
---|
62 |
|
---|
63 | def enterAsm
|
---|
64 | @outp.puts ""
|
---|
65 | putStr "OFFLINE_ASM_BEGIN" if !$emitWinAsm
|
---|
66 |
|
---|
67 | if !$emitWinAsm
|
---|
68 | putStr "OFFLINE_ASM_GLOBAL_LABEL(llintPCRangeStart)"
|
---|
69 | else
|
---|
70 | putsProc("llintPCRangeStart", "")
|
---|
71 | putsProcEndIfNeeded
|
---|
72 | end
|
---|
73 | @state = :asm
|
---|
74 | SourceFile.outputDotFileList(@outp) if $enableDebugAnnotations
|
---|
75 | end
|
---|
76 |
|
---|
77 | def leaveAsm
|
---|
78 | putsProcEndIfNeeded if $emitWinAsm
|
---|
79 | if !$emitWinAsm
|
---|
80 | putStr "OFFLINE_ASM_GLOBAL_LABEL(llintPCRangeEnd)"
|
---|
81 | else
|
---|
82 | putsProc("llintPCRangeEnd", "")
|
---|
83 | putsProcEndIfNeeded
|
---|
84 | end
|
---|
85 | putsLastComment
|
---|
86 | (@deferredNextLabelActions + @deferredActions).each {
|
---|
87 | | action |
|
---|
88 | action.call()
|
---|
89 | }
|
---|
90 | putStr "OFFLINE_ASM_END" if !$emitWinAsm
|
---|
91 | @state = :cpp
|
---|
92 | end
|
---|
93 |
|
---|
94 | def deferAction(&proc)
|
---|
95 | @deferredActions << proc
|
---|
96 | end
|
---|
97 |
|
---|
98 | def deferNextLabelAction(&proc)
|
---|
99 | @deferredNextLabelActions << proc
|
---|
100 | end
|
---|
101 |
|
---|
102 | def newUID
|
---|
103 | @count += 1
|
---|
104 | @count
|
---|
105 | end
|
---|
106 |
|
---|
107 | def inAsm
|
---|
108 | resetAsm
|
---|
109 | enterAsm
|
---|
110 | yield
|
---|
111 | leaveAsm
|
---|
112 | end
|
---|
113 |
|
---|
114 | # Concatenates all the various components of the comment to dump.
|
---|
115 | def lastComment
|
---|
116 | separator = " "
|
---|
117 | result = ""
|
---|
118 | result = "#{@comment}" if @comment
|
---|
119 | if @annotation and $enableInstrAnnotations
|
---|
120 | result += separator if result != ""
|
---|
121 | result += "#{@annotation}"
|
---|
122 | end
|
---|
123 | if @internalComment
|
---|
124 | result += separator if result != ""
|
---|
125 | result += "#{@internalComment}"
|
---|
126 | end
|
---|
127 | if $enableCodeOriginComments and @codeOrigin and @codeOrigin != @lastCodeOrigin
|
---|
128 | @lastCodeOrigin = @codeOrigin
|
---|
129 | result += separator if result != ""
|
---|
130 | result += "#{@codeOrigin}"
|
---|
131 | end
|
---|
132 | if result != ""
|
---|
133 | result = $commentPrefix + " " + result
|
---|
134 | end
|
---|
135 |
|
---|
136 | # Reset all the components that we've just sent to be dumped.
|
---|
137 | @comment = nil
|
---|
138 | @annotation = nil
|
---|
139 | @codeOrigin = nil
|
---|
140 | @internalComment = nil
|
---|
141 | result
|
---|
142 | end
|
---|
143 |
|
---|
144 | # Puts a C Statement in the output stream.
|
---|
145 | def putc(*line)
|
---|
146 | raise unless @state == :asm
|
---|
147 | @outp.puts(formatDump(" " + line.join(''), lastComment))
|
---|
148 | end
|
---|
149 |
|
---|
150 | def formatDump(dumpStr, comment, commentColumns=$preferredCommentStartColumn)
|
---|
151 | result = ""
|
---|
152 | if comment.length > 0
|
---|
153 | result = "%-#{commentColumns}s %s" % [dumpStr, comment]
|
---|
154 | else
|
---|
155 | result = dumpStr
|
---|
156 | end
|
---|
157 | if $enableDebugAnnotations
|
---|
158 | if @debugAnnotationStr and @debugAnnotationStr != @lastDebugAnnotationStr
|
---|
159 | result = "%-#{$preferredDebugAnnotationColumns}s%s" % [@debugAnnotationStr, result]
|
---|
160 | else
|
---|
161 | result = "%-#{$preferredDebugAnnotationColumns}s%s" % ["", result]
|
---|
162 | end
|
---|
163 | @lastDebugAnnotationStr = @debugAnnotationStr
|
---|
164 | @debugAnnotationStr = nil
|
---|
165 | end
|
---|
166 | result
|
---|
167 | end
|
---|
168 |
|
---|
169 | # private method for internal use only.
|
---|
170 | def putAnnotation(text)
|
---|
171 | raise unless @state == :asm
|
---|
172 | if $enableInstrAnnotations
|
---|
173 | @outp.puts text
|
---|
174 | @annotation = nil
|
---|
175 | end
|
---|
176 | end
|
---|
177 |
|
---|
178 | def putLocalAnnotation()
|
---|
179 | putAnnotation " // #{@annotation}" if @annotation
|
---|
180 | end
|
---|
181 |
|
---|
182 | def putGlobalAnnotation()
|
---|
183 | putsNewlineSpacerIfAppropriate(:annotation)
|
---|
184 | putAnnotation "// #{@annotation}" if @annotation
|
---|
185 | end
|
---|
186 |
|
---|
187 | def putsLastComment
|
---|
188 | comment = lastComment
|
---|
189 | unless comment.empty?
|
---|
190 | @outp.puts comment
|
---|
191 | end
|
---|
192 | end
|
---|
193 |
|
---|
194 | def putStr(str)
|
---|
195 | if $enableDebugAnnotations
|
---|
196 | @outp.puts "%-#{$preferredDebugAnnotationColumns}s%s" % ["", str]
|
---|
197 | else
|
---|
198 | @outp.puts str
|
---|
199 | end
|
---|
200 | end
|
---|
201 |
|
---|
202 | def puts(*line)
|
---|
203 | raise unless @state == :asm
|
---|
204 | if !$emitWinAsm
|
---|
205 | @outp.puts(formatDump(" \"" + line.join('') + " \\n\"", lastComment))
|
---|
206 | else
|
---|
207 | @outp.puts(formatDump(" " + line.join(''), lastComment))
|
---|
208 | end
|
---|
209 | end
|
---|
210 |
|
---|
211 | def print(line)
|
---|
212 | raise unless @state == :asm
|
---|
213 | @outp.print("\"" + line + "\"")
|
---|
214 | end
|
---|
215 |
|
---|
216 | def putsNewlineSpacerIfAppropriate(state)
|
---|
217 | if @newlineSpacerState != state
|
---|
218 | @outp.puts("\n")
|
---|
219 | @newlineSpacerState = state
|
---|
220 | end
|
---|
221 | end
|
---|
222 |
|
---|
223 | def putsProc(label, comment)
|
---|
224 | raise unless $emitWinAsm
|
---|
225 | @outp.puts(formatDump("#{label} PROC PUBLIC", comment))
|
---|
226 | @lastlabel = label
|
---|
227 | end
|
---|
228 |
|
---|
229 | def putsProcEndIfNeeded
|
---|
230 | raise unless $emitWinAsm
|
---|
231 | if @lastlabel != ""
|
---|
232 | @outp.puts("#{@lastlabel} ENDP")
|
---|
233 | end
|
---|
234 | @lastlabel = ""
|
---|
235 | end
|
---|
236 |
|
---|
237 | def putsLabel(labelName, isGlobal)
|
---|
238 | raise unless @state == :asm
|
---|
239 | @deferredNextLabelActions.each {
|
---|
240 | | action |
|
---|
241 | action.call()
|
---|
242 | }
|
---|
243 | @deferredNextLabelActions = []
|
---|
244 | @numGlobalLabels += 1
|
---|
245 | putsProcEndIfNeeded if $emitWinAsm and isGlobal
|
---|
246 | putsNewlineSpacerIfAppropriate(:global)
|
---|
247 | @internalComment = $enableLabelCountComments ? "Global Label #{@numGlobalLabels}" : nil
|
---|
248 | if isGlobal
|
---|
249 | if !$emitWinAsm
|
---|
250 | @outp.puts(formatDump("OFFLINE_ASM_GLOBAL_LABEL(#{labelName})", lastComment))
|
---|
251 | else
|
---|
252 | putsProc(labelName, lastComment)
|
---|
253 | end
|
---|
254 | elsif /\Allint_op_/.match(labelName)
|
---|
255 | if !$emitWinAsm
|
---|
256 | @outp.puts(formatDump("OFFLINE_ASM_OPCODE_LABEL(op_#{$~.post_match})", lastComment))
|
---|
257 | else
|
---|
258 | label = "llint_" + "op_#{$~.post_match}"
|
---|
259 | @outp.puts(formatDump(" _#{label}:", lastComment))
|
---|
260 | end
|
---|
261 | else
|
---|
262 | if !$emitWinAsm
|
---|
263 | @outp.puts(formatDump("OFFLINE_ASM_GLUE_LABEL(#{labelName})", lastComment))
|
---|
264 | else
|
---|
265 | @outp.puts(formatDump(" _#{labelName}:", lastComment))
|
---|
266 | end
|
---|
267 | end
|
---|
268 | if $emitELFDebugDirectives
|
---|
269 | deferNextLabelAction {
|
---|
270 | putStr(" \".size #{labelName} , . - #{labelName} \\n\"")
|
---|
271 | putStr(" \".type #{labelName} , function \\n\"")
|
---|
272 | }
|
---|
273 | end
|
---|
274 | @newlineSpacerState = :none # After a global label, we can use another spacer.
|
---|
275 | end
|
---|
276 |
|
---|
277 | def putsLocalLabel(labelName)
|
---|
278 | raise unless @state == :asm
|
---|
279 | @numLocalLabels += 1
|
---|
280 | @outp.puts("\n")
|
---|
281 | @internalComment = $enableLabelCountComments ? "Local Label #{@numLocalLabels}" : nil
|
---|
282 | if !$emitWinAsm
|
---|
283 | @outp.puts(formatDump(" OFFLINE_ASM_LOCAL_LABEL(#{labelName})", lastComment))
|
---|
284 | else
|
---|
285 | @outp.puts(formatDump(" #{labelName}:", lastComment))
|
---|
286 | end
|
---|
287 | end
|
---|
288 |
|
---|
289 | def self.externLabelReference(labelName)
|
---|
290 | if !$emitWinAsm
|
---|
291 | "\" LOCAL_REFERENCE(#{labelName}) \""
|
---|
292 | else
|
---|
293 | "#{labelName}"
|
---|
294 | end
|
---|
295 | end
|
---|
296 |
|
---|
297 | def self.labelReference(labelName)
|
---|
298 | if !$emitWinAsm
|
---|
299 | "\" LOCAL_LABEL_STRING(#{labelName}) \""
|
---|
300 | else
|
---|
301 | "_#{labelName}"
|
---|
302 | end
|
---|
303 | end
|
---|
304 |
|
---|
305 | def self.localLabelReference(labelName)
|
---|
306 | if !$emitWinAsm
|
---|
307 | "\" LOCAL_LABEL_STRING(#{labelName}) \""
|
---|
308 | else
|
---|
309 | "#{labelName}"
|
---|
310 | end
|
---|
311 | end
|
---|
312 |
|
---|
313 | def self.cLabelReference(labelName)
|
---|
314 | if /\Allint_op_/.match(labelName)
|
---|
315 | "op_#{$~.post_match}" # strip opcodes of their llint_ prefix.
|
---|
316 | else
|
---|
317 | "#{labelName}"
|
---|
318 | end
|
---|
319 | end
|
---|
320 |
|
---|
321 | def self.cLocalLabelReference(labelName)
|
---|
322 | "#{labelName}"
|
---|
323 | end
|
---|
324 |
|
---|
325 | def codeOrigin(text)
|
---|
326 | @codeOrigin = text
|
---|
327 | end
|
---|
328 |
|
---|
329 | def comment(text)
|
---|
330 | @comment = text
|
---|
331 | end
|
---|
332 |
|
---|
333 | def annotation(text)
|
---|
334 | @annotation = text
|
---|
335 | end
|
---|
336 |
|
---|
337 | def debugAnnotation(text)
|
---|
338 | @debugAnnotationStr = text
|
---|
339 | end
|
---|
340 | end
|
---|
341 |
|
---|
342 | IncludeFile.processIncludeOptions()
|
---|
343 |
|
---|
344 | asmFile = ARGV.shift
|
---|
345 | offsetsFile = ARGV.shift
|
---|
346 | outputFlnm = ARGV.shift
|
---|
347 | variants = ARGV.shift.split(/[,\s]+/)
|
---|
348 |
|
---|
349 | $options = {}
|
---|
350 | OptionParser.new do |opts|
|
---|
351 | opts.banner = "Usage: asm.rb asmFile offsetsFile outputFileName [--assembler=<ASM>] [--webkit-additions-path=<path>] [--binary-format=<format>] [--depfile=<depfile>]"
|
---|
352 | # This option is currently only used to specify the masm assembler
|
---|
353 | opts.on("--assembler=[ASM]", "Specify an assembler to use.") do |assembler|
|
---|
354 | $options[:assembler] = assembler
|
---|
355 | end
|
---|
356 | opts.on("--webkit-additions-path=PATH", "WebKitAdditions path.") do |path|
|
---|
357 | $options[:webkit_additions_path] = path
|
---|
358 | end
|
---|
359 | opts.on("--binary-format=FORMAT", "Specify the binary format used by the target system.") do |format|
|
---|
360 | $options[:binary_format] = format
|
---|
361 | end
|
---|
362 | opts.on("--depfile=DEPFILE", "Path to write Makefile-style discovered dependencies to.") do |path|
|
---|
363 | $options[:depfile] = path
|
---|
364 | end
|
---|
365 | end.parse!
|
---|
366 |
|
---|
367 | begin
|
---|
368 | configurationList = offsetsAndConfigurationIndexForVariants(offsetsFile, variants)
|
---|
369 | rescue MissingMagicValuesException
|
---|
370 | $stderr.puts "offlineasm: No magic values found. Skipping assembly file generation."
|
---|
371 | exit 1
|
---|
372 | end
|
---|
373 |
|
---|
374 | # The MS compiler doesn't accept DWARF2 debug annotations.
|
---|
375 | if isMSVC
|
---|
376 | $enableDebugAnnotations = false
|
---|
377 | end
|
---|
378 |
|
---|
379 | $emitWinAsm = isMSVC ? outputFlnm.index(".asm") != nil : false
|
---|
380 | $commentPrefix = $emitWinAsm ? ";" : "//"
|
---|
381 |
|
---|
382 | # We want this in all ELF systems we support, except for C_LOOP (we'll disable it later on if we are building cloop)
|
---|
383 | $emitELFDebugDirectives = $options.has_key?(:binary_format) && $options[:binary_format] == "ELF"
|
---|
384 |
|
---|
385 | inputHash =
|
---|
386 | $commentPrefix + " offlineasm input hash: " + parseHash(asmFile, $options) +
|
---|
387 | " " + Digest::SHA1.hexdigest(configurationList.map{|v| (v[0] + [v[1]]).join(' ')}.join(' ')) +
|
---|
388 | " " + selfHash +
|
---|
389 | " " + Digest::SHA1.hexdigest($options.has_key?(:assembler) ? $options[:assembler] : "")
|
---|
390 |
|
---|
391 | if FileTest.exist?(outputFlnm) and (not $options[:depfile] or FileTest.exist?($options[:depfile]))
|
---|
392 | lastLine = nil
|
---|
393 | File.open(outputFlnm, "r") {
|
---|
394 | | file |
|
---|
395 | file.each_line {
|
---|
396 | | line |
|
---|
397 | line = line.chomp
|
---|
398 | unless line.empty?
|
---|
399 | lastLine = line
|
---|
400 | end
|
---|
401 | }
|
---|
402 | }
|
---|
403 | if lastLine and lastLine == inputHash
|
---|
404 | # Nothing changed.
|
---|
405 | exit 0
|
---|
406 | end
|
---|
407 | end
|
---|
408 |
|
---|
409 | File.open(outputFlnm, "w") {
|
---|
410 | | outp |
|
---|
411 | $output = outp
|
---|
412 |
|
---|
413 | $asm = Assembler.new($output)
|
---|
414 |
|
---|
415 | sources = Set.new
|
---|
416 | ast = parse(asmFile, $options, sources)
|
---|
417 | settingsCombinations = computeSettingsCombinations(ast)
|
---|
418 |
|
---|
419 | if $options[:depfile]
|
---|
420 | depfile = File.open($options[:depfile], "w")
|
---|
421 | depfile.print(Shellwords.escape(outputFlnm), ": ")
|
---|
422 | depfile.puts(Shellwords.join(sources.sort))
|
---|
423 | end
|
---|
424 |
|
---|
425 | configurationList.each {
|
---|
426 | | configuration |
|
---|
427 | offsetsList = configuration[0]
|
---|
428 | configIndex = configuration[1]
|
---|
429 | forSettings(settingsCombinations[configIndex], ast) {
|
---|
430 | | concreteSettings, lowLevelAST, backend |
|
---|
431 |
|
---|
432 | # There could be multiple backends we are generating for, but the C_LOOP is
|
---|
433 | # always by itself so this check to turn off $enableDebugAnnotations won't
|
---|
434 | # affect the generation for any other backend.
|
---|
435 | if backend == "C_LOOP" || backend == "C_LOOP_WIN"
|
---|
436 | $enableDebugAnnotations = false
|
---|
437 | $preferredCommentStartColumn = 60
|
---|
438 | $emitELFDebugDirectives = false
|
---|
439 | end
|
---|
440 |
|
---|
441 | lowLevelAST = lowLevelAST.demacroify({})
|
---|
442 | lowLevelAST = lowLevelAST.resolve(buildOffsetsMap(lowLevelAST, offsetsList))
|
---|
443 | lowLevelAST.validate
|
---|
444 | emitCodeInConfiguration(concreteSettings, lowLevelAST, backend) {
|
---|
445 | $currentSettings = concreteSettings
|
---|
446 | $asm.inAsm {
|
---|
447 | lowLevelAST.lower(backend)
|
---|
448 | }
|
---|
449 | }
|
---|
450 | }
|
---|
451 | }
|
---|
452 |
|
---|
453 | $output.fsync
|
---|
454 | $output.puts inputHash
|
---|
455 | }
|
---|