# 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 "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 settingsCombinator(settingsCombinations, {}, (ast.filter(Setting).uniq.collect{|v| v.name} + BACKENDS).uniq) 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. numClaimedBackends = 0 selectedBackend = nil BACKENDS.each { | backend | isSupported = concreteSettings[backend] raise unless isSupported != nil numClaimedBackends += if isSupported then 1 else 0 end if isSupported selectedBackend = backend end } return if numClaimedBackends > 1 # 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.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) $output.puts cppSettingsTest(concreteSettings) if isASTErroneous(ast) $output.puts "#error \"Invalid configuration.\"" elsif not WORKING_BACKENDS.include? backend $output.puts "#error \"This backend is not supported yet.\"" else yield concreteSettings, ast, backend end $output.puts "#endif" 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