7

I recently discovered about the vreinterpret{q}_dsttype_srctype casting operator. However this doesn't seem to support conversion in the data type described at this link (bottom of the page):

Some intrinsics use an array of vector types of the form:

<type><size>x<number of lanes>x<length of array>_t

These types are treated as ordinary C structures containing a single element named val.

An example structure definition is:

struct int16x4x2_t    
{
    int16x4_t val[2];     
};

Do you know how to convert from uint8x16_t to uint8x8x2_t?

Note that that the problem cannot be reliably addressed using union (reading from inactive members leads to undefined behaviour Edit: That's only the case for C++, while it turns out that C allows type punning), nor by using pointers to cast (breaks the strict aliasing rule).

Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197
  • 1
    Using standard C++ `std::memcpy`. Any other (C++) way requires a compiler extension which you would have to research. – Richard Critten Apr 20 '17 at 13:40
  • @RichardCritten I had that in mind, but I didn't want to bias the answers, thank you for confirming that, although I still hope another solution exists... – Antonio Apr 20 '17 at 13:42
  • Possible duplicate of [ARM Neon in C: How to combine different 128bit data types while using intrinsics?](http://stackoverflow.com/questions/43519189/arm-neon-in-c-how-to-combine-different-128bit-data-types-while-using-intrinsics) – too honest for this site Apr 20 '17 at 13:46
  • @RichardCritten: `memcpy`ing from/to different datatypes is not different than an assignment with a cast: it invokes UB (there are few exceptions for `char` types. – too honest for this site Apr 20 '17 at 13:49
  • @Olaf This is more specific, and the answer provided there doesn't answer this question – Antonio Apr 20 '17 at 13:49
  • 3
    @Olaf `memcpying from/to different datatypes is not different than an assignment with a cast` That's not true. `memcpy` has defined behaviour, see discussion [here](http://stackoverflow.com/questions/29253100/using-pointer-conversions-to-store-cast-values-am-i-breaking-the-strict-aliasin). – Antonio Apr 20 '17 at 13:51
  • 4
    @Olaf it is defined behaviour to to cast both source and destination to `char *` and then `memcpy`. – Richard Critten Apr 20 '17 at 13:52
  • @RichardCritten If you want to submit an answer in this respect, I will accept it (unless anything better comes out :) ) – Antonio Apr 20 '17 at 14:01
  • This wouldn't be a straight reinterpretation, the `x2` forms are split over two registers. So it makes sense that you can't just reinterpret it. I'm not sure how it's actually supposed to be done other than a load/store which seems silly.. – harold Apr 20 '17 at 14:03
  • @Antonio that's quite the treasure hunt, what's the actual result for this case? I tried doing in gcc.godbolt but it doesn't have arm_neon.h so.. Of course in general it seems that compilers handle memcpy well but I'm not at all comfortable just assuming the best case under special circumstances like these. – harold Apr 20 '17 at 14:16
  • @Antonio can you actually show that though? I mean you linked somewhere, which linked somewhere, which linked somewhere else, and in the end I didn't find the actual thing. – harold Apr 20 '17 at 14:25
  • @harold https://meta.stackexchange.com/questions/225370/your-answer-is-in-another-castle-when-is-an-answer-not-an-answer :) Take the 2 links mentioned in [this comment](http://stackoverflow.com/questions/17789928/whats-a-proper-way-of-type-punning-a-float-to-an-int-and-vice-versa/17790026#comment25951609_17790026) – Antonio Apr 20 '17 at 15:03
  • I don't feel I have answer for `C` (tagged) but I think in `C` both `union` and cast are defined behaviour - someone else need to answer this part. – Richard Critten Apr 20 '17 at 15:15
  • @Antonio for `C` _"If the member used to access the contents of a union is not the same as the member last used to store a value, the object representation of the value that was stored is reinterpreted as an object representation of the new type (this is known as type punning)."_ source: http://en.cppreference.com/w/c/language/union - I don't have the standard to hand. – Richard Critten Apr 20 '17 at 15:21
  • @RichardCritten It seems [you are right about union](http://stackoverflow.com/a/25672839/2436175)! (But that link confirms that casting through pointers breaks the aliasing rule) Again, if you want to post an answer (C++ requires memcpy, while C can leverage from type punning) – Antonio Apr 20 '17 at 15:31
  • It's far more clear [here](http://stackoverflow.com/a/11640603/2436175). – Antonio Apr 20 '17 at 15:46
  • 1
    @RichardCritten: It is not. It violates the effective type (aka strict aliasing) rule. The intermediate cast does not change that (who should it?). See 6.5p6 for details. – too honest for this site Apr 20 '17 at 17:18
  • @Antonio: The link is about C++, which is a different language than C. I should have been clear I meant C. Though I'm confident it is also UB inm C++, I will not discuss this language as I'm not familiar with its standard. – too honest for this site Apr 20 '17 at 17:20
  • @RichardCritten: In C the cast definitively invokes UB; `memcpy` does not chage that (see my comment ^). I'd actually be surprise it is different in C++, but afaik this language does not allow aliasing via `union`, so maybe it does allow it. However, in C aliasing is **only** allowed via `union`. This is guaranteed by the standard. Not via pointer. And modern compilers can expliot this. gcc e.g. is well known to strictly follow the standard without additional guarantees. That's why "old-style" embedded programmers not used to highly optimizing compilers tend to run into problems with gcc. – too honest for this site Apr 20 '17 at 17:25
  • @Antonio: Your question is abotu C; please stop adding tags for unrelated, **different** languages. – too honest for this site Apr 20 '17 at 17:45
  • 1
    @Olaf The question concerns both C and C++, which, while being different languages, share a lot of concepts, and a lot of programmers by the way. Apparently, and very interestingly, on this simple topic the 2 languages offer different solutions, it's worth to show clearly these differences to avoid they are mixed up. – Antonio Apr 20 '17 at 19:08
  • @Olaf 1) About memcpy you are plain wrong. 2) Type punning (through unions) is allowed only in C, precisely starting from C99 "amended" 3) Casting passing through pointers breaks aliasing rule both in C and C++ – Antonio Apr 20 '17 at 19:13
  • @Antonio: 1) Please provide a reference to the standard. 2) Please provide a citation I said different. 3) Where did I say different? – too honest for this site Apr 20 '17 at 19:17
  • @Olaf Regarding 2 and 3, I just wanted to sum up what said so far (I am glad we agree). Regarding the use of `memcpy`, casting any pointer to `(void*)` is always valid, I do not understand at which step you see strict aliasing getting broken. – Antonio Apr 20 '17 at 20:01
  • @Antonio: How about reading the standartd? I gave the paragraph in a comment above. – too honest for this site Apr 20 '17 at 20:03
  • @Antonio: Just to be clear: the fact the compiler does not complain or it works on some architectures does no way imply it is correct or will under even minimal different conditions. In C, casts and assignments to `void *` are problematic, as the tell teh compiler "shut up, I know what I'm doing". So you better really know, otherwise you invoke UB. – too honest for this site Apr 20 '17 at 20:11
  • 3
    @Olaf C99 standard, 6.3.2.3 "***A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void*** *and back again; the result shall compare equal to the original pointer.*" What is "problematic" about this? Note that this is the only requirement `memcpy` implies. – Antonio Apr 20 '17 at 20:32
  • @Antonio: Where does it say you may convert it to a **different** pointer type **and** dereference this? "A pointer to any incomplete or object type may be **converted to** a pointer to void **and back again**; the result shall compare equal to the original pointer.". Sorry, but I had this discussion often enough, including on SO. Please read about the _effective type rule_ in the standard and do some research what it implies **and what not**. – too honest for this site Apr 20 '17 at 20:46
  • @Olaf I am never converting it to a different pointer type. Sample program: `uint8x16_t a; uint8x8x2_t b; /*Placeholder, some code to assign something to a*/ memcpy(&b,&a,sizeof a);` The only conversion happening here are from `uint8x16_t*` to `void*` and from `uint8x8x2_t*` to `void*` – Antonio Apr 20 '17 at 21:05
  • Last comment, I'm tired of these discussions: This **does** invoke UB! – too honest for this site Apr 20 '17 at 21:13
  • 6
    You're wrong. That code is completely legal C++. – Puppy Apr 21 '17 at 18:13
  • Meta discussion related to this comment thread: https://meta.stackoverflow.com/questions/348197/what-should-one-do-when-in-a-programming-language-dispute-two-programmers-cannot (cc: @Olaf) – user000001 Apr 21 '17 at 18:19
  • 13
    Most of this discussion is *utterly* irrelevant. Most of it deals with what the standards require and guaranty about all possible compilers for their respective languages. The types you're dealing with, however, don't even *exist* in most compilers. Your starting point is code that has no hope of portability at all. Getting pedantic about how you do the conversion is not going to add any meaningful level of portability, so you might as well find what works with the compiler you're using, and live with the fact that it's not portable and never will be. – Jerry Coffin Apr 21 '17 at 18:28
  • 1
    @Olaf According to the standard... 1) `T*` can be cast to `char*` or `unsigned char*`, to allow examinations of an object's raw bytes. This is specified in [`[basic.lval/10.8]`](https://timsong-cpp.github.io/cppwp/n4140/basic.lval#10.8). `std::memcpy()` aliases both pointers as `unsigned char*`, which is a well-defined operation. 2) [Both types are `TriviallyCopyable`](https://godbolt.org/g/T7fwfi), preventing [other UB](http://stackoverflow.com/q/29777492/5386374). – Justin Time - Reinstate Monica Apr 22 '17 at 22:27
  • 3) [CPPReference explicitly notes that `memcpy()` can be used to convert a value to a different type without violating strict aliasing rules](http://en.cppreference.com/w/cpp/string/byte/memcpy). – Justin Time - Reinstate Monica Apr 22 '17 at 22:30
  • Overall, this is legal C++, unless you want to claim that the C++ Standard itself is incorrect. A `uint8x16_t*` is not cast to `uint8x8x2_t*`, nor is a `uint8x8x2_t*` cast to `uint8x16_t*`; rather, each is cast to `unsigned char*` and then back to its original type, which is entirely valid. If this was invalid, then _any_ operation that modifies an object's raw bytes through a `char*` or `unsigned char*` would be equally invalid, because modifying an object's raw bytes through an `unsigned char*` is the operation `memcpy()` performs. – Justin Time - Reinstate Monica Apr 22 '17 at 22:31
  • @JustinTime: The problerm is not the cast, nor the call to `memcpy`, but the destination type. I explicitly talk about C, not C++. To repeat the obvious: they are different languages and in C aliasing via `memcpy` is **not allowed**. However, as you both think you know better and prefer picking raisins from the standard instead of working through the whole chain of evidence, this discussion is pointless. Please don't ping me for this again. – too honest for this site Apr 22 '17 at 22:42
  • @JerryCoffin The trouble with such reasoning is that your code can *stop* working when a new version of the compiler comes along and decides to follow the standard in order to squeeze a bit more performance from the code using new optimizations based on the precise wording of the standard. – user4815162342 Apr 23 '17 at 22:12
  • ... This happened several times in the past, for example when GCC's optimizer started to assume that signed overflow won't happen (because it's UB), or when GCC started to follow strict aliasing rules and assume that pointers of different types can't point to the same data. Both optimizations broke idiomatic code that was incorrect according to the letter of the standard, but had been working reliably for literally decades. The signed overflow optimizations actually caused a number of security wholes in "incorrectly" written software. – user4815162342 Apr 23 '17 at 22:13
  • @user4815162342: In theory, you have a little bit of a point. In reality, my experience indicates that following the letter of the (current) standard can add a great deal of extra work, but *doesn't* really provide much extra protection in return for that work. – Jerry Coffin Apr 23 '17 at 22:40
  • @JerryCoffin The data types are indeed peculiar, however the discussion shouldn't be too different if the data types were `struct int32x4_t {int32_t val[4];};` and `struct int64x2_t {int64_t val[2];};`. – Antonio Apr 24 '17 at 10:09
  • @Antonio: Yes, they're probably somewhat similar. Nonetheless, your problem is equivalent to making supper. You don't need to solve world hunger. – Jerry Coffin Apr 24 '17 at 14:08
  • @Olaf Please take a look at [John Bollinger's answer](http://stackoverflow.com/a/43550283/2436175) – Antonio Apr 24 '17 at 14:35
  • @Olaf The thing is, though, I looked through the standard, and... 1) Nowhere does it say that converting a `T*` to `char*` and reading data from it is UB. 2) Nowhere does it say that converting a `T*` to `char*` and writing data to it is UB. 3) Nowhere does it say that writing data from one `char*` to another `char*` is UB. 4) The question isn't solely about C, but about both C and C++. – Justin Time - Reinstate Monica Apr 24 '17 at 18:53
  • And now that we mentioned C, [§ 6.5p7](http://port70.net/~nsz/c/c11/n1570.html#6.5p7) _also_ allows access through character types, just like C++. Therefore, #1, #2, and #3 above also apply to C. Also note that in [§ 6.5p6](http://port70.net/~nsz/c/c11/n1570.html#6.5p6), which you mentioned early, the sentence regarding `memcpy()` explicitly applies to "an object having no declared type", which is an allocated object; as both objects here have declared types, it does _not_ apply. – Justin Time - Reinstate Monica Apr 24 '17 at 18:56
  • I will reiterate: At no point is a `uint8x16_t*` being cast to `uint8x8x2_t*`, and at no point is a `uint8x8x2_t*` being cast to `uint8x16_t*`; this doesn't even happen inside `memcpy()`, since it takes both pointers as `void*`. According to both the C and C++ standards, converting `T*` to `char*` (albeit indirectly, in this case) is well-defined, writing data from one `char*` to another `char*` is well-defined, and _both_ languages permit the use of `memcpy()` to convert a value of type `U` into a value of type `V`. – Justin Time - Reinstate Monica Apr 24 '17 at 19:00
  • Again, note that CPPReference explicitly states that "Where strict aliasing prohibits examining the same memory as values of two different types, `std::memcpy` may be used to convert the values." on [the CPP page](http://en.cppreference.com/w/cpp/string/byte/memcpy), and "Where strict aliasing prohibits examining the same memory as values of two different types, `memcpy` may be used to convert the values." on [the C page](http://en.cppreference.com/w/c/string/byte/memcpy). The reason for this is that `memcpy()` examines both as `unsigned char*`, which is legal. – Justin Time - Reinstate Monica Apr 24 '17 at 19:06
  • @JustinTime: That's the problem. It is not mentioned for two objects with "declared type". But reading John Bollinger's answer semms to support your position (nothing personal, but there are few people here I can accept as reliable and John is one of them). Nevertheless, it is a bad idea and I see no advantage over using a `union`. The reasonm for using NEON is to speed up code and I would not rely on `memcpy` being optimised out. A `union` is more likely. It also would make the intention more clear. – too honest for this site Apr 24 '17 at 19:07
  • @JerryCoffin: Yes, sorry, darn autocompletion. I'll delete it and repost. – too honest for this site Apr 24 '17 at 19:30
  • @JustinTime "cppreference" is not an autoritative reference. Sorry, but when it comes to such details, **only** the standard is relevant. – too honest for this site Apr 24 '17 at 19:30
  • @Olaf I suppose it comes down to interpretation, then. Where both C and C++ standards say that it's permissible to access a value of type `T*` through a `char*`, I consider this to mean both read and write access are allowed. As this permission to access an object through a `char*` is intended to allow access to the object's raw byte representation, allowing write access thus means that it's permissible to write a byte sequence to the object which results in a valid object of its type. – Justin Time - Reinstate Monica Apr 24 '17 at 20:10
  • The purpose of C § 6.5p6 is determining the effective type of any given object, not specifying which operations are permissible with `memcpy()`; as such, it has no need to mention `memcpy()` in relation to objects with declared types, because their effective type is stated in the first sentence to be their declared type. It can be read as an `if-else` statement: If an object has a declared type, then its effective type is its declared type. Else, ... if a value is copied into the object with `memcpy()`, then its effective type is that value's effective type. – Justin Time - Reinstate Monica Apr 24 '17 at 20:10
  • And there is one distinct advantage in using `memcpy()` over a `union`: While using `memcpy()` is valid in both C and C++, type punning through a `union` is only guaranteed to be valid in C; in C++, [it's a mess, and may or may not be UB depending on exactly how you use the `union`](http://stackoverflow.com/a/11996970/5386374). Considering that the question asks about both languages, `memcpy()` is thus the safer option. – Justin Time - Reinstate Monica Apr 24 '17 at 20:10
  • And don't worry, it's understandable that you wouldn't know how reliable my opinion here is. You don't know who I am, and we were at a disagreement, so it's only natural not to be inclined to believe what I was saying. That's why I was trying to back it up with standard quotes and links to CPPReference (the latter on the grounds that since it's frequented by knowledgeable programmers, any inaccuracies would likely be corrected as soon as they were noticed). – Justin Time - Reinstate Monica Apr 24 '17 at 20:14
  • It's not the most definitive reference, but it's been accurate to the C and C++ standards for most things I've looked up on it. – Justin Time - Reinstate Monica Apr 24 '17 at 20:22
  • @JustinTime: Sorry, but by mentioning cppreference, you did actually do the opposite and made your comments more suspective. They are not always correct, as are some high-voted answers here, just because they **look** plausible and most voters don't really follow the line of argumentation. One of my guidelines is majority is not always correct ("eat feces, millions of flies can't be wrong" :-). I prefer to read the standard myself; have to anyway if I want to know something in-depth. – too honest for this site Apr 24 '17 at 20:25
  • @Olaf Ah, okay, that makes sense. I usually tend to refer to CPPReference in most cases, but look things up in the relevant standard when I need more details or an exact quote, personally, since the site unfortunately doesn't provide standard citations. – Justin Time - Reinstate Monica Apr 27 '17 at 16:15

4 Answers4

6

It's completely legal in C++ to type pun via pointer casting, as long as you're only doing it to char*. This, not coincidentally, is what memcpy is defined as working on (technically unsigned char* which is good enough).

Kindly observe the following passage:

For any object (other than a base-class subobject) of trivially copyable type T, whether or not the object holds a valid value of type T, the underlying bytes (1.7) making up the object can be copied into an array of char or unsigned char.

42 If the content of the array of char or unsigned char is copied back into the object, the object shall subsequently hold its original value. [Example:

#define N sizeof(T)
char buf[N];
T obj;
// obj initialized to its original value
std::memcpy(buf, &obj, N);
// between these two calls to std::memcpy,
// obj might be modified 
std::memcpy(&obj, buf, N);
// at this point, each subobject of obj of scalar type
// holds its original value

— end example ]

Put simply, copying like this is the intended function of std::memcpy. As long as the types you're dealing with meet the necessary triviality requirements, it's totally legit.

Strict aliasing does not include char* or unsigned char*- you are free to alias any type with these.

Note that for unsigned ints specifically, you have some very explicit leeway here. The C++ Standard requires that they meet the requirements of the C Standard. The C Standard mandates the format. The only way that trap representations or anything like that can be involved is if your implementation has any padding bits, but ARM does not have any- 8bit bytes, 8bit and 16bit integers. So for unsigned integers on implementations with zero padding bits, any byte is a valid unsigned integer.

For unsigned integer types other than unsigned char, the bits of the object representation shall be divided into two groups: value bits and padding bits (there need not be any of the latter). If there are N value bits, each bit shall represent a different power of 2 between 1 and 2N−1, so that objects of that type shall be capable of representing values from 0 to 2N−1 using a pure binary representation; this shall be known as the value representation. The values of any padding bits are unspecified.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • He's talking about conversion between two *different* types. – Nicol Bolas Apr 21 '17 at 18:21
  • That doesn't matter at all as long as they meet the necessary triviality requirements. – Puppy Apr 21 '17 at 18:21
  • No, it really does matter. [basic.types] does not explain what the behavior will be of performing `memcpy` between objects of different types. Yes, you can copy into a buffer, and copy that buffer into some other object of a different type. But the standard does not specify what will be stored in that other object; it only specifies the behavior if it is the same type as the original source of those bits. And since the standard does not specify the behavior, it is by definition undefined. – Nicol Bolas Apr 21 '17 at 18:29
  • 1
    Are we confusing unspecified and undefined behaviour? I think it's by definition allowed to use memcpy to perform all the separate steps for POD data types, which means that the behaviour cannot be undefined. The resulting behaviour could be undefined iff subsequent use invokes it, but that's unrelated (e.g. if the result is going to be indirected as an invalid pointer value). I'd say the contents of the target object after memcpy would be _unspecified_. Which makes it fair game for implementation defined - unportable but not undefined - behaviour. – sehe Apr 21 '17 at 19:37
  • 2
    The Standard does not specify what value will be stored in the object. But if the object meets the necessary triviality requirements, it will be legal to copy totally random bytes into it. Whether or not that produces a useful value is another matter. – Puppy Apr 21 '17 at 20:00
  • 1
    I feel like trap representation may have some role in this mess but I'm too tired to go hunting in the standard. – Matteo Italia Apr 21 '17 at 23:28
  • @Puppy: "*But if the object meets the necessary triviality requirements, it will be legal to copy totally random bytes into it.*" Where does the standard say that? Because [basic.types] *only says* that it's legal to copy values into a trivially copyable type if those values were copied from an object of the same type. – Nicol Bolas Apr 21 '17 at 23:28
  • For reference: [basic.types]/2 "*For any object (other than a base-class subobject) of trivially copyable type `T` , whether or not the object holds a valid value of type `T`, the underlying bytes (4.4) making up the object can be copied into an array of `char`, `unsigned char`, or `std::byte` (21.2.1). If the content of that array is copied back into the object, the object shall subsequently hold its original value*" – Nicol Bolas Apr 21 '17 at 23:32
  • [basic.types]/3: "*For any trivially copyable type `T`, if two pointers to `T` point to distinct `T` objects `obj1` and `obj2` , where neither `obj1` nor `obj2` is a base-class subobject, if the underlying bytes (4.4) making up `obj1` are copied into `obj2`, `obj2` shall subsequently hold the same value as `obj1`.*" Nothing here is stated about the behavior of copying data which is not "the underlying bytes making up" an object of the same type. – Nicol Bolas Apr 21 '17 at 23:35
  • I'm not wholly sure about that, I'm pretty sure that I saw that you can do this in general. However in this specific case, it turns out that you don't need it anyway. – Puppy Apr 23 '17 at 10:26
5

Based on your comments, it seems you want to perform a bona fide conversion -- that is, to produce a distinct, new, separate value of a different type. This is a very different thing than a reinterpretation, such as the lead-in to your question suggests you wanted. In particular, you posit variables declared like this:

uint8x16_t  a;
uint8x8x2_t b;

// code to set the value of a ...

and you want to know how to set the value of b so that it is in some sense equivalent to the value of a.

Speaking to the C language:

The strict aliasing rule (C2011 6.5/7) says,

An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

  • a type compatible with the effective type of the object, [...]
  • an aggregate or union type that includes one of the aforementioned types among its members [...], or
  • a character type.

(Emphasis added. Other enumerated options involve differently-qualified and differently-signed versions of the of the effective type of the object or compatible types; these are not relevant here.)

Note that these provisions never interfere with accessing a's value, including the member value, via variable a, and similarly for b. But don't overlook overlook the usage of the term "effective type" -- this is where things can get bolluxed up under slightly different circumstances. More on that later.

Using a union

C certainly permits you to perform a conversion via an intermediate union, or you could rely on b being a union member in the first place so as to remove the "intermediate" part:

union {
    uint8x16_t  x1;
    uint8x8_2_t x2;
} temp;
temp.x1 = a;
b = temp.x2;

Using a typecast pointer (to produce UB)

However, although it's not so uncommon to see it, C does not permit you to type-pun via a pointer:

// UNDEFINED BEHAVIOR - strict-aliasing violation
    b = *(uint8x8x2_t *)&a;
// DON'T DO THAT

There, you are accessing the value of a, whose effective type is uint8x16_t, via an lvalue of type uint8x8x2_t. Note that it is not the cast that is forbidden, nor even, I'd argue, the dereferencing -- it is reading the dereferenced value so as to apply the side effect of the = operator.

Using memcpy()

Now, what about memcpy()? This is where it gets interesting. C permits the stored values of a and b to be accessed via lvalues of character type, and although its arguments are declared to have type void *, this is the only plausible interpretation of how memcpy() works. Certainly its description characterizes it as copying characters. There is therefore nothing wrong with performing a

memcpy(&b, &a, sizeof a);

Having done so, you may freely access the value of b via variable b, as already mentioned. There are aspects of doing so that could be problematic in a more general context, but there's no UB here.

However, contrast this with the superficially similar situation in which you want to put the converted value into dynamically-allocated space:

uint8x8x2_t *c = malloc(sizeof(*c));
memcpy(c, &a, sizeof a);

What could be wrong with that? Nothing is wrong with it, as far as it goes, but here you have UB if you afterward you try to access the value of *c. Why? because the memory to which c points does not have a declared type, therefore its effective type is the effective type of whatever was last stored in it (if that has an effective type), including if that value was copied into it via memcpy() (C2011 6.5/6). As a result, the object to which c points has effective type uint8x16_t after the copy, whereas the expression *c has type uint8x8x2_t; the strict aliasing rule says that accessing that object via that lvalue produces UB.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 2
    Sadly, malloc without (placement) new is technically UB in C++. I don't like it, but... – Yakk - Adam Nevraumont Apr 22 '17 at 17:06
  • ` There are aspects of doing so that could be problematic in a more general context` Do you mean what immediately follows `However...`, or do you mean something else? Or, is Yakk's answer addressing what you are reffering to? If the data type have the same physical size, and all bit combinations for both are valid representations, is there any possible problematic case? – Antonio Apr 24 '17 at 14:35
  • 1
    @Antonio, the potentially problematic aspects are mainly related to the possibility that the bytes copied from one object to the other do not form a valid or a complete representation of a value of the type of the object that they are copied into. Just because you can copy the bytes and access them does not imply that the resulting value of the destination object means what you want it to mean. Additionally, there is, in general, the potential to overrun the destination object's bounds. None of these apply to the specific kinds of structure types you asked about, however. – John Bollinger Apr 24 '17 at 15:17
  • This should not be marked as the correct answer. One cannot use memcpy with neon intrinsics. uint8x16_t represents a 16-byte register; while uint8x8x2_t represents two adjacent 8-byte registers. It's necessary to get (extract) the low 8 bytes and the high 8 bytes of the single 16-byte register using the functions vget_low_u8 and vget_high_u8. The return values can then be stored in a uint8x8x2_t. – D.McG. Dec 20 '18 at 01:02
3

Do you know how to convert from uint8x16_t to uint8x8x2_t?

uint8x16_t input = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
uint8x8x2_t output = { vget_low_u8(input), vget_high_u8(input) };

One must understand that with neon intrinsics, uint8x16_t represents a 16-byte register; while uint8x8x2_t represents two adjacent 8-byte registers. For ARMv7 these may be the same thing (q0 == {d0, d1}) but for ARMv8 the register layout is different. It's necessary to get (extract) the low 8 bytes and the high 8 bytes of the single 16-byte register using two functions. The clang compiler will determine which instruction(s) are necessary based on the context.

D.McG.
  • 275
  • 4
  • 6
  • "ACLE does not define static construction of vector types. E.g.. int32x4_t x = { 1, 2, 3, 4 };. Is not portable." – aqrit May 04 '21 at 22:57
  • ARM Compiler toolchain Compiler Reference: Splitting vectors https://developer.arm.com/documentation/dui0491/c/Using-NEON-Support/Splitting-vectors?lang=en – user550701 Jun 20 '22 at 06:05
2

So there are a bunch of gotchas here. This reflects C++.

First you can convert trivially copyable data to char* or unsigned char* or std::byte*, then copy it from one location to another. The result is defined behavior. The values of the bytes are unspecified.

If you do this from a value of one one type to another via something like memcpy, this can result in undefined behaviour upon access of the target type unless the target type has valid values for all byte representations, or if the layout of the two types is specified by your compiler.

There is the possibility of "trap representations" in the target type -- byte combinations that result in machine exceptions or something similar if interpreted as a value of that type. Imagine a system that doesn't use IEEE floats and where doing math on NaN or INF or the like causes a segfault.

There are also alignment concerns.

In C, I believe that type punning via unions is legal, with similar qualifications.

Finally, note that under a strict reading of the standard, foo* pf = (foo*)malloc(sizeof(foo)); is not a pointer to a foo even if foo was plain old data. You must create an object before interacting with it, and the only way to create an object outside of automatic storage is via new or placement new. This means you must have data of the target type before you memcpy into it.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524