| # Copyright 2016, VIXL authors |
| # All rights reserved. |
| # |
| # Redistribution and use in source and binary forms, with or without |
| # modification, are permitted provided that the following conditions are met: |
| # |
| # * Redistributions of source code must retain the above copyright notice, |
| # this list of conditions and the following disclaimer. |
| # * 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. |
| # * Neither the name of ARM Limited nor the names of its contributors may be |
| # used to endorse or promote products derived from this software without |
| # specific prior written permission. |
| # |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 THE COPYRIGHT OWNER OR 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. |
| |
| import itertools |
| import random |
| import os.path |
| from copy import deepcopy |
| |
| class OperandList(object): |
| """ |
| Convenience class representing a list of operand objects. It can be viewed is |
| an iterator over operand objects. |
| |
| Attributes: |
| operand_list |
| """ |
| |
| def __init__(self, operand_list): |
| self.operand_list = operand_list |
| |
| def __iter__(self): |
| return iter(self.operand_list) |
| |
| def unwrap(self): |
| """ |
| Return a list of `Operand` objects, unwrapping `OperandWrapper` objects into |
| `Operand` objects. For example: |
| |
| ~~~ |
| Condition, Register, Operand(Register, Shift, Register) |
| ~~~ |
| |
| Unwraps to: |
| |
| ~~~ |
| Condition, Register, Register, Shift, Register |
| ~~~ |
| """ |
| return itertools.chain(*self.operand_list) |
| |
| def ExcludeVariants(self, type_name, variant_to_exclude): |
| """ |
| Remove variants in `variant_to_exclude` from operands with type `type_name`. |
| """ |
| # Find the list of operand with type `type_name`. |
| relevant_operands = filter(lambda operand: operand.type_name == type_name, |
| self) |
| for operand in relevant_operands: |
| # Remove the intersection of the existing variants and variants we do not |
| # want. |
| for variant in set(operand.variants) & set(variant_to_exclude): |
| operand.variants.remove(variant) |
| |
| def GetNames(self): |
| """ |
| Return the list of all `Operand` names, excluding `OperandWrapper` objects. |
| """ |
| return [operand.name for operand in self.unwrap()] |
| |
| |
| class InputList(object): |
| """ |
| Convevience class representing a list of input objects. |
| |
| This class is an iterator over input objects. |
| |
| Attributes: |
| inputs |
| """ |
| |
| def __init__(self, inputs): |
| self.inputs = inputs |
| |
| def __iter__(self): |
| return iter(self.inputs) |
| |
| def GetNames(self): |
| """ |
| Return the list of input names. |
| """ |
| return [input.name for input in self] |
| |
| |
| class TestCase(object): |
| """ |
| Object representation of a test case, as described in JSON. This object is |
| used to build sets of operands and inputs that will be used by the generator |
| to produce C++ arrays. |
| |
| Attributes: |
| name Name of the test case, it is used to name the array to |
| produce. |
| seed Seed value to use for reproducable random generation. |
| operand_names List of operand names this test case covers. |
| input_names List of input names this test case covers. |
| operand_filter Python expression as a string to filter out operands. |
| input_filter Python expression as a string to filter out inputs. |
| operand_limit Optional limit of the number of operands to generate. |
| input_limit Optional limit of the number of inputs to generate. |
| it_condition If not None, an IT instruction needs to be generated for the |
| instruction under test to be valid. This member is a string |
| template indicating the name of the condition operand, to be |
| used with "format". For example, it will most likely have |
| the value "{cond}". |
| """ |
| |
| # Declare functions that will be callable from Python expressions in |
| # `self.operand_filter`. |
| operand_filter_runtime = { |
| 'register_is_low': lambda register: |
| register in ["r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7"] |
| } |
| |
| def __init__(self, name, seed, operand_names, input_names, operand_filter, |
| input_filter, operand_limit, input_limit, it_condition): |
| self.name = name |
| self.seed = seed |
| self.operand_names = operand_names |
| self.input_names = input_names |
| self.operand_filter = operand_filter |
| self.input_filter = input_filter |
| self.operand_limit = operand_limit |
| self.input_limit = input_limit |
| self.it_condition = it_condition |
| |
| def GenerateOperands(self, operand_types): |
| """ |
| Generate a list of tuples, each tuple describing what operands to pass to an |
| instruction to encode it. We use this to generate operand definitions. |
| |
| The algorithm used is a simple product of all operand variants. To limit |
| what we generate, we choose to apply the product only on operands with their |
| name in the `self.operand_names` list. |
| |
| Additionally, we use the Python expression in `self.operand_filter` to |
| filter out tuples we do not want. |
| |
| Argument: |
| operand_types The `OperandList` object that describe the form of the |
| instruction to generate code for. |
| """ |
| # Build a list of all possible variants as a list of tuples. If the |
| # operand's name is not in `self.operand_names`, then we restrict the list |
| # to contain default variant. Each tuple in the list has the form |
| # `(name, [variant1, variant2, ...])`. For example: |
| # |
| # [ |
| # ('cond', ['al', 'ne', 'eq', ...]), # All condition variants. |
| # ('rd', ['r0', 'r1', ...]), # All register variants. |
| # ('rn', ['r0']) # Default register variant (r0). |
| # ... |
| # ] |
| variants = [ |
| [(operand_type.name, variant) for variant in operand_type.variants] |
| if operand_type.name in self.operand_names |
| else [(operand_type.name, operand_type.default)] |
| for operand_type in operand_types.unwrap() |
| ] |
| lambda_string = "lambda {args}: {expression}".format( |
| args=",".join(operand_types.GetNames()), |
| expression=self.operand_filter) |
| filter_lambda = eval(lambda_string, self.operand_filter_runtime) |
| |
| def BuildOperandDefinition(operands): |
| """ |
| Take a list of tuples describing the operands and build a definition from |
| it. A definition is a tuple with a list of variants and a |
| `expect_instruction_before` string. |
| |
| For example, we are turning this: |
| |
| [ |
| ('cond', 'ne'), |
| ('rd', 'r0'), |
| ('rn', 'r1'), |
| ('rm', 'r0) |
| [ |
| |
| Into: |
| |
| (['ne', 'r0', 'r1', 'r0'], "It ne;") |
| |
| """ |
| return ( |
| # Build a list of operands by only keeping the second element of each |
| # tuple. |
| [operand[1] for operand in operands], |
| # The next field is a boolean indicating if the test case needs to |
| # generate an IT instruction. |
| "true" if self.it_condition else "false", |
| # If so, what condition should it be? |
| self.it_condition.format(**dict(operands)) if self.it_condition else "al" |
| ) |
| |
| # Build and return a list of operand definitions by computing the product of |
| # all variants and filtering them with `filter_lambda`. |
| # |
| # Operand definitions consist of a list with a list of variants and an |
| # optional `expect_instruction_before` string. For example: |
| # |
| # [ |
| # (['al', 'r0', 'r1', 'r2'], ""), |
| # (['ne', 'r0', 'r1', 'r0'], "It ne;"), |
| # ... |
| # ] |
| # |
| # Here, the filtered product of variants builds a list of lists of tuples, as such: |
| # |
| # [ |
| # [('cond', 'al'), ('rd', 'r0'), ('rn', 'r1'), ('rn', 'r2')] |
| # [('cond', 'ne'), ('rd', 'r0'), ('rn', 'r1'), ('rn', 'r0')], |
| # ... |
| # ] |
| # |
| # We then pass them to `BuildOperandDefinition` to produce the expected form |
| # out of it. |
| result = [ |
| BuildOperandDefinition(operands) |
| for operands in itertools.product(*variants) |
| if filter_lambda(**dict(operands)) |
| ] |
| if self.operand_limit is None: |
| return result |
| else: |
| # Use a fixed seed to randomly choose a limited set of operands. |
| random.seed(self.seed) |
| return random.sample(result, self.operand_limit) |
| |
| def GenerateInputs(self, input_types): |
| """ |
| Generate a list of tuples, each tuple describing what input to pass to an |
| instruction at runtime. We use this to generate input definitions. |
| |
| The algorithm used is a simple product of all input values. To limit what |
| we generate, we choose to apply the product only on inputs with their name |
| in the `self.input_names` list. |
| |
| Additionally, we use the Python expression in `self.input_filter` to filter |
| out tuples we do not want. |
| |
| Argument: |
| input_types The `InputList` object describing the list of inputs the |
| instruction can take. |
| """ |
| # Build a list of all possible values as a list of lists. If the input's |
| # name is not in `self.input_names`, then we restrict the list to the |
| # default value. |
| values = [ |
| input_type.values |
| if input_type.name in self.input_names else [input_type.default] |
| for input_type in input_types |
| ] |
| lambda_string = "lambda {args}: {expression}".format( |
| args=", ".join(input_types.GetNames()), |
| expression=self.input_filter) |
| filter_lambda = eval(lambda_string) |
| # Build and return a list of input definitions, such as |
| # [('NoFlag', '0xffffffff', 0xabababab'), ...] for example. |
| result = [ |
| input_definition |
| for input_definition in itertools.product(*values) |
| if filter_lambda(*input_definition) |
| ] |
| if self.input_limit is None: |
| return result |
| else: |
| # Use a fixed seed to randomly choose a limited set of inputs. |
| random.seed(self.seed) |
| return random.sample(result, self.input_limit) |
| |
| |
| class Generator(object): |
| """ |
| A `Generator` object contains all information needed to generate a test file. |
| Each method will return a string used to fill a variable in a template. |
| |
| |
| Attributes: |
| test_name Name of the test inferred from the name of the configuration |
| file. It has the following form: `type-op1-op2-op3-isa`. |
| test_type Type of the test, extracted from test_name. |
| mnemonics List of instruction mnemonics. |
| operands `OperandList` object. |
| inputs `InputList` object. |
| test_cases List of `TestCase` objects. |
| """ |
| |
| def __init__(self, test_name, test_isa, test_type, mnemonics, operands, |
| inputs, test_cases): |
| self.test_name = test_name |
| self.test_isa = test_isa |
| self.test_type = test_type |
| self.mnemonics = mnemonics |
| self.inputs = inputs |
| self.test_cases = test_cases |
| |
| # A simulator test cannot easily make use of the PC and SP registers. |
| if self.test_type == "simulator": |
| # We need to explicitely create our own deep copy the operands before we |
| # can modify them. |
| self.operands = deepcopy(operands) |
| self.operands.ExcludeVariants("Register", ["r13", "r15"]) |
| else: |
| self.operands = operands |
| |
| def MnemonicToMethodName(self, mnemonic): |
| if self.test_type in ["simulator", "macro-assembler"]: |
| # Return a MacroAssembler method name |
| return mnemonic.capitalize() |
| else: |
| # Return an Assembler method name |
| method_name = mnemonic.lower() |
| return "and_" if method_name == "and" else method_name |
| |
| def InstructionListDeclaration(self): |
| """ |
| ~~~ |
| M(Adc) \ |
| M(Adcs) \ |
| M(Add) \ |
| M(Adds) \ |
| M(And) \ |
| ... |
| ~~~ |
| """ |
| return "".join([ |
| "M({}) \\\n".format(self.MnemonicToMethodName(mnemonic)) |
| for mnemonic in self.mnemonics |
| ]) |
| |
| def OperandDeclarations(self): |
| """ |
| ~~~ |
| Condition cond; |
| Register rd; |
| Register rn; |
| ... |
| ~~~ |
| """ |
| return "".join([operand.Declare() for operand in self.operands]) |
| |
| def InputDeclarations(self): |
| """ |
| ~~~ |
| uint32_t cond; |
| uint32_t rd; |
| uint32_t rn; |
| ... |
| ~~~ |
| """ |
| return "".join([input.Declare() for input in self.inputs]) |
| |
| def InputDefinitions(self): |
| """ |
| ~~~ |
| static const Inputs kCondition[] = {{...},{...}, ...}; |
| static const Inputs kRdIsRd[] = {{...},{...}, ...}; |
| ... |
| ~~~ |
| """ |
| def InputDefinition(test_input): |
| inputs = [ |
| "{{{}}}".format(",".join(input)) |
| for input in test_input.GenerateInputs(self.inputs) |
| ] |
| |
| return """static const Inputs k{name}[] = {{ {input} }}; |
| """.format(name=test_input.name, input=",".join(inputs)) |
| |
| return "\n".join(map(InputDefinition, self.test_cases)) |
| |
| def TestCaseDefinitions(self): |
| """ |
| For simulator tests: |
| ~~~ |
| {{eq, r0, r0, ...}, |
| "eq r0 r0 ...", |
| "Condition_eq_r0_...", |
| ARRAY_SIZE(kCondition), kCondition}, |
| ... |
| {{eq, r0, r0, ...}, |
| "eq r0 r0 ...", |
| "RdIsRd_eq_r0_...", |
| ARRAY_SIZE(kRdIsRd), kRdIsRn}, |
| ... |
| ~~~ |
| |
| For assembler tests: |
| ~~~ |
| {{eq, r0, r0, ...}, |
| "", |
| "eq r0 r0 ...", |
| "Condition_eq_r0_...", |
| ... |
| {{eq, r0, r0, ...}, |
| "", |
| "eq r0 r0 ...", |
| "RdIsRd_eq_r0_..."} |
| ... |
| {{eq, r0, r0, ...}, |
| "It eq", |
| "eq r0 r0 ...", |
| "RdIsRd_eq_r0_..."} |
| ... |
| ~~~ |
| """ |
| def SimulatorTestCaseDefinition(test_case): |
| test_cases = [ |
| """{{ {{ {operands} }}, |
| "{operands_description}", |
| "{identifier}", |
| ARRAY_SIZE(k{test_case_name}), |
| k{test_case_name} }} |
| """.format(operands=",".join(operand), |
| operands_description=" ".join(operand), |
| identifier=test_case.name + "_" + "_".join(operand), |
| test_case_name=test_case.name) |
| for operand, _, _ in test_case.GenerateOperands(self.operands) |
| ] |
| return ",\n".join(test_cases) |
| |
| def AssemblerTestCaseDefinition(test_case): |
| test_cases = [ |
| """{{ {{ {operands} }}, |
| {in_it_block}, |
| {it_condition}, |
| "{operands_description}", |
| "{identifier}" }} |
| """.format(operands=",".join(operand), |
| in_it_block=in_it_block, |
| it_condition=it_condition, |
| operands_description=" ".join(operand), |
| identifier="_".join(operand)) |
| for operand, in_it_block, it_condition |
| in test_case.GenerateOperands(self.operands) |
| ] |
| return ",\n".join(test_cases) |
| |
| def MacroAssemblerTestCaseDefinition(test_case): |
| test_cases = [ |
| """{{ {{ {operands} }}, |
| "{operands_description}", |
| "{identifier}" }} |
| """.format(operands=",".join(operand), |
| operands_description=", ".join(operand), |
| identifier="_".join(operand)) |
| for operand, _, _ in test_case.GenerateOperands(self.operands) |
| ] |
| return ",\n".join(test_cases) |
| |
| if self.test_type == "simulator": |
| return ",\n".join(map(SimulatorTestCaseDefinition, self.test_cases)) |
| elif self.test_type == "assembler": |
| return ",\n".join(map(AssemblerTestCaseDefinition, self.test_cases)) |
| elif self.test_type == "macro-assembler": |
| return ",\n".join(map(MacroAssemblerTestCaseDefinition, self.test_cases)) |
| else: |
| raise Exception("Unrecognized test type \"{}\".".format(self.test_type)) |
| |
| def IncludeTraceFiles(self): |
| """ |
| ~~~ |
| #include "aarch32/traces/sim-...-a32.h" |
| #include "aarch32/traces/sim-...-a32.h" |
| ... |
| ~~~ |
| """ |
| operands = "-".join(self.operands.GetNames()) |
| return "".join([ |
| "#include \"aarch32/traces/" + self.GetTraceFileName(mnemonic) + "\"\n" |
| for mnemonic in self.mnemonics |
| ]) |
| |
| def MacroAssemblerMethodArgs(self): |
| """ |
| ~~~ |
| Condition cond, Register rd, Register rm, const Operand& immediate |
| ~~~ |
| """ |
| return ", ".join([ |
| operand.GetArgumentType() + " " + operand.name |
| for operand in self.operands |
| ]) |
| |
| def MacroAssemblerSetISA(self): |
| """ |
| Generate code to set the ISA. |
| """ |
| if self.test_isa == "t32": |
| return "masm.UseT32();" |
| else: |
| return "masm.UseA32();" |
| |
| def CodeInstantiateOperands(self): |
| """ |
| ~~~ |
| Condition cond = kTests[i].operands.cond; |
| Register rd = kTests[i].operands.rd; |
| ... |
| ~~~ |
| """ |
| code = "".join([operand.Instantiate() for operand in self.operands]) |
| if self.test_type in ["simulator", "macro-assembler"]: |
| # Simulator tests need scratch registers to function and uses |
| # `UseScratchRegisterScope` to dynamically allocate them. We need to |
| # exclude all register operands from the list of available scratch |
| # registers. |
| # MacroAssembler test also need to ensure that they don't try to run tests |
| # with registers that are scratch registers; the MacroAssembler contains |
| # assertions to protect against such usage. |
| excluded_registers = [ |
| "scratch_registers.Exclude({});".format(operand.name) |
| for operand in self.operands.unwrap() |
| if operand.type_name == "Register" |
| ] |
| return code + "\n".join(excluded_registers) |
| return code |
| |
| def CodePrologue(self): |
| """ |
| ~~~ |
| __ Ldr(rn, MemOperand(input_ptr, offsetof(Inputs, rn))); |
| __ Ldr(rm, MemOperand(input_ptr, offsetof(Inputs, rm))); |
| ... |
| ~~~ |
| """ |
| return "".join([input.Prologue() for input in self.inputs]) |
| |
| def CodeEpilogue(self): |
| """ |
| ~~~ |
| __ Str(rn, MemOperand(result_ptr, offsetof(Inputs, rn))); |
| __ Str(rm, MemOperand(result_ptr, offsetof(Inputs, rm))); |
| ... |
| ~~~ |
| """ |
| return "".join([input.Epilogue() for input in self.inputs]) |
| |
| def CodeParameterList(self): |
| """ |
| ~~~ |
| cond, rd, rn, immediate |
| ~~~ |
| """ |
| return ", ".join([ |
| operand.name |
| for operand in self.operands |
| ]) |
| |
| def TracePrintOutputs(self): |
| """ |
| ~~~ |
| printf("0x%08" PRIx32, results[i]->outputs[j].cond); |
| printf(", "); |
| printf("0x%08" PRIx32, results[i]->outputs[j].rd); |
| printf(", "); |
| ... |
| ~~~ |
| """ |
| return "printf(\", \");".join( |
| [input.PrintOutput() for input in self.inputs]) |
| |
| |
| def CheckInstantiateResults(self): |
| """ |
| ~~~ |
| uint32_t cond = results[i]->outputs[j].cond; |
| uint32_t rd = results[i]->outputs[j].rd; |
| ... |
| ~~~ |
| """ |
| return "".join([input.InstantiateResult() for input in self.inputs]) |
| |
| def CheckInstantiateInputs(self): |
| """ |
| ~~~ |
| uint32_t cond_input = kTests[i].inputs[j].cond; |
| uint32_t rd_input = kTests[i].inputs[j].rd; |
| ... |
| ~~~ |
| """ |
| return "".join([input.InstantiateInput("_input") for input in self.inputs]) |
| |
| def CheckInstantiateReferences(self): |
| """ |
| ~~~ |
| uint32_t cond_ref = reference[i].outputs[j].cond; |
| uint32_t rd_ref = reference[i].outputs[j].rd; |
| ... |
| ~~~ |
| """ |
| return "".join([input.InstantiateReference("_ref") for input in self.inputs]) |
| |
| def CheckResultsAgainstReferences(self): |
| """ |
| ~~~ |
| (cond != cond_ref) || (rd != rd_ref) || ... |
| ~~~ |
| """ |
| return " || ".join([input.Compare("", "!=", "_ref") for input in self.inputs]) |
| |
| def CheckPrintInput(self): |
| """ |
| ~~~ |
| printf("0x%08" PRIx32, cond_input); |
| printf(", "); |
| printf("0x%08" PRIx32, rd_input); |
| printf(", "); |
| ... |
| ~~~ |
| """ |
| return "printf(\", \");".join( |
| [input.PrintInput("_input") for input in self.inputs]) |
| |
| def CheckPrintExpected(self): |
| """ |
| ~~~ |
| printf("0x%08" PRIx32, cond_ref); |
| printf(", "); |
| printf("0x%08" PRIx32, rd_ref); |
| printf(", "); |
| ... |
| ~~~ |
| """ |
| return "printf(\", \");".join( |
| [input.PrintInput("_ref") for input in self.inputs]) |
| |
| def CheckPrintFound(self): |
| """ |
| ~~~ |
| printf("0x%08" PRIx32, cond); |
| printf(", "); |
| printf("0x%08" PRIx32, rd); |
| printf(", "); |
| ... |
| ~~~ |
| """ |
| return "printf(\", \");".join( |
| [input.PrintInput("") for input in self.inputs]) |
| |
| def TestName(self): |
| """ |
| ~~~ |
| SIMULATOR_COND_RD_RN_RM_... |
| ~~~ |
| """ |
| return self.test_type.replace("-", "_").upper() + "_" + \ |
| self.test_name.replace("-", "_").upper() |
| |
| def GetTraceFileName(self, mnemonic): |
| """ |
| Return the name of a trace file for a given mnemonic. |
| """ |
| return self.test_type + "-" + self.test_name + "-" + \ |
| mnemonic.lower() + ".h" |
| |
| def WriteEmptyTraces(self, output_directory): |
| """ |
| Write out empty trace files so we can compile the new test cases. |
| """ |
| for mnemonic in self.mnemonics: |
| # The MacroAssembler tests have no traces. |
| if self.test_type == "macro-assembler": continue |
| |
| with open(os.path.join(output_directory, self.GetTraceFileName(mnemonic)), |
| "w") as f: |
| code = "static const TestResult *kReference{} = NULL;\n" |
| f.write(code.format(self.MnemonicToMethodName(mnemonic))) |
| |
| def GetIsaGuard(self): |
| """ |
| This guard ensure the ISA of the test is enabled. |
| """ |
| if 'A32' in self.TestName(): |
| return 'VIXL_INCLUDE_TARGET_A32' |
| else: |
| assert 'T32' in self.TestName() |
| return 'VIXL_INCLUDE_TARGET_T32' |
| |