# 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.
|
|
class OperandBase(object):
|
"""
|
Base class for operands. An operand represents an argument passed to the
|
macro-assembler to generate an instruction. For example, registers, conditions
|
or C++ `Operand` and `MemOperand` objects are operands. We can think of them
|
as "assemble-time" data.
|
|
An operand is described with a type name, corresponding to the C++ type used
|
to represent it (e.g. "Register", "ShiftType", "MemOperand", ...) and a name
|
to identify it.
|
|
Attributes:
|
name Name for the operand. It is used to declare variable names.
|
type_name C++ type for the operand.
|
"""
|
|
def __init__(self, name, type_name):
|
self.name = name
|
self.type_name = type_name
|
|
@staticmethod
|
def __iter__():
|
"""
|
Operand types have to act as an iterator so that `generator.OperandList` can
|
unwrap them (see `OperandWrapper` and `generator.OperandList.unwrap()`).
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def GetArgumentType():
|
"""
|
Return the type to be printed when passing this operand as an argument. For
|
example, we could pass it by value or by reference.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def Declare():
|
"""
|
Generate code to declare the operand `struct Operands`.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def Instantiate():
|
"""
|
Generate code to instantiate the operand from inside a `kTests` loop, with
|
`i` being the index variable.
|
"""
|
raise NotImplementedError()
|
|
|
class Operand(OperandBase):
|
"""
|
Representation of a single operand. An operand has different variants
|
(e.g. "r0", "r1", "LSL", ...) and a default one.
|
|
Attributes:
|
variants List of variants the operand can take.
|
default Default variant to use.
|
|
Note that the `variants` and `default` attributes are not used from inside the
|
class. They are public fields used by the `TestCase` object to generate C++.
|
"""
|
|
def __init__(self, name, type_name, variants, default):
|
super().__init__(name, type_name)
|
self.variants = variants
|
self.default = default
|
|
def __iter__(self):
|
"""
|
Iterating over a single operand just yields the operand object.
|
"""
|
yield self
|
|
def GetArgumentType(self):
|
"""
|
A single operand is passed to the macro-assembler by value. We just print
|
the type name as it is.
|
"""
|
return self.type_name
|
|
def Declare(self):
|
"""
|
Generate code to declare the operand as a single member in
|
`struct Operands`.
|
"""
|
return "{type_name} {name};".format(type_name=self.type_name,
|
name=self.name)
|
|
def Instantiate(self):
|
"""
|
Generate code to instantiate the operand as a single local variable.
|
"""
|
code = "{type_name} {name} = kTests[i].operands.{name};"
|
return code.format(type_name=self.type_name, name=self.name)
|
|
|
class OperandWrapper(OperandBase):
|
"""
|
Representation for an operand type which wraps a list of more operands. It
|
corresponds to C++ objects such as `Operand` or `MemOperand`.
|
|
Attributes:
|
operand_list List of operand that this object wraps.
|
"""
|
|
def __init__(self, name, type_name, operand_list):
|
super().__init__(name, type_name)
|
self.operand_list = operand_list
|
|
def __iter__(self):
|
"""
|
Iterate through the list of operands. This is required by
|
`OperandList.unwrap()`.
|
"""
|
return iter(self.operand_list)
|
|
def GetArgumentType(self):
|
"""
|
An operand wrapper object will be passed to the macro-assembler by
|
reference.
|
"""
|
return "const " + self.type_name + "&"
|
|
def Declare(self):
|
"""
|
An `OperandWrapper` object does not need to be declared in
|
`struct Operands`. Although we do need to declare all underlying operands.
|
"""
|
return "\n".join([operand.Declare() for operand in self.operand_list])
|
|
def Instantiate(self):
|
"""
|
Instantiate the underlying operands first and then finally instantiate the
|
wrapper object.
|
|
For example, if the object represents a C++ `Operand` type for a shifted
|
register we would get:
|
|
~~~
|
Register rm = kTests[i].operands.rm;
|
Shift shift_type = kTests[i].operands.shift_type;
|
uint32_t amount = kTests[i].operands.amount;
|
Operand op(rm, shift_type, amount);
|
~~~
|
"""
|
instantiate_wrapped_operands = "\n".join([
|
operand.Instantiate()
|
for operand in self.operand_list
|
])
|
instantiate_this = "{type_name} {name}({arguments});".format(
|
type_name=self.type_name,
|
name=self.name,
|
arguments=", ".join([operand.name for operand in self.operand_list]))
|
return instantiate_wrapped_operands + "\n" + instantiate_this
|
|
|
class Input(object):
|
"""
|
Base class for all input types instantiated from a JSON description. This
|
class should not be instantiated directly, instead, we create subclasses for
|
each kind of input we have.
|
|
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.
|
|
Attributes:
|
name Name of the input. It is used to declare variable names.
|
|
values List of values this input can take.
|
default Default value to use.
|
"""
|
|
def __init__(self, name, values, default):
|
self.name = name
|
|
self.values = values
|
self.default = default
|
|
@staticmethod
|
def Prologue():
|
"""
|
Return a string describing what C++ code to emit before the instruction
|
under test is emitted.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def Epilogue():
|
"""
|
Return a string describing what C++ code to emit after the instruction under
|
test is emitted.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def Declare():
|
"""
|
Generate code to declare the input in `struct Inputs`.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def PrintInput(suffix = ""):
|
"""
|
Generate code to print the input referred to by `self.name`. Optionally add
|
`suffix` to the input's name.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def PrintOutput():
|
"""
|
Generate code to print the input from the result buffer, indexed with `i`
|
and `j`.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def InstantiateResult(suffix = ""):
|
"""
|
Generate code to instantiate an input from the result buffer, indexed with
|
`i`. Optionally add `suffix` to the input's name.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def InstantiateInput(suffix = ""):
|
"""
|
Generate code to instantiate an input from the input buffer, indexed with
|
`i`. Optionally add `suffix` to the input's name.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def InstantiateReference(suffix = ""):
|
"""
|
Generate code to instantiate an input from the reference buffer, indexed
|
with `i`. Optionally add `suffix` to the input's name.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def Compare(left_suffix, operand, right_suffix):
|
"""
|
Generate code as a C++ expression comparing two inputs of this type.
|
"""
|
raise NotImplementedError()
|
|
|
class Scalar(Input):
|
"""
|
Base class for inputs that are represented as a single scalar value.
|
Subclasses should implement `TypeName()`, `Pri()` and `NDigit()` to specify
|
how they should be represented and printed, see the `U32` class for an
|
example.
|
"""
|
|
@staticmethod
|
def TypeName():
|
"""
|
Return the type name as used to declare and instantiate this input.
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def Pri():
|
"""
|
Return the format string used to generate a call to `printf` to print this
|
input type. For example, "PRIx32".
|
"""
|
raise NotImplementedError()
|
|
@staticmethod
|
def NDigit():
|
"""
|
Return the number of digits to use to print this input type.
|
"""
|
raise NotImplementedError()
|
|
def Declare(self):
|
"""
|
A scalar input is declared as a single member in `struct Inputs`.
|
"""
|
return "{type_name} {name};".format(type_name=self.TypeName(),
|
name=self.name)
|
|
def PrintInput(self, suffix = ""):
|
code = "printf(\"0x%0{n_digit}\" {pri}, {name});"
|
return code.format(n_digit=self.NDigit(), pri=self.Pri(),
|
name=self.name + suffix)
|
|
def PrintOutput(self):
|
code = "printf(\"0x%0{n_digit}\" {pri}, results[i]->outputs[j].{name});"
|
return code.format(n_digit=self.NDigit(), pri=self.Pri(), name=self.name)
|
|
def InstantiateResult(self, suffix = ""):
|
code = "{type_name} {name}{suffix} = results[i]->outputs[j].{name};"
|
return code.format(type_name=self.TypeName(), name=self.name, suffix=suffix)
|
|
def InstantiateInput(self, suffix = ""):
|
code = "{type_name} {name}{suffix} = kTests[i].inputs[j].{name};"
|
return code.format(type_name=self.TypeName(), name=self.name, suffix=suffix)
|
|
def InstantiateReference(self, suffix = ""):
|
code = "{type_name} {name}{suffix} = reference[i].outputs[j].{name};"
|
return code.format(type_name=self.TypeName(), name=self.name, suffix=suffix)
|
|
def Compare(self, left_suffix, operand, right_suffix):
|
return """
|
({name}{left_suffix} {operand} {name}{right_suffix})
|
""".format(name=self.name, left_suffix=left_suffix, operand=operand,
|
right_suffix=right_suffix)
|
|
|
class U32(Scalar):
|
"""
|
Base class for inputs that can be represented as 32 bit unsigned words.
|
"""
|
|
@staticmethod
|
def TypeName():
|
return "uint32_t"
|
|
@staticmethod
|
def Pri():
|
return "PRIx32"
|
|
@staticmethod
|
def NDigit():
|
return 8
|
|
|
class F64(Scalar):
|
@staticmethod
|
def TypeName():
|
return "uint64_t"
|
|
@staticmethod
|
def Pri():
|
return "PRIx64"
|
|
@staticmethod
|
def NDigit():
|
return 16
|
|
def PrintInput(self, suffix = ""):
|
code = "printf(\"0x%0{n_digit}\" {pri} \"(%g)\", {name}, RawbitsToDouble({name}));"
|
return code.format(n_digit=self.NDigit(), pri=self.Pri(),
|
name=self.name + suffix)
|
|
|
class Register(U32):
|
"""
|
Description of a Register input. The `Prologue` and `Epilogue` methods
|
describe what C++ code to emit to set and record the value of a register
|
before and after executing an instruction.
|
"""
|
|
def Prologue(self):
|
code = "__ Ldr({name}, MemOperand(input_ptr, offsetof(Inputs, {name})));"
|
return code.format(name=self.name)
|
|
def Epilogue(self):
|
code = "__ Str({name}, MemOperand(result_ptr, offsetof(Inputs, {name})));"
|
return code.format(name=self.name)
|
|
|
class DRegisterF64(F64):
|
def Prologue(self):
|
code = "__ Vldr({name}, MemOperand(input_ptr, offsetof(Inputs, {name})));"
|
return code.format(name=self.name)
|
|
def Epilogue(self):
|
code = "__ Vstr({name}, MemOperand(result_ptr, offsetof(Inputs, {name})));"
|
return code.format(name=self.name)
|
|
|
class NZCV(U32):
|
"""
|
Description of NZCV flags as inputs to an instruction.
|
|
The `Prologue` and `Epilogue` methods describe what C++ code to emit to set
|
and record the NZCV flags before and after emitting the instruction under
|
test.
|
"""
|
|
def Prologue(self):
|
# When setting the `NZCV` flags, we need to make sure we do not override the
|
# `Q` bit. Therefore we use two scratch registers that we push on the stack
|
# first to allow the instruction to use them as operands.
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register nzcv_bits = temp_registers.Acquire();
|
Register saved_q_bit = temp_registers.Acquire();
|
// Save the `Q` bit flag.
|
__ Mrs(saved_q_bit, APSR);
|
__ And(saved_q_bit, saved_q_bit, QFlag);
|
// Set the `NZCV` and `Q` flags together.
|
__ Ldr(nzcv_bits, MemOperand(input_ptr, offsetof(Inputs, {})));
|
__ Orr(nzcv_bits, nzcv_bits, saved_q_bit);
|
__ Msr(APSR_nzcvq, nzcv_bits);
|
}}
|
"""
|
return code.format(self.name)
|
|
def Epilogue(self):
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register nzcv_bits = temp_registers.Acquire();
|
__ Mrs(nzcv_bits, APSR);
|
// Only record the NZCV bits.
|
__ And(nzcv_bits, nzcv_bits, NZCVFlag);
|
__ Str(nzcv_bits, MemOperand(result_ptr, offsetof(Inputs, {})));
|
}}
|
"""
|
return code.format(self.name)
|
|
|
class Q(U32):
|
"""
|
Description of the Q flag as inputs to an instruction.
|
|
The `Prologue` and `Epilogue` methods describe what C++ code to emit to set
|
and record the Q flag before and after emitting the instruction under test.
|
"""
|
|
def Prologue(self):
|
# When clearing or setting the `Q` bit, we need to make sure the `NZCV`
|
# flags are not overriden. Therefore we use two scratch registers that we
|
# push on the stack first to allow the instruction to use them as operands.
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register q_bit = temp_registers.Acquire();
|
Register saved_nzcv_bits = temp_registers.Acquire();
|
// Save the `NZCV` flags.
|
__ Mrs(saved_nzcv_bits, APSR);
|
__ And(saved_nzcv_bits, saved_nzcv_bits, NZCVFlag);
|
// Set the `NZCV` and `Q` flags together.
|
__ Ldr(q_bit, MemOperand(input_ptr, offsetof(Inputs, {})));
|
__ Orr(q_bit, q_bit, saved_nzcv_bits);
|
__ Msr(APSR_nzcvq, q_bit);
|
}}
|
"""
|
return code.format(self.name)
|
|
def Epilogue(self):
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register q_bit = temp_registers.Acquire();
|
__ Mrs(q_bit, APSR);
|
// Only record the Q bit.
|
__ And(q_bit, q_bit, QFlag);
|
__ Str(q_bit, MemOperand(result_ptr, offsetof(Inputs, {})));
|
}}
|
"""
|
return code.format(self.name)
|
|
|
class GE(U32):
|
"""
|
Description of the GE flag as inputs to an instruction.
|
|
The `Prologue` and `Epilogue` methods describe what C++ code to emit to set
|
and record the GE flags before and after emitting the instruction under test.
|
"""
|
|
def Prologue(self):
|
# We need a scratch register to load the `GE` flags.
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register ge_bits = temp_registers.Acquire();
|
__ Ldr(ge_bits, MemOperand(input_ptr, offsetof(Inputs, {})));
|
__ Msr(APSR_g, ge_bits);
|
}}
|
"""
|
return code.format(self.name)
|
|
def Epilogue(self):
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register ge_bits = temp_registers.Acquire();
|
__ Mrs(ge_bits, APSR);
|
// Only record the GE bits.
|
__ And(ge_bits, ge_bits, GEFlags);
|
__ Str(ge_bits, MemOperand(result_ptr, offsetof(Inputs, {})));
|
}}
|
"""
|
return code.format(self.name)
|
|
|
class FPSCR(U32):
|
def Prologue(self):
|
# We need a scratch register to load the `FPCSR` flags.
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register fpsr_bits = temp_registers.Acquire();
|
__ Ldr(fpsr_bits, MemOperand(input_ptr, offsetof(Inputs, {})));
|
__ Vmsr(FPSCR, fpsr_bits);
|
}}
|
"""
|
return code.format(self.name)
|
|
def Epilogue(self):
|
code = """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register fpsr_bits = temp_registers.Acquire();
|
__ Vmrs(RegisterOrAPSR_nzcv(fpsr_bits.GetCode()), FPSCR);
|
__ Str(fpsr_bits, MemOperand(result_ptr, offsetof(Inputs, {})));
|
}}
|
"""
|
return code.format(self.name)
|
|
|
class MemOperand(Input):
|
"""
|
Description of a memory location input, used to test a `MemOperand` operand.
|
|
A memory location input is a compound type and is represented as an
|
array of two `uint32_t` values, an "offset" and a "data":
|
|
~~~
|
{
|
0x0, // Offset used to record the base register in case it was
|
// updated.
|
0xabababab // 32 bit value in memory the instruction should work with.
|
}
|
~~~
|
"""
|
|
def Declare(self):
|
"""
|
Declare the input as a two element array.
|
"""
|
return "uint32_t {name}[2];".format(name=self.name)
|
|
def PrintInput(self, suffix = ""):
|
code = '''printf("{{0x%08" PRIx32 ", 0x%08" PRIx32 "}}",
|
{name}[0], {name}[1]);'''
|
return code.format(name=self.name + suffix)
|
|
def PrintOutput(self):
|
code = '''printf("{{0x%08" PRIx32 ", 0x%08" PRIx32 "}}",
|
results[i]->outputs[j].{name}[0],
|
results[i]->outputs[j].{name}[1]);'''
|
return code.format(name=self.name)
|
|
def InstantiateResult(self, suffix = ""):
|
code = """uint32_t {name}{suffix}[2] = {{
|
results[i]->outputs[j].{name}[0],
|
results[i]->outputs[j].{name}[1]
|
}};
|
"""
|
return code.format(name=self.name, suffix=suffix)
|
|
def InstantiateInput(self, suffix = ""):
|
code = """uint32_t {name}{suffix}[2] = {{
|
kTests[i].inputs[j].{name}[0],
|
kTests[i].inputs[j].{name}[1]
|
}};
|
"""
|
return code.format(name=self.name, suffix=suffix)
|
|
def InstantiateReference(self, suffix = ""):
|
code = """uint32_t {name}{suffix}[2] = {{
|
results[i]->outputs[j].{name}[0],
|
results[i]->outputs[j].{name}[1]
|
}};
|
"""
|
return code.format(name=self.name, suffix=suffix)
|
|
def Compare(self, left_suffix, operand, right_suffix):
|
return """
|
(({name}{left_suffix}[0] {operand} {name}{right_suffix}[0]) &&
|
({name}{left_suffix}[1] {operand} {name}{right_suffix}[1]))
|
""".format(name=self.name, left_suffix=left_suffix, operand=operand,
|
right_suffix=right_suffix)
|
|
def Prologue(self):
|
return """
|
// Allocate 4 bytes for the instruction to work with.
|
scratch_memory_buffers[i] = new byte[4];
|
{{
|
UseScratchRegisterScope temp_registers(&masm);
|
|
Register {name}_tmp = temp_registers.Acquire();
|
Register base_register = {name}.GetBaseRegister();
|
|
// Write the expected data into the scratch buffer.
|
__ Mov(base_register, Operand::From(scratch_memory_buffers[i]));
|
__ Ldr({name}_tmp, MemOperand(input_ptr, offsetof(Inputs, {name}) + 4));
|
__ Str({name}_tmp, MemOperand(base_register));
|
|
// Compute the address to put into the base register so that the
|
// `MemOperand` points to the right location.
|
// TODO: Support more kinds of `MemOperand`.
|
if (!{name}.IsPostIndex()) {{
|
if ({name}.IsImmediate()) {{
|
if ({name}.GetSign().IsPlus()) {{
|
__ Mov({name}_tmp, {name}.GetOffsetImmediate());
|
__ Sub(base_register, base_register, {name}_tmp);
|
}} else {{
|
__ Mov({name}_tmp, -{name}.GetOffsetImmediate());
|
__ Add(base_register, base_register, {name}_tmp);
|
}}
|
}} else if ({name}.IsShiftedRegister()) {{
|
__ Mov({name}_tmp, Operand({name}.GetOffsetRegister(),
|
{name}.GetShift(),
|
{name}.GetShiftAmount()));
|
if ({name}.GetSign().IsPlus()) {{
|
__ Sub(base_register, base_register, {name}_tmp);
|
}} else {{
|
__ Add(base_register, base_register, {name}_tmp);
|
}}
|
}}
|
}}
|
}}
|
""".format(name=self.name)
|
|
def Epilogue(self):
|
# TODO: This generated code does not support recording the state for
|
# instructions where the base register is the same as another register used
|
# in the instruction. It is possible to do so but requires more temporary
|
# registers which is not trivial to implement without
|
# `UseScratchRegisterScope`. We will be able to lift this restriction when
|
# it is implemented.
|
return """{{
|
UseScratchRegisterScope temp_registers(&masm);
|
Register {name}_tmp = temp_registers.Acquire();
|
Register base_register = {name}.GetBaseRegister();
|
|
// Compute the address of the scratch buffer by from the base register. If
|
// the instruction has updated the base register, we will be able to
|
// record it.
|
if (!{name}.IsPostIndex()) {{
|
if ({name}.IsImmediate()) {{
|
if ({name}.GetSign().IsPlus()) {{
|
__ Mov({name}_tmp, {name}.GetOffsetImmediate());
|
__ Add(base_register, base_register, {name}_tmp);
|
}} else {{
|
__ Mov({name}_tmp, -{name}.GetOffsetImmediate());
|
__ Sub(base_register, base_register, {name}_tmp);
|
}}
|
}} else if ({name}.IsShiftedRegister()) {{
|
__ Mov({name}_tmp, Operand({name}.GetOffsetRegister(),
|
{name}.GetShift(),
|
{name}.GetShiftAmount()));
|
if ({name}.GetSign().IsPlus()) {{
|
__ Add(base_register, base_register, {name}_tmp);
|
}} else {{
|
__ Sub(base_register, base_register, {name}_tmp);
|
}}
|
}}
|
}}
|
|
// Record the value of the base register, as an offset from the scratch
|
// buffer's address.
|
__ Mov({name}_tmp, Operand::From(scratch_memory_buffers[i]));
|
__ Sub(base_register, base_register, {name}_tmp);
|
__ Str(base_register, MemOperand(result_ptr, offsetof(Inputs, {name})));
|
|
// Record the 32 bit word from memory.
|
__ Ldr({name}_tmp, MemOperand({name}_tmp));
|
__ Str({name}_tmp, MemOperand(result_ptr, offsetof(Inputs, {name}) + 4));
|
}}
|
""".format(name=self.name)
|