You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
795 lines
26 KiB
795 lines
26 KiB
#!/usr/bin/env python3
|
|
|
|
# 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.
|
|
|
|
"""
|
|
Generating tests
|
|
================
|
|
|
|
From the VIXL toplevel directory run:
|
|
|
|
$ ./tools/generate_tests.py
|
|
|
|
The script assumes that `clang-format-4.0` is in the current path. If it isn't,
|
|
you can provide your own:
|
|
|
|
$ ./tools/generate_tests.py --clang-format /patch/to/clang-format
|
|
|
|
Once the script has finished, it will have generated test files, as many as
|
|
present in the `default_config_files` list. For example:
|
|
|
|
- test/aarch32/test-assembler-cond-rd-rn-immediate-a32.cc
|
|
- test/aarch32/test-assembler-cond-rd-rn-rm-a32.cc
|
|
- test/aarch32/test-assembler-cond-rd-rn-rm-q-a32.cc
|
|
- test/aarch32/test-assembler-cond-rd-rn-rm-ge-a32.cc
|
|
|
|
Because these test cases need traces in order to build, the script will have
|
|
generated placeholder trace files in `test/aarch32/traces/`. If you look at them
|
|
you'll see they are basically empty:
|
|
|
|
$ cat test/aarch32/traces/sim-cond-rd-rn-immediate-adc-a32.h
|
|
static const TestResult *kReferenceAdc = NULL;
|
|
|
|
So of course, we can now build the test cases but running them will crash. We
|
|
need to re-generate traces with real hardware; the test cases do not support
|
|
running in the simulator just yet.
|
|
|
|
Generating traces
|
|
=================
|
|
|
|
You need to have either compiled natively for ARM, or cross-compiled
|
|
`test-runner`. The traces can then be generated in the same way as with VIXL64.
|
|
Note that it takes a few minutes to generate everything.
|
|
|
|
./tools/generate_simulator_traces.py --runner /path/to/test-runner \
|
|
--aarch32-only
|
|
|
|
You can now rebuild everything. If it all goes well, running the new tests
|
|
should pass.
|
|
|
|
Test configuration format
|
|
=========================
|
|
|
|
TODO: Write a simple and well documented complete example configuration file and
|
|
mention it here.
|
|
|
|
The underlying `test_generator` framework reads JSON description files and
|
|
generates tests according to them. These files live in `test/aarch32/config` by
|
|
default, but you may provide your own files with the `--config-files FILE ...`
|
|
flag. The JSON format was extended to support C++ like one-line comments.
|
|
|
|
Each configuration file will serve to generate one or more test files,
|
|
we even use its file name to choose the name of the test:
|
|
|
|
test/aarch32/config/cond-rd-rn-immediate-a32.json
|
|
`-> test/aarch32/test-simulator-cond-rd-rn-immediate-a32.cc
|
|
`-> test/aarch32/test-assembler-cond-rd-rn-immediate-a32.cc
|
|
|
|
In addition to these test configuration files, we also provide a JSON
|
|
description with shared information. This information represents data types that
|
|
instructions use and lives in `test/aarch32/config/data-types.json`.
|
|
|
|
Data types description
|
|
----------------------
|
|
|
|
We refer to two kinds of data types: `operand` and `input`.
|
|
|
|
An `operand` represents an argument passed to the macro-assembler to generate an
|
|
instruction. For example, a register or an immediate are operands. We can think
|
|
of it as "assemble-time" data.
|
|
|
|
As opposed to `operands`, an `input` represents data passed to an instruction at
|
|
runtime. For example, it will be the value you write to a register before
|
|
executing the instruction under test.
|
|
|
|
The `data-types.json` file has the following structure:
|
|
|
|
~~~
|
|
{
|
|
"operands": [
|
|
// List of operand types.
|
|
],
|
|
"inputs": [
|
|
// List of input types.
|
|
]
|
|
}
|
|
~~~
|
|
|
|
Each operand is described with the following structure:
|
|
|
|
~~~
|
|
{
|
|
// Unique name for this operand type.
|
|
"name": "AllRegistersButPC",
|
|
// C++ type used by VIXL to represent this operand.
|
|
"type": "Register",
|
|
// List of possible variants.
|
|
"variants": [
|
|
"r0",
|
|
"r1",
|
|
"r2",
|
|
"r3",
|
|
"r4",
|
|
"r5",
|
|
"r6",
|
|
"r7",
|
|
"r8",
|
|
"r9",
|
|
"r10",
|
|
"r11",
|
|
"r12",
|
|
"r13",
|
|
"r14"
|
|
],
|
|
// Default variant to use.
|
|
"default": "r0"
|
|
}
|
|
~~~
|
|
|
|
The "name" field of the operand will be used by test configuration files in
|
|
order to specify what kind of operands an instruction takes. The "type" field
|
|
simply tells the generator what C++ type should be generated, e.g. "Condition",
|
|
"Register", "uint32_t", "ShiftType", ...etc.
|
|
|
|
Inputs are described in a very similar way:
|
|
|
|
~~~
|
|
{
|
|
// Unique name for this input type.
|
|
"name": "Register",
|
|
// Python type from `test_generator.data_types` to use to generate C++ code
|
|
// for this input.
|
|
"type": "Register",
|
|
// List of possible values.
|
|
"values": [
|
|
"0x00000000",
|
|
"0xffffffff",
|
|
"0xabababab",
|
|
"0x5a5a5a5a"
|
|
],
|
|
// Default value.
|
|
"default": "0xabababab"
|
|
}
|
|
~~~
|
|
|
|
The "name" field has the same purpose as for operands. The "type" field however,
|
|
is the name of a Python class in `test_generator.data_types`. The type will
|
|
specify what C++ code to generate in order to load and record the input value,
|
|
e.g. how to load a value into a register, how to read and record it.
|
|
|
|
When adding more tests, one may have to create new data types in this file. For
|
|
example, when we want to test an instruction with a different set of registers.
|
|
If adding new input types which need different C++ code to load and record them,
|
|
one will have to add it to `test_generator.data_types` and override the
|
|
`Epilogue` and `Prologue` methods.
|
|
|
|
Test configuration
|
|
------------------
|
|
|
|
Once we have all the data types we need described, we need test configuration
|
|
files to describe what instructions to test and with what `inputs` and
|
|
`operands` they take.
|
|
|
|
These files have the following structure:
|
|
|
|
~~~
|
|
{
|
|
"mnemonics": [
|
|
// List of instruction mnemonics to use. These must correspond to
|
|
// `MacroAssembler` methods.
|
|
],
|
|
"description": {
|
|
"operands": [
|
|
// List of operands the instruction takes.
|
|
],
|
|
"inputs: [
|
|
// List of inputs the instruction can be affected by.
|
|
]
|
|
},
|
|
// List of files to generate.
|
|
"test-files": [
|
|
{
|
|
"type": "assembler",
|
|
"mnemonics": [
|
|
// Optional list of instruction mnemonics to use, overriding the
|
|
// top-level list.
|
|
],
|
|
"test-cases": [
|
|
// List of test cases for "assembler" tests, see below for
|
|
// details.
|
|
]
|
|
},
|
|
{
|
|
"type": "simulator",
|
|
"test-cases": [
|
|
// List of test cases for "simulator" tests, see below for
|
|
// details.
|
|
]
|
|
}
|
|
]
|
|
}
|
|
~~~
|
|
|
|
- List of operands:
|
|
|
|
The operand list describes the actual argument to the `MacroAssembler` method.
|
|
For example, if we take instruction in the form
|
|
"XXX.cond rd rn rm shift #amount":
|
|
|
|
We want to generate C++ code as such:
|
|
|
|
~~~
|
|
Condition cond = ...;
|
|
Register rd = ...;
|
|
Register rn = ...;
|
|
Register rm = ...;
|
|
ShiftType type = ...;
|
|
uint32_t amount = ...;
|
|
Operand op(rm, type, amount);
|
|
|
|
__ Xxx(cond, rd, rn, op);
|
|
~~~
|
|
|
|
We will have the following operand list:
|
|
|
|
~~~
|
|
"operands": [
|
|
{
|
|
"name": "cond",
|
|
"type": "Condition"
|
|
},
|
|
{
|
|
"name": "rd",
|
|
"type": "AllRegistersButPC"
|
|
},
|
|
{
|
|
"name": "rn",
|
|
"type": "AllRegistersButPC"
|
|
},
|
|
{
|
|
"name": "op",
|
|
"wrapper": "Operand",
|
|
"operands": [
|
|
{
|
|
"name": "rm",
|
|
"operand": "AllRegistersButPC"
|
|
},
|
|
{
|
|
"name": "type",
|
|
"operand": "Shift"
|
|
},
|
|
{
|
|
"name": "amount",
|
|
"operand": "ImmediateShiftAmount"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
~~~
|
|
|
|
The "name" field represents the identifier of the operand and will be used as a
|
|
variable name in the generated code. The "type" field corresponds to an operand
|
|
type described in the `data-types.json` file as described above.
|
|
|
|
We can see that we've wrapped the last three operands into an "op"
|
|
wrapper object. This allows us to tell the generator to wrap these
|
|
operands into a `Operand` C++ object.
|
|
|
|
- List of inputs:
|
|
|
|
This structure is similar to the operand list, but this time it describes what
|
|
input data the instructions may be affected by at runtime. If we take the same
|
|
example as above, we will have the following list:
|
|
|
|
~~~
|
|
"inputs": [
|
|
{
|
|
"name": "apsr",
|
|
"type": "NZCV"
|
|
},
|
|
{
|
|
"name": "rd",
|
|
"type": "Register"
|
|
},
|
|
{
|
|
"name": "rn",
|
|
"type": "Register"
|
|
},
|
|
{
|
|
"name": "rm",
|
|
"type": "Register"
|
|
}
|
|
]
|
|
~~~
|
|
|
|
This will specify what C++ code to generate before and after emitting the
|
|
instruction under test. The C++ code will set and record register values for
|
|
example. See `test_generator.data_types` for more details.
|
|
|
|
- Test files and test cases:
|
|
|
|
Up until now, we've only just described the environment in which instructions
|
|
can operate. We need to express what files we want generating, what instructions
|
|
we want to test and what we want them to do.
|
|
|
|
As previously mentioned, a configuration file can control the generation of
|
|
several test files. We will generate one file per element in the "test-files"
|
|
array:
|
|
|
|
~~~
|
|
"test-files": [
|
|
{
|
|
"type": "assembler",
|
|
"test-cases": [
|
|
// List of test cases for "assembler" tests, see below for
|
|
// details.
|
|
]
|
|
},
|
|
{
|
|
"type": "assembler",
|
|
"name": "special-case",
|
|
"mnemonics": [
|
|
// Override the top-level list with a subset of instructions concerned
|
|
// with this special case.
|
|
],
|
|
"test-cases": [
|
|
// List of test cases for "assembler" tests, see below for
|
|
// details.
|
|
]
|
|
},
|
|
{
|
|
"type": "simulator",
|
|
"test-cases": [
|
|
// List of test cases for "simulator" tests, see below for
|
|
// details.
|
|
]
|
|
}
|
|
]
|
|
~~~
|
|
|
|
Above, we've decided to generate three tests: a "simulator" test and two
|
|
"assembler" tests. The resulting files will have names with the following
|
|
pattern.
|
|
|
|
- "test/aarch32/test-assembler-{configuration name}-a32.cc"
|
|
- "test/aarch32/test-assembler-{configuration name}-special-case-a32.cc"
|
|
- "test/aarch32/test-simulator-{configuration name}-a32.cc"
|
|
|
|
The "type" field describes the kind of testing we want to do, these types are
|
|
recognized by the generator and, at the moment, can be one of "simulator",
|
|
"assembler", "macro-assembler" and "assembler-negative". Simulator tests will
|
|
run each instruction and record the changes while assembler tests will only
|
|
record the code buffer and never execute anything. MacroAssembler tests
|
|
currently only generate code to check that the MacroAssembler does not crash;
|
|
the output itself is not yet tested. Because you may want to generate more than
|
|
one test of the same type, as we are doing in the example, we need a way to
|
|
differentiate them. You may use the optional "name" field for this. Negative
|
|
assembler tests check that the instructions described are not allowed, which
|
|
means that an exception is raised when VIXL is built in negative testing mode.
|
|
|
|
Finally, we describe how to test the instruction by declaring a list of test
|
|
cases with the "test-cases" field.
|
|
|
|
Here is an example of what we can express:
|
|
~~~
|
|
[
|
|
// Generate all combinations of instructions where "rd" an "rn" are the same
|
|
// register and "cond" and "rm" are just the default.
|
|
// For example:
|
|
// __ Xxx(al, r0, r0, r0);
|
|
// __ Xxx(al, r1, r1, r0);
|
|
// __ Xxx(al, r2, r2, r0);
|
|
// ...
|
|
// __ Xxx(al, r12, r12, r0);
|
|
// __ Xxx(al, r13, r13, r0);
|
|
// __ Xxx(al, r14, r14, r0);
|
|
//
|
|
// For each of the instructions above, run them with a different value in "rd"
|
|
// and "rn".
|
|
{
|
|
"name": "RdIsRn",
|
|
"operands": [
|
|
"rd", "rn"
|
|
],
|
|
"operand-filter": "rd == rn",
|
|
"inputs": [
|
|
"rd", "rn"
|
|
],
|
|
"input-filter": "rd == rn"
|
|
},
|
|
// Generate all combinations of instructions with different condition codes.
|
|
// For example:
|
|
// __ Xxx(eq, r0, r0, r0);
|
|
// __ Xxx(ne, r0, r0, r0);
|
|
// __ Xxx(cs, r0, r0, r0);
|
|
// ...
|
|
// __ Xxx(gt, r0, r0, r0);
|
|
// __ Xxx(le, r0, r0, r0);
|
|
// __ Xxx(al, r0, r0, r0);
|
|
//
|
|
// For each of the instructions above, run them against all combinations of
|
|
// NZCV bits.
|
|
{
|
|
"name": "ConditionVersusNZCV",
|
|
"operands": [
|
|
"cond"
|
|
],
|
|
"inputs": [
|
|
"apsr"
|
|
]
|
|
},
|
|
// We are interested in testing that the Q bit gets set and cleared, so we've
|
|
// limited the instruction generation to a single instruction and instead have
|
|
// stressed the values put in "rn" and "rm".
|
|
//
|
|
// So for this instruction, we choose to run it will all combinations of
|
|
// values in "rn" and "rm". Additionally, we include "qbit" in the inputs,
|
|
// which will make the test set or clear it before executing the instruction.
|
|
// Note that "qbit" needs to be declared as an input in the instruction
|
|
// description (see "List of inputs" section).
|
|
{
|
|
"name": "Qbit",
|
|
"operands": [
|
|
"rn", "rm"
|
|
],
|
|
"inputs": [
|
|
"qbit", "rn", "rm"
|
|
],
|
|
"operand-filter": "rn != rm'",
|
|
"operand-limit": 1
|
|
},
|
|
// Generate 10 random instructions with all different registers but use the
|
|
// default condition.
|
|
// For example:
|
|
// __ Xxx(al, r5, r1, r0);
|
|
// __ Xxx(al, r8, r9, r7);
|
|
// __ Xxx(al, r9, r1, r2);
|
|
// __ Xxx(al, r0, r6, r2);
|
|
// __ Xxx(al, r11, r9, r11);
|
|
// __ Xxx(al, r14, r2, r11);
|
|
// __ Xxx(al, r8, r2, r5);
|
|
// __ Xxx(al, r10, r0, r1);
|
|
// __ Xxx(al, r11, r2, r7);
|
|
// __ Xxx(al, r2, r6, r1);
|
|
//
|
|
// For each instruction, feed it 200 different combination of values in the
|
|
// three registers.
|
|
{
|
|
"name": "RegisterSimulatorTest",
|
|
"operands": [
|
|
"rd", "rn", "rm"
|
|
],
|
|
"inputs": [
|
|
"rd", "rn", "rm"
|
|
],
|
|
"operand-limit": 10,
|
|
"input-limit": 200
|
|
}
|
|
]
|
|
~~~
|
|
|
|
Assembler test cases are much simpler, here are some examples:
|
|
~~~
|
|
// Generate 2000 random instructions out of all possible operand combinations.
|
|
{
|
|
"name": "LotsOfRandomInstructions",
|
|
"operands": [
|
|
"cond", "rd", "rn", "rm"
|
|
],
|
|
"operand-limit": 2000
|
|
},
|
|
// Same as above but limit the test to 200 instructions where rd == rn.
|
|
{
|
|
"name": "RdIsRn",
|
|
"operands": [
|
|
"cond", "rd", "rn", "rm"
|
|
],
|
|
"operand-filter": "rd == rn",
|
|
"operand-limit": 200
|
|
}
|
|
~~~
|
|
|
|
As can be expected, assembler test do not have the notion of "inputs".
|
|
|
|
Here are details about each field. Note that all of them except for "name" are
|
|
optional.
|
|
|
|
* "name":
|
|
|
|
A unique name should be given to the test case, it will be used to give the
|
|
generated C++ `const Input[]` array a name.
|
|
|
|
* "operands":
|
|
|
|
List of operand names that we are interested in testing. The generator will
|
|
lookup the list of variants for each operand and build the product of all of
|
|
them. It will then choose the default variant for the operands not specified
|
|
here.
|
|
|
|
* "operand-filter":
|
|
|
|
As you would expect, the product of all operand variants may be huge. To
|
|
prevent this, you may specify a Python expression to filter the list.
|
|
|
|
* "operand-limit":
|
|
|
|
We can potentially obtain a *massive* set of variants of instructions, as we
|
|
are computing a product of operand variants in "operands". This field allows
|
|
us to limit this by choosing a random sample from the computed variants.
|
|
Note that this is a seeded pseudo-random sample, and the seed corresponds to
|
|
the test case description. The same test case description will always
|
|
generate the same code.
|
|
|
|
* "inputs":
|
|
|
|
This is exactly the same as "operands" but for inputs.
|
|
|
|
* "input-filter":
|
|
|
|
Ditto.
|
|
|
|
* "input-limit":
|
|
|
|
Ditto.
|
|
|
|
Here is an example of the C++ code that will be generated for a given test case.
|
|
For simplicity, let's generate tests for an instruction with only `NZCV` and two
|
|
registers as inputs.
|
|
|
|
For the following test case, which will target encodings where `rd` and `rn` are
|
|
the same registers:
|
|
|
|
~~~
|
|
{
|
|
"name": "RdIsRn",
|
|
"operands": [
|
|
"rd", "rn"
|
|
],
|
|
"operand-filter": "rd == rn",
|
|
"inputs": [
|
|
"rd", "rn"
|
|
],
|
|
"input-filter": "rd == rn"
|
|
},
|
|
~~~
|
|
|
|
It will generate the following input array.
|
|
|
|
~~~
|
|
// apsr, rd, rn
|
|
static const Inputs kRdIsRn[] = {{NoFlag, 0x00000000, 0x00000000},
|
|
{NoFlag, 0xffffffff, 0xffffffff},
|
|
{NoFlag, 0xabababab, 0xabababab},
|
|
{NoFlag, 0x5a5a5a5a, 0x5a5a5a5a}};
|
|
~~~
|
|
|
|
We can see that the default apsr value was chosen (NoFlag), as apsr is not in
|
|
the list of "inputs".
|
|
|
|
It will also generate a list of instructions to test:
|
|
|
|
~~~
|
|
static const TestLoopData kTests[] = {
|
|
{{al, r1, r1, 0x000000ab}, ARRAY_SIZE(kRdIsRn), kRdIsRn, "RdIsRn"},
|
|
{{al, r2, r2, 0x000000ab}, ARRAY_SIZE(kRdIsRn), kRdIsRn, "RdIsRn"},
|
|
{{al, r8, r8, 0x000000ab}, ARRAY_SIZE(kRdIsRn), kRdIsRn, "RdIsRn"},
|
|
{{al, r9, r9, 0x000000ab}, ARRAY_SIZE(kRdIsRn), kRdIsRn, "RdIsRn"},
|
|
};
|
|
~~~
|
|
|
|
As a result, the new test we will assemble each instructions in "mnemonics" with
|
|
all of the operands described in `kTests` above. And each instruction will be
|
|
executed and passed all inputs in `kRdIsRn`.
|
|
"""
|
|
|
|
import subprocess
|
|
import argparse
|
|
import string
|
|
import re
|
|
import multiprocessing
|
|
import functools
|
|
|
|
import test_generator.parser
|
|
|
|
|
|
default_config_files = [
|
|
# A32 and T32 tests
|
|
'test/aarch32/config/rd-rn-rm.json',
|
|
'test/aarch32/config/cond-dt-drt-drd-drn-drm-float.json',
|
|
|
|
# A32 specific tests
|
|
'test/aarch32/config/cond-rd-rn-operand-const-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-shift-amount-1to31-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-shift-amount-1to32-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-shift-rs-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-ror-amount-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-pc-a32.json',
|
|
'test/aarch32/config/cond-rd-rn-rm-a32.json',
|
|
'test/aarch32/config/cond-rd-operand-const-a32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-a32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-shift-amount-1to31-a32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-shift-amount-1to32-a32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-shift-rs-a32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-ror-amount-a32.json',
|
|
'test/aarch32/config/cond-rd-memop-immediate-512-a32.json',
|
|
'test/aarch32/config/cond-rd-memop-immediate-8192-a32.json',
|
|
'test/aarch32/config/cond-rd-memop-rs-a32.json',
|
|
'test/aarch32/config/cond-rd-memop-rs-shift-amount-1to31-a32.json',
|
|
'test/aarch32/config/cond-rd-memop-rs-shift-amount-1to32-a32.json',
|
|
|
|
# T32 specific tests
|
|
'test/aarch32/config/cond-rd-rn-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-rm-t32.json',
|
|
'test/aarch32/config/cond-rdlow-rnlow-rmlow-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-const-t32.json',
|
|
'test/aarch32/config/cond-rd-pc-operand-imm12-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-imm12-t32.json',
|
|
'test/aarch32/config/cond-rd-pc-operand-imm8-t32.json',
|
|
'test/aarch32/config/cond-rd-sp-operand-imm8-t32.json',
|
|
'test/aarch32/config/cond-rdlow-rnlow-operand-immediate-t32.json',
|
|
'test/aarch32/config/cond-sp-sp-operand-imm7-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-shift-amount-1to31-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-shift-amount-1to32-t32.json',
|
|
'test/aarch32/config/cond-rd-rn-operand-rm-ror-amount-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-const-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-imm16-t32.json',
|
|
'test/aarch32/config/cond-rdlow-operand-imm8-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-shift-amount-1to31-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-shift-amount-1to32-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-shift-rs-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-ror-amount-t32.json',
|
|
'test/aarch32/config/cond-rd-operand-rn-t32.json',
|
|
]
|
|
|
|
|
|
# Link a test type with a template file.
|
|
template_files = {
|
|
'simulator': "test/aarch32/config/template-simulator-aarch32.cc.in",
|
|
'assembler': "test/aarch32/config/template-assembler-aarch32.cc.in",
|
|
'macro-assembler': "test/aarch32/config/template-macro-assembler-aarch32.cc.in",
|
|
'assembler-negative': "test/aarch32/config/template-assembler-negative-aarch32.cc.in",
|
|
}
|
|
|
|
|
|
def BuildOptions():
|
|
result = argparse.ArgumentParser(
|
|
description = 'Test generator for AArch32.',
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
|
result.add_argument('--config-files', nargs='+',
|
|
default=default_config_files,
|
|
metavar='FILE',
|
|
help='Configuration files, each will generate a test file.')
|
|
result.add_argument('--clang-format',
|
|
default='clang-format-4.0', help='Path to clang-format.')
|
|
result.add_argument('--jobs', '-j', type=int, metavar='N',
|
|
default=multiprocessing.cpu_count(),
|
|
help='Allow N jobs at once')
|
|
result.add_argument('--skip-traces', action='store_true',
|
|
help='Skip generation of placeholder traces.')
|
|
return result.parse_args()
|
|
|
|
|
|
def DoNotEditComment(template_file):
|
|
# We rely on `clang-format` to wrap this comment to 80 characters.
|
|
return """
|
|
// -----------------------------------------------------------------------------
|
|
// This file is auto generated from the {} template file using tools/generate_tests.py.
|
|
//
|
|
// PLEASE DO NOT EDIT.
|
|
// -----------------------------------------------------------------------------
|
|
""".format(template_file)
|
|
|
|
def GenerateTest(generator, clang_format, skip_traces):
|
|
template_file = template_files[generator.test_type]
|
|
generated_file = ""
|
|
with open(template_file, "r") as f:
|
|
# Strip out comments starting with three forward slashes before creating the
|
|
# string.Template object.
|
|
template = string.Template(re.sub("\/\/\/.*", "", f.read()))
|
|
|
|
# The `generator` object has methods generating strings to fill the template.
|
|
generated_file = template.substitute({
|
|
# Add a top comment stating this file is auto-generated.
|
|
'do_not_edit_comment': DoNotEditComment(template_file),
|
|
|
|
# List of mnemonics.
|
|
'instruction_list_declaration': generator.InstructionListDeclaration(),
|
|
|
|
# Declarations.
|
|
'operand_list_declaration': generator.OperandDeclarations(),
|
|
'input_declarations': generator.InputDeclarations(),
|
|
|
|
# Definitions.
|
|
'input_definitions': generator.InputDefinitions(),
|
|
'test_case_definitions': generator.TestCaseDefinitions(),
|
|
|
|
# Include traces.
|
|
'include_trace_files': generator.IncludeTraceFiles(),
|
|
|
|
# Define a typedef for the MacroAssembler method.
|
|
'macroassembler_method_args': generator.MacroAssemblerMethodArgs(),
|
|
|
|
# Generate code to switch instruction set.
|
|
'macroassembler_set_isa': generator.MacroAssemblerSetISA(),
|
|
|
|
# Generate code to emit instructions.
|
|
'code_instantiate_operands': generator.CodeInstantiateOperands(),
|
|
'code_prologue': generator.CodePrologue(),
|
|
'code_epilogue': generator.CodeEpilogue(),
|
|
'code_parameter_list': generator.CodeParameterList(),
|
|
|
|
# Generate code to trace the execution and print C++.
|
|
'trace_print_outputs': generator.TracePrintOutputs(),
|
|
|
|
# Generate code to compare the results against a trace.
|
|
'check_instantiate_results': generator.CheckInstantiateResults(),
|
|
'check_instantiate_inputs': generator.CheckInstantiateInputs(),
|
|
'check_instantiate_references': generator.CheckInstantiateReferences(),
|
|
'check_results_against_references':
|
|
generator.CheckResultsAgainstReferences(),
|
|
'check_print_input': generator.CheckPrintInput(),
|
|
'check_print_expected': generator.CheckPrintExpected(),
|
|
'check_print_found': generator.CheckPrintFound(),
|
|
|
|
'test_isa': generator.TestISA(),
|
|
'test_name': generator.TestName(),
|
|
'isa_guard': generator.GetIsaGuard()
|
|
})
|
|
# Create the test case and pipe it through `clang-format` before writing it.
|
|
with open(
|
|
"test/aarch32/test-{}-{}-{}.cc".format(generator.test_type,
|
|
generator.test_name,
|
|
generator.test_isa),
|
|
"w") as f:
|
|
proc = subprocess.Popen([clang_format], stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE)
|
|
out, _ = proc.communicate(generated_file.encode())
|
|
f.write(out.decode())
|
|
if not skip_traces:
|
|
# Write placeholder trace files into 'test/aarch32/traces/'.
|
|
generator.WriteEmptyTraces("test/aarch32/traces/")
|
|
print("Generated {} {} test for \"{}\".".format(generator.test_isa.upper(),
|
|
generator.test_type,
|
|
generator.test_name))
|
|
|
|
if __name__ == '__main__':
|
|
args = BuildOptions()
|
|
|
|
# Each file in `args.config_files` populates a `Generator` object.
|
|
generators = test_generator.parser.Parse('test/aarch32/config/data-types.json',
|
|
args.config_files)
|
|
|
|
# Call the `GenerateTest` function for each generator object in parallel. This
|
|
# will use as many processes as defined by `-jN`, which defaults to 1.
|
|
with multiprocessing.Pool(processes=args.jobs) as pool:
|
|
pool.map(functools.partial(GenerateTest, clang_format=args.clang_format,
|
|
skip_traces=args.skip_traces),
|
|
generators)
|