ARM Neon: How to convert from uint8x16_t to uint8x8x2_t?
Asked Answered
U

4

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).

Unaccomplished answered 20/4, 2017 at 13:38 Comment(57)
Using standard C++ std::memcpy. Any other (C++) way requires a compiler extension which you would have to research.Oys
@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...Unaccomplished
Possible duplicate of ARM Neon in C: How to combine different 128bit data types while using intrinsics?Convulsion
@RichardCritten: memcpying from/to different datatypes is not different than an assignment with a cast: it invokes UB (there are few exceptions for char types.Convulsion
@Olaf This is more specific, and the answer provided there doesn't answer this questionUnaccomplished
@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.Unaccomplished
@Olaf it is defined behaviour to to cast both source and destination to char * and then memcpy.Oys
@RichardCritten If you want to submit an answer in this respect, I will accept it (unless anything better comes out :) )Unaccomplished
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..Robin
@Unaccomplished 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.Robin
@Unaccomplished 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.Robin
@harold meta.stackexchange.com/questions/225370/… :) Take the 2 links mentioned in this commentUnaccomplished
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.Oys
@Unaccomplished 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: en.cppreference.com/w/c/language/union - I don't have the standard to hand.Oys
@RichardCritten It seems you are right about union! (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)Unaccomplished
It's far more clear here.Unaccomplished
@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.Convulsion
@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.Convulsion
@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.Convulsion
@Antonio: Your question is abotu C; please stop adding tags for unrelated, different languages.Convulsion
@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.Unaccomplished
@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++Unaccomplished
@Antonio: 1) Please provide a reference to the standard. 2) Please provide a citation I said different. 3) Where did I say different?Convulsion
@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.Unaccomplished
@Antonio: How about reading the standartd? I gave the paragraph in a comment above.Convulsion
@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.Convulsion
@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.Unaccomplished
@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.Convulsion
@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*Unaccomplished
Last comment, I'm tired of these discussions: This does invoke UB!Convulsion
You're wrong. That code is completely legal C++.Sabra
Meta discussion related to this comment thread: meta.#348697 (cc: @Olaf)Dunnite
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.Gunther
@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]. std::memcpy() aliases both pointers as unsigned char*, which is a well-defined operation. 2) Both types are TriviallyCopyable, preventing other UB.Alaniz
3) CPPReference explicitly notes that memcpy() can be used to convert a value to a different type without violating strict aliasing rules.Alaniz
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.Alaniz
@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.Convulsion
@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.Marcus
... 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.Marcus
@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.Gunther
@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];};.Unaccomplished
@Antonio: Yes, they're probably somewhat similar. Nonetheless, your problem is equivalent to making supper. You don't need to solve world hunger.Gunther
@Olaf Please take a look at John Bollinger's answerUnaccomplished
@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++.Alaniz
And now that we mentioned C, § 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, 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.Alaniz
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.Alaniz
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, 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. The reason for this is that memcpy() examines both as unsigned char*, which is legal.Alaniz
@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.Convulsion
@JerryCoffin: Yes, sorry, darn autocompletion. I'll delete it and repost.Convulsion
@JustinTime "cppreference" is not an autoritative reference. Sorry, but when it comes to such details, only the standard is relevant.Convulsion
@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.Alaniz
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.Alaniz
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. Considering that the question asks about both languages, memcpy() is thus the safer option.Alaniz
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).Alaniz
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.Alaniz
@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.Convulsion
@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.Alaniz
F
4

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.

Feces answered 21/4, 2017 at 19:9 Comment(4)
Sadly, malloc without (placement) new is technically UB in C++. I don't like it, but...Malanie
` 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?Unaccomplished
@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.Feces
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.Schizoid
S
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.

Sabra answered 21/4, 2017 at 18:18 Comment(10)
He's talking about conversion between two different types.Kienan
That doesn't matter at all as long as they meet the necessary triviality requirements.Sabra
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.Kienan
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.Kaitlinkaitlyn
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.Sabra
I feel like trap representation may have some role in this mess but I'm too tired to go hunting in the standard.Marybethmaryellen
@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.Kienan
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"Kienan
[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.Kienan
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.Sabra
F
4

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.

Feces answered 21/4, 2017 at 19:9 Comment(4)
Sadly, malloc without (placement) new is technically UB in C++. I don't like it, but...Malanie
` 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?Unaccomplished
@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.Feces
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.Schizoid
S
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.

Schizoid answered 20/12, 2018 at 0:57 Comment(2)
"ACLE does not define static construction of vector types. E.g.. int32x4_t x = { 1, 2, 3, 4 };. Is not portable."Charron
ARM Compiler toolchain Compiler Reference: Splitting vectors developer.arm.com/documentation/dui0491/c/Using-NEON-Support/…Rabon
M
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.

Malanie answered 22/4, 2017 at 16:14 Comment(1)
@Unaccomplished yes. Typo fixed.Malanie

© 2022 - 2024 — McMap. All rights reserved.