1# Copyright 2016, VIXL authors
2# All rights reserved.
3#
4# Redistribution and use in source and binary forms, with or without
5# modification, are permitted provided that the following conditions are met:
6#
7#   * Redistributions of source code must retain the above copyright notice,
8#     this list of conditions and the following disclaimer.
9#   * Redistributions in binary form must reproduce the above copyright notice,
10#     this list of conditions and the following disclaimer in the documentation
11#     and/or other materials provided with the distribution.
12#   * Neither the name of ARM Limited nor the names of its contributors may be
13#     used to endorse or promote products derived from this software without
14#     specific prior written permission.
15#
16# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
17# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27import json
28import re
29import os
30import hashlib
31import collections
32import itertools
33
34from test_generator import data_types
35from test_generator import generator
36
37class DataTypeBuilder(object):
38  """
39  Factory object for building `data_types.Operand` and `data_types.Input`
40  objects. This object stores information about all operand and input types
41  described in JSON as dictionnaries indexed by their identifier. See
42  `test/a32/config/data-types.json` as a reference.
43
44  Attributes:
45    operand_types    Dictionnary of type names corresponding to the JSON
46                     "type" field.
47    operand_variants Dictionnary of (variants, default) tuples.
48
49    input_types      Dictionnary of type names corresponding to the JSON
50                     "type" field.
51    input_values     Dictionnary of (values, default) tuples.
52  """
53
54  def __init__(self, operand_types, operand_variants, input_types,
55               input_values):
56    self.operand_types = operand_types
57    self.operand_variants = operand_variants
58    self.input_types = input_types
59    self.input_values = input_values
60
61  def BuildOperand(self, name, identifier):
62    """
63    Build a `data_types.Operand` object with the name `name`. `identifier`
64    identifies which type we want to create, as declared in JSON.
65    """
66    type_name = self.operand_types[identifier]
67    variants, default = self.operand_variants[identifier]
68    # We simply pass the `type_name` as a parameter which will be used verbatim
69    # in the code.
70    return data_types.Operand(name, type_name, variants, default)
71
72  def BuildInput(self, name, identifier):
73    """
74    Build a `data_types.Input` object with the name `name`. `identifier`
75    identifies which type we want to create, as declared in JSON.
76    """
77    type_name = self.input_types[identifier]
78    values, default = self.input_values[identifier]
79    # For `data_types.Input` types, the `type_name` refers to the actual name of
80    # the Python class, inheriting from `Input`. This is done so that different
81    # input types can generate different C++ code by overriding the `Load` and
82    # `Store` methods.
83    input_constructor = getattr(data_types, type_name)
84    return input_constructor(name, values, default)
85
86
87def LoadJSON(filename):
88  """
89  Read `filename`, strip its comments and load as JSON.
90  """
91  with open(filename, "r") as f:
92    match_cpp_comments = re.compile("//.*\n")
93    # The order in which structures are described in JSON matters as we use them
94    # as a seed. Computing a hash from a unordered dict always gives a different
95    # value. We use the `object_pairs_hook` to make the json module create
96    # `OrderedDict` objects instead of builtin `dict` objects.
97    return json.loads(match_cpp_comments.sub("", f.read()),
98                      object_pairs_hook=collections.OrderedDict)
99
100
101def ParseDataTypes(json_data_types):
102  """
103  Build a `DataTypeBuilder` object containing all information from the JSON
104  description in `json_data_types`.
105
106  ~~~
107  {
108    "operands": [
109      {
110        "identifier": "AllRegistersButPC"
111        "type": "Register"
112        "variants": [
113          "r0",
114          "r1",
115          "r2",
116          "r3"
117        ]
118        "default": "r0"
119      },
120      {
121        ...
122      }
123    ],
124    "inputs": [
125      {
126        "identifier": "Register"
127        "type": "Register"
128        "values": [
129          "0x00000000",
130          "0xffffffff",
131          "0xabababab"
132        ]
133        "default": "0xabababab"
134      },
135      {
136        ...
137      }
138    ]
139  }
140  ~~~
141  """
142  operand_types = {
143      json_operand_type["identifier"]: json_operand_type["type"]
144      for json_operand_type in json_data_types["operands"]
145  }
146  operand_variants = {
147      json_operand_type["identifier"]:
148          (json_operand_type["variants"], json_operand_type["default"])
149      for json_operand_type in json_data_types["operands"]
150  }
151  input_types = {
152      json_input_type["identifier"]: json_input_type["type"]
153      for json_input_type in json_data_types["inputs"]
154  }
155  input_values = {
156      json_input_type["identifier"]:
157          (json_input_type["values"], json_input_type["default"])
158      for json_input_type in json_data_types["inputs"]
159  }
160  return DataTypeBuilder(operand_types, operand_variants, input_types, input_values)
161
162
163def ParseDescription(data_type_builder, json_description):
164  """
165  Parse the instruction description into a
166  (`generator.OperandList`, `generator.InputList`) tuple.
167
168  Example for an instruction that takes a condidition code, two registers and an
169  immediate as operand. It will also need inputs for the registers, as well as
170  NZCV flags.
171  ~~~
172  {
173    "operands": [
174      {
175        "name": "cond",
176        "type": "Condition",
177      },
178      {
179        "name": "rd",
180        "type": "RegisterScratch",
181      },
182      {
183        "name": "rn",
184        "type": "RegisterScratch",
185      },
186      // The last operand needs to be wrapped into a C++ `Operand` object. We
187      // declare the operands that need to be wrapped as a list.
188      {
189        "name": "op",
190        "wrapper": "Operand",
191        "operands": [
192          {
193            "name": "immediate",
194            "type": "ModifiedImmediate",
195          }
196        ]
197      }
198    ],
199    "inputs": [
200      {
201        "name": "apsr",
202        "type": "NZCV"
203      },
204      {
205        "name": "rd",
206        "type": "Register"
207      },
208      {
209        "name": "rn",
210        "type": "Register"
211      }
212    ]
213  ]
214  ~~~
215  """
216
217  operands = []
218  for json_operand in json_description["operands"]:
219    if "name" in json_operand and "type" in json_operand:
220      operands.append(data_type_builder.BuildOperand(json_operand["name"],
221                                                     json_operand["type"]))
222    elif "name" in json_operand and \
223         "wrapper" in json_operand and \
224         "operands" in json_operand:
225      wrapped_operands = [
226          data_type_builder.BuildOperand(json_wrapped_operand["name"],
227                                         json_wrapped_operand["type"])
228          for json_wrapped_operand in json_operand["operands"]
229      ]
230      operands.append(data_types.OperandWrapper(json_operand["name"],
231                                                json_operand["wrapper"],
232                                                wrapped_operands))
233    else:
234      raise Exception("Parser failed to recognize JSON \"description\".")
235  operand_list = generator.OperandList(operands)
236
237  json_description_inputs = json_description["inputs"]
238  input_list = generator.InputList([
239      data_type_builder.BuildInput(json_input["name"], json_input["type"])
240      for json_input in json_description_inputs
241  ])
242
243  return operand_list, input_list
244
245
246def ParseTestCase(json_test_case):
247  """
248  Build a `generator.TestCase` object from its JSON description.
249
250  ~~~
251  {
252    "name": "RdIsNotRn",
253    "operands": [
254      "rd", "rn"
255    ],
256    "inputs": [
257      "rd", "rn"
258    ],
259    "operand-filter": "rd != rn", // Python code to limit operand generation.
260    "operand-limit": 10           // Choose a random sample of 10 operands.
261  }
262  ...
263  {
264    "name": "Flags",
265    "operands": [
266      "cond"
267    ],
268    "inputs": [
269      "apsr", "q"
270    ],
271    "input-filter": "q == \"QFlag\"", // Python code to limit input generation
272    "input-limit": 200                // Choose a random sample of 200 inputs.
273  }
274  ...
275  {
276    "name": "InITBlock",
277    "operands": [
278      "cond", "rd", "rn", "rm"
279    ],
280    "in-it-block": "{cond}", // Generate an extra IT instruction. This string
281                             // will be used as the operand passed to IT. One
282                             // needs to specify under what name the condition
283                             // operand is represented, in braces.
284    "operand-filter": "cond != 'al' and rd == rm"
285  }
286  ~~~
287  """
288
289  # TODO: The fields in "operands" and "inputs" respectively refer to operands
290  # and inputs declared in the instruction description (see `ParseDescription`).
291  # We should assert that the user hasn't miss typed them and raise an
292  # exception.
293
294  # If the fields are not present, give them default values (empty list,
295  # "True", or "None").
296  operand_names = json_test_case["operands"] \
297      if "operands" in json_test_case else []
298  input_names = json_test_case["inputs"] if "inputs" in json_test_case else []
299  operand_filter = json_test_case["operand-filter"] \
300      if "operand-filter" in json_test_case else "True"
301  input_filter = json_test_case["input-filter"] \
302      if "input-filter" in json_test_case else "True"
303  operand_limit = json_test_case["operand-limit"] \
304      if "operand-limit" in json_test_case else None
305  input_limit = json_test_case["input-limit"] \
306      if "input-limit" in json_test_case else None
307  in_it_block = json_test_case["in-it-block"] \
308      if "in-it-block" in json_test_case else None
309
310  # Create a seed from the test case description. It will only change if the
311  # test case has changed.
312  md5 = hashlib.md5(str(json_test_case).encode())
313  seed = md5.hexdigest()
314
315  return generator.TestCase(json_test_case["name"], seed, operand_names, input_names,
316                            operand_filter, input_filter, operand_limit,
317                            input_limit, in_it_block)
318
319
320def ParseTestFile(test_name, test_isa, mnemonics, operand_list, input_list,
321                  json_test_file):
322  """
323  Build a `generator.Generator` object from a test file description. We have one
324  for each generated test files.
325
326  ~~~
327  {
328    "type": "simulator",  // Type of the test. This will control the prefix we
329                          // use when naming the file to generate.
330    "name": "special-case",  // Optional name that will be included in the
331                             // generated filename.
332    "mnemonics": [  // Optional list of instruction, overriding the top-level
333      "Adc",        // one.
334      "Add",
335      ...
336    ],
337    "test-cases": [
338      ... // Test case descriptions parsed with `ParseTestCase`.
339    ]
340  }
341  ~~~
342  """
343  name = json_test_file["name"] if "name" in json_test_file else ""
344  if name is not "":
345    test_name = test_name + "-" + name
346  # Override the top-level mnemonics list with a subset.
347  if "mnemonics" in json_test_file:
348    if set(json_test_file["mnemonics"]) == set(mnemonics):
349      raise Exception(
350          "Overriding mnemonic list is identical to the top-level list")
351    if not(set(json_test_file["mnemonics"]) < set(mnemonics)):
352      raise Exception(
353          "Overriding mnemonic list should a subset of the top-level list")
354    mnemonics = json_test_file["mnemonics"]
355  test_cases = [
356      ParseTestCase(json_test_case)
357      for json_test_case in json_test_file["test-cases"]
358  ]
359  return generator.Generator(test_name, test_isa, json_test_file["type"],
360                             mnemonics, operand_list, input_list, test_cases)
361
362
363def ParseConfig(test_name, test_isas, data_type_builder, json_config):
364  """
365  Return a list of `generator.Generator` objects from a JSON description. This
366  is the top-level description.
367
368  ~~~
369  {
370    "mnemonics": [
371      "Adc",
372      "Add",
373      ...
374    ],
375    "description": [
376      ... // Instruction description parsed with `ParseDescription`.
377    ],
378    "test-files": [
379      ... // Test files descriptions parsed with `ParseTestFile`.
380    ]
381  }
382  ~~~
383  """
384  mnemonics = json_config["mnemonics"]
385  operand_list, input_list = ParseDescription(
386      data_type_builder, json_config["description"])
387
388  return itertools.chain(*[[
389          ParseTestFile(test_name, test_isa, mnemonics, operand_list,
390                        input_list, json_test_file)
391          for json_test_file in json_config["test-files"]
392      ]
393      for test_isa in test_isas
394  ])
395
396
397def GetTestNameAndISAFromFileName(filename):
398  """
399  Return a tuple (name, [isa, ...]) extracted from the file name.
400  """
401  # Strip the ".json" extension
402  stripped_basename = os.path.splitext(os.path.basename(filename))[0]
403  # The ISA is the last element in the filename, seperated with "-".
404  if stripped_basename.endswith(('-a32', '-t32')):
405    isa = [stripped_basename[-3:]]
406    test_name = stripped_basename[:-4]
407  else:
408    # If the ISA is ommitted, support both.
409    isa = ["a32", "t32"]
410    test_name = stripped_basename
411
412  return (test_name, isa)
413
414
415def GetTestNameFromFileName(filename):
416  """
417  Return the name given to this test from its file name, stripped of the
418  optional "a32" or "t32" at the end.
419  """
420  test_name, _ = GetTestNameAndISAFromFileName(filename)
421  return test_name
422
423
424def GetISAsFromFileName(filename):
425  """
426  Return a list of ISAs supported by the test, from the file name, either
427  ["a32"], ["t32"] or both.
428  """
429  _, isas = GetTestNameAndISAFromFileName(filename)
430
431  return isas
432
433def Parse(data_type_file, config_files):
434  """
435  Parse the `data_type_file` and `test_case_files` json description files into a
436  list of (name, test_case) tuples. Test cases are `generator.TestCase`
437  objects that can be used to generate C++.
438  """
439
440  # Create a `DataTypeBuilder` object. This object will passed down and used to
441  # instantiate `data_types.Operand` and `data_types.Input` objects.
442  data_type_builder = ParseDataTypes(LoadJSON(data_type_file))
443
444  # Build a list of (name, JSON) tuples to represent the new tests.
445  json_configs = [
446      # We create the name of the test by looking at the file name stripped of
447      # its extension.
448      (GetTestNameFromFileName(config_file), GetISAsFromFileName(config_file),
449       LoadJSON(config_file))
450      for config_file in config_files
451  ]
452
453  # Return a list of Generator objects. The generator is the entry point to
454  # generate a file.
455  # Note that `ParseConfig` returns a list of generators already. We use `chain`
456  # here to flatten a list of lists into just a list.
457  return itertools.chain(*[
458      ParseConfig(test_name, test_isas, data_type_builder, json_config)
459      for test_name, test_isas, json_config in json_configs
460  ])
461