| # Copyright (C) 2011 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. |
| |
| require "config" |
| require "ast" |
| require "backends" |
| require "parser" |
| require "transform" |
| |
| # |
| # computeSettingsCombinations(ast) -> settingsCombiations |
| # |
| # Computes an array of settings maps, where a settings map constitutes |
| # a configuration for the assembly code being generated. The map |
| # contains key value pairs where keys are settings names (strings) and |
| # the values are booleans (true for enabled, false for disabled). |
| # |
| |
| def computeSettingsCombinations(ast) |
| settingsCombinations = [] |
| |
| def settingsCombinator(settingsCombinations, mapSoFar, remaining) |
| if remaining.empty? |
| settingsCombinations << mapSoFar |
| return |
| end |
| |
| newMap = mapSoFar.dup |
| newMap[remaining[0]] = true |
| settingsCombinator(settingsCombinations, newMap, remaining[1..-1]) |
| |
| newMap = mapSoFar.dup |
| newMap[remaining[0]] = false |
| settingsCombinator(settingsCombinations, newMap, remaining[1..-1]) |
| end |
| |
| nonBackendSettings = ast.filter(Setting).uniq.collect{ |v| v.name } |
| nonBackendSettings.delete_if { |
| | setting | |
| isBackend? setting |
| } |
| |
| allBackendsFalse = {} |
| BACKENDS.each { |
| | backend | |
| allBackendsFalse[backend] = false |
| } |
| |
| # This will create entries for invalid backends. That's fine. It's necessary |
| # because it ensures that generate_offsets_extractor (which knows about valid |
| # backends) has settings indices that are compatible with what asm will see |
| # (asm doesn't know about valid backends). |
| BACKENDS.each { |
| | backend | |
| map = allBackendsFalse.clone |
| map[backend] = true |
| settingsCombinator(settingsCombinations, map, nonBackendSettings) |
| } |
| |
| settingsCombinations |
| end |
| |
| # |
| # forSettings(concreteSettings, ast) { |
| # | concreteSettings, lowLevelAST, backend | ... } |
| # |
| # Determines if the settings combination is valid, and if so, calls |
| # the block with the information you need to generate code. |
| # |
| |
| def forSettings(concreteSettings, ast) |
| # Check which architectures this combinator claims to support. |
| selectedBackend = nil |
| BACKENDS.each { |
| | backend | |
| if concreteSettings[backend] |
| raise if selectedBackend |
| selectedBackend = backend |
| end |
| } |
| |
| return unless isValidBackend? selectedBackend |
| |
| # Resolve the AST down to a low-level form (no macros or conditionals). |
| lowLevelAST = ast.resolveSettings(concreteSettings) |
| |
| yield concreteSettings, lowLevelAST, selectedBackend |
| end |
| |
| # |
| # forEachValidSettingsCombination(ast) { |
| # | concreteSettings, ast, backend, index | ... } |
| # |
| # forEachValidSettingsCombination(ast, settingsCombinations) { |
| # | concreteSettings, ast, backend, index | ... } |
| # |
| # Executes the given block for each valid settings combination in the |
| # settings map. The ast passed into the block is resolved |
| # (ast.resolve) against the settings. |
| # |
| # The first form will call computeSettingsCombinations(ast) for you. |
| # |
| |
| def forEachValidSettingsCombination(ast, *optionalSettingsCombinations) |
| raise if optionalSettingsCombinations.size > 1 |
| |
| if optionalSettingsCombinations.empty? |
| settingsCombinations = computeSettingsCombinations(ast) |
| else |
| settingsCombinations = optionalSettingsCombiations[0] |
| end |
| |
| settingsCombinations.each_with_index { |
| | concreteSettings, index | |
| forSettings(concreteSettings, ast) { |
| | concreteSettings_, lowLevelAST, backend | |
| yield concreteSettings, lowLevelAST, backend, index |
| } |
| } |
| end |
| |
| # |
| # cppSettingsTest(concreteSettings) |
| # |
| # Returns the C++ code used to test if we are in a configuration that |
| # corresponds to the given concrete settings. |
| # |
| |
| def cppSettingsTest(concreteSettings) |
| "#if " + concreteSettings.to_a.collect{ |
| | pair | |
| (if pair[1] |
| "" |
| else |
| "!" |
| end) + "OFFLINE_ASM_" + pair[0] |
| }.join(" && ") |
| end |
| |
| # |
| # isASTErroneous(ast) |
| # |
| # Tests to see if the AST claims that there is an error - i.e. if the |
| # user's code, after settings resolution, has Error nodes. |
| # |
| |
| def isASTErroneous(ast) |
| not ast.demacroify({}).filter(Error).empty? |
| end |
| |
| # |
| # assertConfiguration(concreteSettings) |
| # |
| # Emits a check that asserts that we're using the given configuration. |
| # |
| |
| def assertConfiguration(concreteSettings) |
| $output.puts cppSettingsTest(concreteSettings) |
| $output.puts "#else" |
| $output.puts "#error \"Configuration mismatch.\"" |
| $output.puts "#endif" |
| end |
| |
| # |
| # emitCodeInConfiguration(concreteSettings, ast, backend) { |
| # | concreteSettings, ast, backend | ... } |
| # |
| # Emits all relevant guards to see if the configuration holds and |
| # calls the block if the configuration is not erroneous. |
| # |
| |
| def emitCodeInConfiguration(concreteSettings, ast, backend) |
| Label.resetReferenced |
| |
| if !$emitWinAsm |
| $output.puts cppSettingsTest(concreteSettings) |
| else |
| if backend == "X86_WIN" |
| $output.puts ".MODEL FLAT, C" |
| end |
| $output.puts "INCLUDE #{File.basename($output.path)}.sym" |
| $output.puts "_TEXT SEGMENT" |
| end |
| |
| if isASTErroneous(ast) |
| $output.puts "#error \"Invalid configuration. Error at: #{ast.filter(Error)[0].codeOrigin}\"" |
| elsif not WORKING_BACKENDS.include? backend |
| $output.puts "#error \"This backend is not supported yet.\"" |
| else |
| yield concreteSettings, ast, backend |
| end |
| |
| if !$emitWinAsm |
| $output.puts "#endif" |
| else |
| $output.puts "_TEXT ENDS" |
| $output.puts "END" |
| |
| # Write symbols needed by MASM |
| File.open("#{File.basename($output.path)}.sym", "w") { |
| | outp | |
| Label.forReferencedExtern { |
| | name | |
| outp.puts "EXTERN #{name[1..-1]} : near" |
| } |
| } |
| end |
| end |
| |
| # |
| # emitCodeInAllConfigurations(ast) { |
| # | concreteSettings, ast, backend, index | ... } |
| # |
| # Emits guard codes for all valid configurations, and calls the block |
| # for those configurations that are valid and not erroneous. |
| # |
| |
| def emitCodeInAllConfigurations(ast) |
| forEachValidSettingsCombination(ast) { |
| | concreteSettings, lowLevelAST, backend, index | |
| $output.puts cppSettingsTest(concreteSettings) |
| yield concreteSettings, lowLevelAST, backend, index |
| $output.puts "#endif" |
| } |
| end |
| |
| |
| |