1#!/usr/bin/env ruby
2
3# Copyright (C) 2011 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
28require "config"
29require "backends"
30require "digest/sha1"
31require "offsets"
32require "parser"
33require "self_hash"
34require "settings"
35require "transform"
36
37class Assembler
38    def initialize(outp)
39        @outp = outp
40        @state = :cpp
41        @commentState = :none
42        @comment = nil
43        @internalComment = nil
44        @annotation = nil
45        @codeOrigin = nil
46        @numLocalLabels = 0
47        @numGlobalLabels = 0
48
49        @newlineSpacerState = :none
50    end
51    
52    def enterAsm
53        @outp.puts "OFFLINE_ASM_BEGIN"
54        @state = :asm
55    end
56    
57    def leaveAsm
58        putsLastComment
59        @outp.puts "OFFLINE_ASM_END"
60        @state = :cpp
61    end
62    
63    def inAsm
64        enterAsm
65        yield
66        leaveAsm
67    end
68    
69    # Concatenates all the various components of the comment to dump.
70    def lastComment
71        separator = " "
72        result = ""
73        result = "#{@comment}" if @comment
74        if @annotation and $enableInstrAnnotations
75            result += separator if result != ""
76            result += "#{@annotation}"
77        end
78        if @internalComment
79            result += separator if result != ""
80            result += "#{@internalComment}"
81        end
82        if @codeOrigin and $enableCodeOriginComments
83            result += separator if result != ""
84            result += "#{@codeOrigin}"
85        end
86        if result != ""
87            result = "// " + result
88        end
89
90        # Reset all the components that we've just sent to be dumped.
91        @commentState = :none
92        @comment = nil
93        @annotation = nil
94        @codeOrigin = nil
95        @internalComment = nil
96        result
97    end
98    
99    # Puts a C Statement in the output stream.
100    def putc(*line)
101        raise unless @state == :asm
102        @outp.puts(formatDump("    " + line.join(''), lastComment))
103    end
104    
105    def formatDump(dumpStr, comment, commentColumns=$preferredCommentStartColumn)
106        if comment.length > 0
107            "%-#{commentColumns}s %s" % [dumpStr, comment]
108        else
109            dumpStr
110        end
111    end
112
113    # private method for internal use only.
114    def putAnnotation(text)
115        raise unless @state == :asm
116        if $enableInstrAnnotations
117            @outp.puts text
118            @annotation = nil
119        end
120    end
121
122    def putLocalAnnotation()
123        putAnnotation "    // #{@annotation}" if @annotation
124    end
125
126    def putGlobalAnnotation()
127        putsNewlineSpacerIfAppropriate(:annotation)
128        putAnnotation "// #{@annotation}" if @annotation
129    end
130
131    def putsLastComment
132        comment = lastComment
133        unless comment.empty?
134            @outp.puts comment
135        end
136    end
137    
138    def puts(*line)
139        raise unless @state == :asm
140        @outp.puts(formatDump("    \"\\t" + line.join('') + "\\n\"", lastComment))
141    end
142    
143    def print(line)
144        raise unless @state == :asm
145        @outp.print("\"" + line + "\"")
146    end
147    
148    def putsNewlineSpacerIfAppropriate(state)
149        if @newlineSpacerState != state
150            @outp.puts("\n")
151            @newlineSpacerState = state
152        end
153    end
154
155    def putsLabel(labelName)
156        raise unless @state == :asm
157        @numGlobalLabels += 1
158        putsNewlineSpacerIfAppropriate(:global)
159        @internalComment = $enableLabelCountComments ? "Global Label #{@numGlobalLabels}" : nil
160        if /\Allint_op_/.match(labelName)
161            @outp.puts(formatDump("OFFLINE_ASM_OPCODE_LABEL(op_#{$~.post_match})", lastComment))
162        else
163            @outp.puts(formatDump("OFFLINE_ASM_GLUE_LABEL(#{labelName})", lastComment))
164        end
165        @newlineSpacerState = :none # After a global label, we can use another spacer.
166    end
167    
168    def putsLocalLabel(labelName)
169        raise unless @state == :asm
170        @numLocalLabels += 1
171        @outp.puts("\n")
172        @internalComment = $enableLabelCountComments ? "Local Label #{@numLocalLabels}" : nil
173        @outp.puts(formatDump("  OFFLINE_ASM_LOCAL_LABEL(#{labelName})", lastComment))
174    end
175    
176    def self.labelReference(labelName)
177        "\" LOCAL_REFERENCE(#{labelName}) \""
178    end
179    
180    def self.localLabelReference(labelName)
181        "\" LOCAL_LABEL_STRING(#{labelName}) \""
182    end
183    
184    def self.cLabelReference(labelName)
185        if /\Allint_op_/.match(labelName)
186            "op_#{$~.post_match}"  # strip opcodes of their llint_ prefix.
187        else
188            "#{labelName}"
189        end
190    end
191    
192    def self.cLocalLabelReference(labelName)
193        "#{labelName}"
194    end
195    
196    def codeOrigin(text)
197        case @commentState
198        when :none
199            @codeOrigin = text
200            @commentState = :one
201        when :one
202            if $enableCodeOriginComments
203                @outp.puts "    // #{@codeOrigin}"
204                @outp.puts "    // #{text}"
205            end
206            @codeOrigin = nil
207            @commentState = :many
208        when :many
209            @outp.puts "// #{text}" if $enableCodeOriginComments
210        else
211            raise
212        end
213    end
214
215    def comment(text)
216        @comment = text
217    end
218    def annotation(text)
219        @annotation = text
220    end
221end
222
223asmFile = ARGV.shift
224offsetsFile = ARGV.shift
225outputFlnm = ARGV.shift
226
227$stderr.puts "offlineasm: Parsing #{asmFile} and #{offsetsFile} and creating assembly file #{outputFlnm}."
228
229begin
230    configurationList = offsetsAndConfigurationIndex(offsetsFile)
231rescue MissingMagicValuesException
232    $stderr.puts "offlineasm: No magic values found. Skipping assembly file generation."
233    exit 0
234end
235
236inputHash =
237    "// offlineasm input hash: " + parseHash(asmFile) +
238    " " + Digest::SHA1.hexdigest(configurationList.map{|v| (v[0] + [v[1]]).join(' ')}.join(' ')) +
239    " " + selfHash
240
241if FileTest.exist? outputFlnm
242    File.open(outputFlnm, "r") {
243        | inp |
244        firstLine = inp.gets
245        if firstLine and firstLine.chomp == inputHash
246            $stderr.puts "offlineasm: Nothing changed."
247            exit 0
248        end
249    }
250end
251
252File.open(outputFlnm, "w") {
253    | outp |
254    $output = outp
255    $output.puts inputHash
256    
257    $asm = Assembler.new($output)
258    
259    ast = parse(asmFile)
260    
261    configurationList.each {
262        | configuration |
263        offsetsList = configuration[0]
264        configIndex = configuration[1]
265        forSettings(computeSettingsCombinations(ast)[configIndex], ast) {
266            | concreteSettings, lowLevelAST, backend |
267            lowLevelAST = lowLevelAST.resolve(*buildOffsetsMap(lowLevelAST, offsetsList))
268            lowLevelAST.validate
269            emitCodeInConfiguration(concreteSettings, lowLevelAST, backend) {
270                $asm.inAsm {
271                    lowLevelAST.lower(backend)
272                }
273            }
274        }
275    }
276}
277
278$stderr.puts "offlineasm: Assembly file #{outputFlnm} successfully generated."
279
280