vec_reg.hh revision 13610:5d5404ac6288
1/*
2 * Copyright (c) 2015-2016, 2018 ARM Limited
3 * All rights reserved
4 *
5 * The license below extends only to copyright in the software and shall
6 * not be construed as granting a license to any other intellectual
7 * property including but not limited to intellectual property relating
8 * to a hardware implementation of the functionality of the software
9 * licensed hereunder.  You may use the software subject to the license
10 * terms below provided that you ensure that this notice is replicated
11 * unmodified and in its entirety in all distributions of the software,
12 * modified or unmodified, in source code or in binary form.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are
16 * met: redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer;
18 * redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution;
21 * neither the name of the copyright holders nor the names of its
22 * contributors may be used to endorse or promote products derived from
23 * this software without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
28 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
29 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
30 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
31 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
32 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
33 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
35 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
36 *
37 * Authors: Giacomo Gabrielli
38 *          Nathanael Premillieu
39 *          Rekai Gonzalez
40 */
41
42/** \file arch/generic/vec_reg.hh
43 * Vector Registers layout specification.
44 *
45 * This register type is to be used to model the SIMD registers.
46 * It takes into account the possibility that different architectural names
47 * may overlap (like for ARMv8 AArch32 for example).
48 *
49 * The design is having a basic vector register container that holds the
50 * bytes, unaware of anything else. This is implemented by VecRegContainer.
51 * As the (maximum) length of the physical vector register is a compile-time
52 * constant, it is defined as a template parameter.
53 *
54 * This file also describes two views of the container that have semantic
55 * information about the bytes. The first of this views is VecRegT.
56 *    A VecRegT is a view of a VecRegContainer (by reference). The VecRegT has
57 *    a type (VecElem) to which bytes are casted, and the amount of such
58 *    elements that the vector contains (NumElems). The size of a view,
59 *    calculated as sizeof(VecElem) * NumElems must match the size of the
60 *    underlying container. As VecRegT has some degree of type information it
61 *    has vector semantics, and defines the index operator ([]) to get
62 *    references to particular bytes understood as a VecElem.
63 * The second view of a container implemented in this file is VecLaneT, which
64 * is a view of a subset of the container.
65 *    A VecLaneT is a view of a lane of a vector register, where a lane is
66 *    identified by a type (VecElem) and an index (although the view is
67 *    unaware of its index). Operations on the lane are directly applied to
68 *    the corresponding bytes of the underlying VecRegContainer through a
69 *    reference.
70 *
71 * The intended usage is requesting views to the VecRegContainer via the
72 * member 'as' for VecRegT and the member 'laneView' for VecLaneT. Kindly
73 * find an example of usage in the following.
74 *
75 *
76 * // We declare 512 bits vectors
77 * using Vec512 = VecRegContainer<64>;
78 * ...
79 * // We implement the physical vector register file
80 * Vec512 physicalVecRegFile[NUM_VREGS];
81 * ...
82 * // Usage example, for a macro op:
83 * VecFloat8Add(ExecContext* xd) {
84 *    // Request source vector register to the execution context (const as it
85 *    // is read only).
86 *    const Vec512& vsrc1raw = xc->readVecRegOperand(this, 0);
87 *    // View it as a vector of floats (we could just specify the first
88 *    // template parametre, the second has a default value that works, and the
89 *    // last one is derived by the constness of vsrc1raw).
90 *    VecRegT<float, 8, true>& vsrc1 = vsrc1raw->as<float, 8>();
91 *
92 *    // Second source and view
93 *    const Vec512& vsrc2raw = xc->readVecRegOperand(this, 1);
94 *    VecRegT<float, 8, true>& vsrc2 = vsrc2raw->as<float, 8>();
95 *
96 *    // Destination and view
97 *    Vec512 vdstraw;
98 *    VecRegT<float, 8, false>& vdst = vdstraw->as<float, 8>();
99 *
100 *    for (auto i = 0; i < 8; i++) {
101 *        // This asignment sets the bits in the underlying Vec512: vdstraw
102 *        vdst[i] = vsrc1[i] + vsrc2[i];
103 *    }
104 *    xc->setWriteRegOperand(this, 0, vdstraw);
105 * }
106 *
107 * // Usage example, for a micro op that operates over lane number _lidx:
108 * VecFloatLaneAdd(ExecContext* xd) {
109 *    // Request source vector register to the execution context (const as it
110 *    // is read only).
111 *    const Vec512& vsrc1raw = xc->readVecRegOperand(this, 0);
112 *    // View it as a lane of a vector of floats (we could just specify the
113 *    // first template parametre, the second is derived by the constness of
114 *    // vsrc1raw).
115 *    VecLaneT<float, true>& src1 = vsrc1raw->laneView<float>(this->_lidx);
116 *
117 *    // Second source and view
118 *    const Vec512& vsrc2raw = xc->readVecRegOperand(this, 1);
119 *    VecLaneT<float, true>& src2 = vsrc2raw->laneView<float>(this->_lidx);
120 *
121 *    // (Writable) destination and view
122 *    // As this is a partial write, we need the exec context to support that
123 *    // through, e.g., 'readVecRegOperandToWrite' returning a writable
124 *    // reference to the register
125 *    Vec512 vdstraw = xc->readVecRegOperandToWrite(this, 3);
126 *    VecLaneT<float, false>& dst = vdstraw->laneView<float>(this->_lidx);
127 *
128 *    dst = src1 + src2;
129 *    // There is no need to copy the value back into the exec context, as
130 *    // the assignment to dst modifies the appropriate bytes in vdstraw which
131 *    // is in turn, a reference to the register in the cpu model.
132 *    // For operations that do conditional writeback, we can decouple the
133 *    // write by doing:
134 *    //   auto tmp = src1 + src2;
135 *    //   if (test) {
136 *    //       dst = tmp; // do writeback
137 *    //   } else {
138 *    //      // do not do writeback
139 *    //   }
140 * }
141 *
142 */
143
144#ifndef __ARCH_GENERIC_VEC_REG_HH__
145#define __ARCH_GENERIC_VEC_REG_HH__
146
147#include <array>
148#include <cassert>
149#include <iostream>
150#include <string>
151#include <type_traits>
152#include <vector>
153
154#include "base/cprintf.hh"
155#include "base/logging.hh"
156
157template <size_t Sz>
158class VecRegContainer;
159
160/** Vector Register Abstraction
161 * This generic class is a view in a particularization of MVC, to vector
162 * registers. There is a VecRegContainer that implements the model, and
163 * contains the data. To that model we can interpose different instantiations
164 * of VecRegT to view the container as a vector of NumElems elems of type
165 * VecElem.
166 * @tparam VecElem Type of each element of the vector.
167 * @tparam NumElems Amount of components of the vector.
168 * @tparam Const Indicate if the underlying container can be modified through
169 * the view.
170 */
171template <typename VecElem, size_t NumElems, bool Const>
172class VecRegT
173{
174    /** Size of the register in bytes. */
175    static constexpr size_t SIZE = sizeof(VecElem) * NumElems;
176  public:
177    /** Container type alias. */
178    using Container = typename std::conditional<Const,
179                                              const VecRegContainer<SIZE>,
180                                              VecRegContainer<SIZE>>::type;
181  private:
182    /** My type alias. */
183    using MyClass = VecRegT<VecElem, NumElems, Const>;
184    /** Reference to container. */
185    Container& container;
186
187  public:
188    /** Constructor. */
189    VecRegT(Container& cnt) : container(cnt) {};
190
191    /** Zero the container. */
192    template<bool Condition = !Const>
193    typename std::enable_if<Condition, void>::type
194    zero() { container.zero(); }
195
196    template<bool Condition = !Const>
197    typename std::enable_if<Condition, MyClass&>::type
198    operator=(const MyClass& that)
199    {
200        container = that.container;
201        return *this;
202    }
203
204    /** Index operator. */
205    const VecElem& operator[](size_t idx) const
206    {
207        return container.template raw_ptr<VecElem>()[idx];
208    }
209
210    /** Index operator. */
211    template<bool Condition = !Const>
212    typename std::enable_if<Condition, VecElem&>::type
213    operator[](size_t idx)
214    {
215        return container.template raw_ptr<VecElem>()[idx];
216    }
217
218    /** Equality operator.
219     * Required to compare thread contexts.
220     */
221    template<typename VE2, size_t NE2, bool C2>
222    bool
223    operator==(const VecRegT<VE2, NE2, C2>& that) const
224    {
225        return container == that.container;
226    }
227    /** Inequality operator.
228     * Required to compare thread contexts.
229     */
230    template<typename VE2, size_t NE2, bool C2>
231    bool
232    operator!=(const VecRegT<VE2, NE2, C2>& that) const
233    {
234        return !operator==(that);
235    }
236
237    /** Output stream operator. */
238    friend std::ostream&
239    operator<<(std::ostream& os, const MyClass& vr)
240    {
241        /* 0-sized is not allowed */
242        os << "[" << std::hex << (uint32_t)vr[0];
243        for (uint32_t e = 1; e < vr.SIZE; e++)
244            os << " " << std::hex << (uint32_t)vr[e];
245        os << ']';
246        return os;
247    }
248
249    const std::string print() const { return csprintf("%s", *this); }
250    /**
251     * Cast to VecRegContainer&
252     * It is useful to get the reference to the container for ISA tricks,
253     * because casting to reference prevents unnecessary copies.
254     */
255    operator Container&() { return container; }
256};
257
258/* Forward declaration. */
259template <typename VecElem, bool Const>
260class VecLaneT;
261
262/**
263 * Vector Register Abstraction
264 * This generic class is the model in a particularization of MVC, to vector
265 * registers. The model has functionality to create views of itself, or a
266 * portion through the method 'as
267 * @tparam Sz Size of the container in bytes.
268 */
269template <size_t Sz>
270class VecRegContainer
271{
272  static_assert(Sz > 0,
273          "Cannot create Vector Register Container of zero size");
274  public:
275    static constexpr size_t SIZE = Sz;
276    using Container = std::array<uint8_t,Sz>;
277  private:
278    Container container;
279    using MyClass = VecRegContainer<SIZE>;
280
281  public:
282    VecRegContainer() {}
283    /* This is required for de-serialisation. */
284    VecRegContainer(const std::vector<uint8_t>& that)
285    {
286        assert(that.size() >= SIZE);
287        std::memcpy(container.data(), &that[0], SIZE);
288    }
289
290    /** Zero the container. */
291    void zero() { memset(container.data(), 0, SIZE); }
292
293    /** Assignment operators. */
294    /** @{ */
295    /** From VecRegContainer */
296    MyClass& operator=(const MyClass& that)
297    {
298        if (&that == this)
299            return *this;
300        memcpy(container.data(), that.container.data(), SIZE);
301        return *this;
302    }
303
304    /** From appropriately sized uint8_t[]. */
305    MyClass& operator=(const Container& that)
306    {
307        std::memcpy(container.data(), that.data(), SIZE);
308        return *this;
309    }
310
311    /** From vector<uint8_t>.
312     * This is required for de-serialisation.
313     * */
314    MyClass& operator=(const std::vector<uint8_t>& that)
315    {
316        assert(that.size() >= SIZE);
317        std::memcpy(container.data(), that.data(), SIZE);
318        return *this;
319    }
320    /** @} */
321
322    /** Copy the contents into the input buffer. */
323    /** @{ */
324    /** To appropriately sized uint8_t[] */
325    void copyTo(Container& dst) const
326    {
327        std::memcpy(dst.data(), container.data(), SIZE);
328    }
329
330    /** To vector<uint8_t>
331     * This is required for serialisation.
332     * */
333    void copyTo(std::vector<uint8_t>& dst) const
334    {
335        dst.resize(SIZE);
336        std::memcpy(dst.data(), container.data(), SIZE);
337    }
338    /** @} */
339
340    /** Equality operator.
341     * Required to compare thread contexts.
342     */
343    template<size_t S2>
344    inline bool
345    operator==(const VecRegContainer<S2>& that) const
346    {
347        return SIZE == S2 &&
348               !memcmp(container.data(), that.container.data(), SIZE);
349    }
350    /** Inequality operator.
351     * Required to compare thread contexts.
352     */
353    template<size_t S2>
354    bool
355    operator!=(const VecRegContainer<S2>& that) const
356    {
357        return !operator==(that);
358    }
359
360    const std::string print() const { return csprintf("%s", *this); }
361    /** Get pointer to bytes. */
362    template <typename Ret>
363    const Ret* raw_ptr() const { return (const Ret*)container.data(); }
364
365    template <typename Ret>
366    Ret* raw_ptr() { return (Ret*)container.data(); }
367
368    /**
369     * View interposers.
370     * Create a view of this container as a vector of VecElems with an
371     * optional amount of elements. If the amount of elements is provided,
372     * the size of the container is checked, to test bounds. If it is not
373     * provided, the length is inferred from the container size and the
374     * element size.
375     * @tparam VecElem Type of each element of the vector for the view.
376     * @tparam NumElem Amount of elements in the view.
377     */
378    /** @{ */
379    template <typename VecElem, size_t NumElems = SIZE/sizeof(VecElem)>
380    VecRegT<VecElem, NumElems, true> as() const
381    {
382        static_assert(SIZE % sizeof(VecElem) == 0,
383                "VecElem does not evenly divide the register size");
384        static_assert(sizeof(VecElem) * NumElems <= SIZE,
385                "Viewing VecReg as something bigger than it is");
386        return VecRegT<VecElem, NumElems, true>(*this);
387    }
388
389    template <typename VecElem, size_t NumElems = SIZE/sizeof(VecElem)>
390    VecRegT<VecElem, NumElems, false> as()
391    {
392        static_assert(SIZE % sizeof(VecElem) == 0,
393                "VecElem does not evenly divide the register size");
394        static_assert(sizeof(VecElem) * NumElems <= SIZE,
395                "Viewing VecReg as something bigger than it is");
396        return VecRegT<VecElem, NumElems, false>(*this);
397    }
398
399    template <typename VecElem, int LaneIdx>
400    VecLaneT<VecElem, false> laneView();
401    template <typename VecElem, int LaneIdx>
402    VecLaneT<VecElem, true> laneView() const;
403    template <typename VecElem>
404    VecLaneT<VecElem, false> laneView(int laneIdx);
405    template <typename VecElem>
406    VecLaneT<VecElem, true> laneView(int laneIdx) const;
407    /** @} */
408    /**
409     * Output operator.
410     * Used for serialization.
411     */
412    friend std::ostream& operator<<(std::ostream& os, const MyClass& v)
413    {
414        for (auto& b: v.container) {
415            os << csprintf("%02x", b);
416        }
417        return os;
418    }
419};
420
421/** We define an auxiliary abstraction for LaneData. The ISA should care
422 * about the semantics of a, e.g., 32bit element, treating it as a signed or
423 * unsigned int, or a float depending on the semantics of a particular
424 * instruction. On the other hand, the cpu model should only care about it
425 * being a 32-bit value. */
426enum class LaneSize
427{
428    Empty = 0,
429    Byte,
430    TwoByte,
431    FourByte,
432    EightByte,
433};
434
435/** LaneSize is an abstraction of a LS byte value for the execution and thread
436 * contexts to handle values just depending on its width. That way, the ISA
437 * can request, for example, the second 4 byte lane of register 5 to the model.
438 * The model serves that value, agnostic of the semantics of those bits. Then,
439 * it is up to the ISA to interpret those bits as a float, or as an uint.
440 * To maximize the utility, this class implements the assignment operator and
441 * the casting to equal-size types.
442 * As opposed to a RegLaneT, LaneData is not 'backed' by a VecRegContainer.
443 * The idea is:
444 *  When data is passed and is susceptible to being copied, use LaneData, as
445 *     copying the primitive type is build on is cheap.
446 *  When data is passed as references (const or not), use RegLaneT, as all
447 *     operations happen 'in place', avoiding any copies (no copies is always
448 *     cheaper than cheap copies), especially when things are inlined, and
449 *     references are not explicitly passed.
450 */
451template <LaneSize LS>
452class LaneData
453{
454  public:
455    /** Alias to the native type of the appropriate size. */
456    using UnderlyingType =
457        typename std::conditional<LS == LaneSize::EightByte, uint64_t,
458            typename std::conditional<LS == LaneSize::FourByte, uint32_t,
459                typename std::conditional<LS == LaneSize::TwoByte, uint16_t,
460                    typename std::conditional<LS == LaneSize::Byte, uint8_t,
461                    void>::type
462                >::type
463            >::type
464        >::type;
465  private:
466    static constexpr auto ByteSz = sizeof(UnderlyingType);
467    UnderlyingType _val;
468    using MyClass = LaneData<LS>;
469
470  public:
471    template <typename T> explicit
472    LaneData(typename std::enable_if<sizeof(T) == ByteSz, const T&>::type t)
473                : _val(t) {}
474
475    template <typename T>
476    typename std::enable_if<sizeof(T) == ByteSz, MyClass&>::type
477    operator=(const T& that)
478    {
479        _val = that;
480        return *this;
481    }
482    template<typename T,
483             typename std::enable_if<sizeof(T) == ByteSz, int>::type I = 0>
484    operator T() const {
485        return *static_cast<const T*>(&_val);
486    }
487};
488
489/** Output operator overload for LaneData<Size>. */
490template <LaneSize LS>
491inline std::ostream&
492operator<<(std::ostream& os, const LaneData<LS>& d)
493{
494    return os << static_cast<typename LaneData<LS>::UnderlyingType>(d);
495}
496
497/** Vector Lane abstraction
498 * Another view of a container. This time only a partial part of it is exposed.
499 * @tparam VecElem Type of each element of the vector.
500 * @tparam Const Indicate if the underlying container can be modified through
501 * the view.
502 */
503/** @{ */
504/* General */
505template <typename VecElem, bool Const>
506class VecLaneT
507{
508  public:
509    /** VecRegContainer friendship to access private VecLaneT constructors.
510     * Only VecRegContainers can build VecLanes.
511     */
512    /** @{ */
513    friend VecLaneT<VecElem, !Const>;
514
515    /*template <size_t Sz>
516    friend class VecRegContainer;*/
517    friend class VecRegContainer<8>;
518    friend class VecRegContainer<16>;
519    friend class VecRegContainer<32>;
520    friend class VecRegContainer<64>;
521    friend class VecRegContainer<128>;
522
523    /** My type alias. */
524    using MyClass = VecLaneT<VecElem, Const>;
525
526  private:
527    using Cont = typename std::conditional<Const,
528                                              const VecElem,
529                                              VecElem>::type;
530    static_assert(!std::is_const<VecElem>::value || Const,
531            "Asked for non-const lane of const type!");
532    static_assert(std::is_integral<VecElem>::value,
533            "VecElem type is not integral!");
534    /** Reference to data. */
535    Cont& container;
536
537    /** Constructor */
538    VecLaneT(Cont& cont) : container(cont) { }
539
540  public:
541    /** Assignment operators.
542     * Assignment operators are only enabled if the underlying container is
543     * non-constant.
544     */
545    /** @{ */
546    template <bool Assignable = !Const>
547    typename std::enable_if<Assignable, MyClass&>::type
548    operator=(const VecElem& that) {
549        container = that;
550        return *this;
551    }
552    /**
553     * Generic.
554     * Generic bitwise assignment. Narrowing and widening assignemnts are
555     * not allowed, pre-treatment of the rhs is required to conform.
556     */
557    template <bool Assignable = !Const, typename T>
558    typename std::enable_if<Assignable, MyClass&>::type
559    operator=(const T& that) {
560        static_assert(sizeof(T) >= sizeof(VecElem),
561                "Attempt to perform widening bitwise copy.");
562        static_assert(sizeof(T) <= sizeof(VecElem),
563                "Attempt to perform narrowing bitwise copy.");
564        container = static_cast<VecElem>(that);
565        return *this;
566    }
567    /** @} */
568    /** Cast to vecElem. */
569    operator VecElem() const { return container; }
570
571    /** Constification. */
572    template <bool Cond = !Const, typename std::enable_if<Cond, int>::type = 0>
573    operator VecLaneT<typename std::enable_if<Cond, VecElem>::type, true>()
574    {
575        return VecLaneT<VecElem, true>(container);
576    }
577};
578
579namespace std {
580    template<typename T, bool Const>
581    struct add_const<VecLaneT<T, Const>> { typedef VecLaneT<T, true> type; };
582}
583
584/** View as the Nth lane of type VecElem. */
585template <size_t Sz>
586template <typename VecElem, int LaneIdx>
587VecLaneT<VecElem, false>
588VecRegContainer<Sz>::laneView()
589{
590    return VecLaneT<VecElem, false>(as<VecElem>()[LaneIdx]);
591}
592
593/** View as the const Nth lane of type VecElem. */
594template <size_t Sz>
595template <typename VecElem, int LaneIdx>
596VecLaneT<VecElem, true>
597VecRegContainer<Sz>::laneView() const
598{
599    return VecLaneT<VecElem, true>(as<VecElem>()[LaneIdx]);
600}
601
602/** View as the Nth lane of type VecElem. */
603template <size_t Sz>
604template <typename VecElem>
605VecLaneT<VecElem, false>
606VecRegContainer<Sz>::laneView(int laneIdx)
607{
608    return VecLaneT<VecElem, false>(as<VecElem>()[laneIdx]);
609}
610
611/** View as the const Nth lane of type VecElem. */
612template <size_t Sz>
613template <typename VecElem>
614VecLaneT<VecElem, true>
615VecRegContainer<Sz>::laneView(int laneIdx) const
616{
617    return VecLaneT<VecElem, true>(as<VecElem>()[laneIdx]);
618}
619
620using VecLane8 = VecLaneT<uint8_t, false>;
621using VecLane16 = VecLaneT<uint16_t, false>;
622using VecLane32 = VecLaneT<uint32_t, false>;
623using VecLane64 = VecLaneT<uint64_t, false>;
624
625using ConstVecLane8 = VecLaneT<uint8_t, true>;
626using ConstVecLane16 = VecLaneT<uint16_t, true>;
627using ConstVecLane32 = VecLaneT<uint32_t, true>;
628using ConstVecLane64 = VecLaneT<uint64_t, true>;
629
630/**
631 * Calls required for serialization/deserialization
632 */
633/** @{ */
634template <size_t Sz>
635inline bool
636to_number(const std::string& value, VecRegContainer<Sz>& v)
637{
638    fatal_if(value.size() > 2 * VecRegContainer<Sz>::SIZE,
639             "Vector register value overflow at unserialize");
640
641    for (int i = 0; i < VecRegContainer<Sz>::SIZE; i++) {
642        uint8_t b = 0;
643        if (2 * i < value.size())
644            b = stoul(value.substr(i * 2, 2), nullptr, 16);
645        v.template raw_ptr<uint8_t>()[i] = b;
646    }
647    return true;
648}
649/** @} */
650
651/**
652 * Dummy type aliases and constants for architectures that do not implement
653 * vector registers.
654 */
655/** @{ */
656using DummyVecElem = uint32_t;
657constexpr unsigned DummyNumVecElemPerVecReg = 2;
658using DummyVecReg = VecRegT<DummyVecElem, DummyNumVecElemPerVecReg, false>;
659using DummyConstVecReg = VecRegT<DummyVecElem, DummyNumVecElemPerVecReg, true>;
660using DummyVecRegContainer = DummyVecReg::Container;
661constexpr size_t DummyVecRegSizeBytes = DummyNumVecElemPerVecReg *
662    sizeof(DummyVecElem);
663/** @} */
664
665#endif /* __ARCH_GENERIC_VEC_REG_HH__ */
666