tag:blogger.com,1999:blog-5246987755651065286.post1261708867718012501..comments2024-02-22T16:15:42.388-08:00Comments on cbloom rants: 05-29-10 - x64 so farcbloomhttp://www.blogger.com/profile/10714564834899413045noreply@blogger.comBlogger11125tag:blogger.com,1999:blog-5246987755651065286.post-30347890512407207102010-05-31T02:40:32.558-07:002010-05-31T02:40:32.558-07:00That's how you implement memcpy portably, whic...That's how you implement memcpy portably, which is what C cares about. (Maybe your ints have sizeof(int)==4 but 30-bit range and a 2-bit garbage collection tag, and if you read 4 arbitrary 8-bit chars into them then you'll crash randomly). If you only care about real life and not about obscure historical or hypothetical or future architectures then you're going outside the standard's scope, so you have to rely on compiler-specific features (and then you can use GCC's <a href="http://gcc.gnu.org/onlinedocs/gcc/Type-Attributes.html" rel="nofollow">__attribute__((__may_alias__))</a> or compile in a separate source file with -fno-strict-aliasing, or use a compiler that doesn't do strict aliasing optimisations at all).Philip Taylorhttps://www.blogger.com/profile/13121419389252492670noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-14792968833511074412010-05-30T18:41:44.075-07:002010-05-30T18:41:44.075-07:00"To implement memcpy in C you just do it like..."To implement memcpy in C you just do it like "T src; S dst; for (i...) ((char*)&dst)[i] = ((char*)&src)[i]" - that's where the special case of accessing values through char types comes in, which makes it fine for src"<br /><br />That's not how you implement memcpy. You roll up to U32 copies, then U128 copies and maybe do SSE non-temporal streams. How am I supposed to do that without a way to tell the compiler "hey I am aliasing some shit here, respect it".<br /><br />I know the reality is memcpy is not really a library function any more, it's basically a keyword in the language with special meaning, but I need to be able to stuff similar to memcpy often so I think it's a salient example.cbloomhttps://www.blogger.com/profile/10714564834899413045noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-39942878237051012962010-05-30T17:39:58.309-07:002010-05-30T17:39:58.309-07:00If you implement a bit_cast with memcpy then it...If you implement a bit_cast with memcpy then it's not really a cast, it's a copy - you're never asking it to read/write the same piece of memory as two different non-char types so there's no unsafe aliasing. It solves the problem by returning a fresh value with the original bytes copied into it rather than returning a pointer that's aliased with the original.<br /><br />To implement memcpy in C you just do it like "T src; S dst; for (i...) ((char*)&dst)[i] = ((char*)&src)[i]" - that's where the special case of accessing values through char types comes in, which makes it fine for src to be accessed as both T and char. Nothing is ever used as both T and S so there's no problem, and there's no need to extend the language and compilers with new constructs to add to the already-unpleasantly-complex aliasing mess.<br /><br />(Then you rely heavily on the optimiser to make all your char copying not completely awful, or you rely on compiler-specific knowledge and write technically undefined code instead.)Philip Taylorhttps://www.blogger.com/profile/13121419389252492670noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-61215245637560144942010-05-30T15:21:33.951-07:002010-05-30T15:21:33.951-07:00"The relevant special case in the standard (3..."The relevant special case in the standard (3.10.15) is about accessing values of any type through an lvalue of char type, not about casting."<br /><br />Mmm maybe if I just cast both sides to char * and poke them as char's that will trip things up.<br /><br />"The compiler doesn't care what same_size_bit_cast_p does internally"<br /><br />I don't think that's true. If you have a memcpy internally it must turn off the no-aliasing assumption. I assume most compilers turn it off when they see asm blocks too.<br /><br />"A standardised bit_cast wouldn't be any different - the compiler would have to do some whole-program data-flow analysis"<br /><br />That's not true. All I need is a local __assume_aliasing { } block.<br /><br />The whole way assume-no-aliasing is associated with types is bonkers IMO. Same thing with putting restrict on variables. It should be portions of *code*. I should be able to take chunks and say "here there be aliasing" or "here there be no aliasing".<br /><br />"You can implement Swap32 in C like"<br /><br />I don't see how that's valid unless memcpy and casts to char* (accesses as char) get special treatment (eg. triggering reloads from memory afterward). So let me have a way to achieve the same thing.<br /><br />And of course to implement memcpy I'd have to do lots of type punning, so how in the world do I implement memcpy?<br /><br />I think I could get everything I want if I just had a _CompilerMemFence() directive. CompilerMemFence is not a real memory fence, it just forces the compiler to act as if all writes are flushed before the fence, and all future reads will see any changes from earlier writes.cbloomhttps://www.blogger.com/profile/10714564834899413045noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-74990313697751889142010-05-30T13:52:18.826-07:002010-05-30T13:52:18.826-07:00It doesn't work in (at least) 32/64-bit GCC 4....It doesn't work in (at least) 32/64-bit GCC 4.1 and 4.4 on Linux - simple test case:<br /><br />int main() {<br /> float f = 0;<br /> same_size_bit_cast_p<int, float>(f) = 0x3f800000;<br /> printf("%f\n", f);<br />}<br /><br />prints 1.000000 with -O0, and 0.000000 with -O2. (It would work in old GCCs that don't have strict aliasing optimisations, but that's not very interesting.)<br /><br />The relevant special case in the standard (3.10.15) is about <i>accessing</i> values of any type through an lvalue of char type, not about casting. Casting might give you an lvalue of char type, but all that matters is the type of the lvalues you use for dereferencing and the casts are a distraction.<br /><br />The compiler doesn't care what same_size_bit_cast_p does internally (whether it's doing lots of casts or going through a union or is written in assembly or whatever), it simply knows that writes to an int lvalue can never legally affect reads of a float. A standardised bit_cast wouldn't be any different - the compiler would have to do some whole-program data-flow analysis to work out which variables you might leak aliased pointers into, otherwise it could never be sure if the optimisation is safe.<br /><br />You can implement Swap32 in C like<br /><br />void Swap32(char* a, char* b) {<br /> char t[4];<br /> memcpy(t, a, 4);<br /> memcpy(a, b, 4);<br /> memcpy(b, t, 4);<br />}<br /><br />which gets optimised into mov instructions, and it's legal since values are only being accessed as their original type and as char (since memcpy is defined as (conceptually) copying chars). Not pretty, but at least it's possible.Philip Taylorhttps://www.blogger.com/profile/13121419389252492670noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-11626521916315423282010-05-30T12:01:22.374-07:002010-05-30T12:01:22.374-07:00There is one solution for this :
Swap32( uint32 *...There is one solution for this :<br /><br />Swap32( uint32 * a, uint32 * b);<br /><br />which is to instead define it like<br /><br />Swap32( void* a, void* b);<br /><br />because void* and char* are allowed to alias anything. Now, if I actually implemented Swap32 in C, I would have to cast back from void* to uint32* which would be forbidden, so I'll just fucking implement it in assembly. Take that, compiler!cbloomhttps://www.blogger.com/profile/10714564834899413045noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-79837872012298573412010-05-30T11:51:27.362-07:002010-05-30T11:51:27.362-07:00Yeah this is fucking annoying and pointless.
How ...Yeah this is fucking annoying and pointless.<br /><br />How am I supposed to do this? I have some type I know is 32 bits and aligned and blah blah, like<br /><br />__declspec align(4)<br />struct { uint16 a; uint8 b[2]; }<br /><br />and I want to call<br /><br />Swap32( uint32 * a, uint32 * b);<br /><br />and pass in my struct. WTF.cbloomhttps://www.blogger.com/profile/10714564834899413045noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-74045228271583211822010-05-30T11:23:10.275-07:002010-05-30T11:23:10.275-07:00"That doesn't make aliasing work (in theo..."That doesn't make aliasing work (in theory or in practice). (I think the union doesn't work in theory but does in practice)."<br /><br />Hmm I'm pretty sure char * does in fact work in practice on some compilers (gcc 32 if I recall correctly), but you are right that it's not a general solution.<br /><br />"Casts are irrelevant"<br /><br />This is not true, casting to char * is special cased in the standard.<br /><br />"is that you can't legally access a value in memory as two incompatible types."<br /><br />Yeah this is bollocks and I have to find a real solution to get around it.<br /><br />Fucking C99 should have defined a standardized "bit_cast". Either that or just an "allow aliasing here" bracket.cbloomhttps://www.blogger.com/profile/10714564834899413045noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-48248787099942874782010-05-30T05:28:14.598-07:002010-05-30T05:28:14.598-07:00"// cast through char * to make aliasing work..."// cast through char * to make aliasing work ?"<br /><br />That doesn't make aliasing work (in theory or in practice). (I think the union doesn't work in theory but does in practice). Casts are irrelevant - what matters is that you can't legally access a value in memory as two incompatible types. T is compatible with const T, and with a member of type T in a struct/union, etc, and with char, but it's not compatible with some unrelated type S. If you access the value as T then you can never access the same value as S, regardless of what you do with the pointer types. If the compiler sees a T* and an S* at the same time, it can always assume changes to one won't affect the other. So you have to copy the value into a value of the new type (use memcpy and the compiler will often inline it and optimise it into nothingness).Philip Taylorhttps://www.blogger.com/profile/13121419389252492670noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-51041845594616593172010-05-29T15:36:31.266-07:002010-05-29T15:36:31.266-07:00Yeah, I'm almost with you on that.
Unfortunat...Yeah, I'm almost with you on that.<br /><br />Unfortunately MS didn't get the message about making all Vista & Win7 versions 64 bit . Grrr.<br /><br />Also if you want to actually sell something, XP-32 is still king :<br /><br />http://store.steampowered.com/hwsurvey/<br /><br />on the plus side XP-64 is negligible, so I can just ignore that case.<br /><br />I guess the simplified case is to support XP-32 and Vista-64 , and for the other weirdo cases (Vista-32,XP-64) they can just run the XP-32 version.<br /><br />Also the Vista-64 version only supports the newer AMD64 chips with all the instructions, and older chips get the XP-32 version.<br /><br />Mmm that's not too bad.cbloomhttps://www.blogger.com/profile/10714564834899413045noreply@blogger.comtag:blogger.com,1999:blog-5246987755651065286.post-29400021440429200632010-05-29T15:24:26.705-07:002010-05-29T15:24:26.705-07:00> I could write my own Wintel code for home and...> I could write my own Wintel code for home and not think about any of that<br /><br />It's still monoflavour, it's just Win7+, x64, VS08+ now.<br /><br />x86? vs05? xp/vista? Let it go. There's so many other platforms (OSX, Linux, phones*N) vying for attention that that's all Windows gets allocated these days.Scott Grahamhttps://www.blogger.com/profile/05856999162962423216noreply@blogger.com