5/29/2010

05-29-10 - Some more x64

Okay , MASM/MSDev support for x64 is a bit fucked. MSDev has built-in support for .ASM starting in VC 2005 which does everything for you, sets up custom build rule, etc. The problem is, it hard-codes to ML.EXE - not ML64. Apparently they have fixed this for VC 2010 but it is basically impossible to back-fix. (in VC 2008 the custom build rule for .ASM is in an XML file, so you can fix it yourself thusly )

The workaround goes like this :

Go to "c:\program files (x86)\microsoft visual studio 8\vc\bin". Find the occurance of ML64.exe ; copy them to ML.exe . Now you can add .ASM files to your project. Go to the Win32 platform config and exclude them from build in Win32.

You now have .ASM files for ML64. For x86/32 - just use inline assembly. For x64, you extern from your ASM file.

Calling to x64 ASM is actually very easy, even easier than x86, and there are more volatile registers and the convention is that caller has to do all the saving. All of this means that you as a little writer of ASM helper routines can get away with doing very little. Usually your args are right there in {rcx,rdx,r8,r9} , and then you can use {rax,r10,r11} as temp space, so you don't even have to bother with saving space on the stack or any of that. See list of volatile registers

BTW the best docs are just the full AMD64 manuals .

For example here's a full working .ASM file :


public my_cmpxchg64

.CODE

align 8
my_cmpxchg64 PROC 

  mov rax, [rdx]
  lock cmpxchg [rcx], r8
  jne my_cmpxchg64_fail
  mov rax, 1
  ret

align 8
my_cmpxchg64_fail:
  mov [rdx], rax
  mov rax, 0
  ret
align 8
my_cmpxchg64 ENDP

END

And how to get to it from C :


extern "C"  extern int my_cmpxchg64( uint64 * val, uint64 * oldV, const uint64 newV );

BTW one of the great things about posting things on the net is just that it makes me check myself. That cmpxchg64 has a stupid branch, I think this version is better :


align 8
my_cmpxchg64 PROC
  mov rax, [rdx]
  lock cmpxchg [rcx], r8
  sete cl
  mov [rdx], rax
  movzx rax,cl
  ret
my_cmpxchg64 ENDP

and you can probably do better. (for example it's probably better to just define your function as returning unsigned char and then you can avoid the movzx and let the caller worry about that)

ADDENDUM : I just found a new evil secret way I'm fucked. Unions with size mismatches appears not to even be a warning of any kind. So for example you can silently have this in your code :


union Fucked
{
    struct
    {
        void * p1;
        int t;
    } s;
    uint64  i;
};

build in 64 bit and it's just hose city. BTW I think using unions as a datatype in general is probably bad practice. If you need to be doing that for some fucked reason, you should just store the member as raw bits, and then same_size_bit_cast() to convert it to the various types. In other words, the dual identity of that memory should be a part of the imperative code, not a part of the data declaration.

3 comments:

nothings said...

I think it's fine to use unions; just, you can't expect the various sides of the union to take up the same amount of space.

If you're relying on them taking up the same amount of space, or, in other words, expecting that you know the effective layout of the union, include a compile-time assert to verify the size of the union.

Both this and the struct alignment thing are both pretty familiar to me from writing old-school (unix-era) portable C...

cbloom said...

"If you're relying on them taking up the same amount of space, or, in other words, expecting that you know the effective layout of the union, include a compile-time assert to verify the size of the union."

Yeah that's certainly wise, the only way I've found a lot of these problems is because I fortunately did put a lot of compiler asserts in places I was relying on weirdness, but not all the places. It also means unnamed structs and unions don't work.

"Both this and the struct alignment thing are both pretty familiar to me from writing old-school (unix-era) portable C... "

Yeah the struct alignment thing was just a brain fart. But this whole issue is giving me the heebee-jeebees about unions.

For one thing, the union has two sort of different purposes - one is just to be able to reuse some memory in two different ways depending on the state, the other is to cast through from one type to another.

For the first usage I prefer to have specifically different types. eg.

struct BSPTree_Node { ... }
struct BSPTree_Leaf { ... }

struct BSPTree_Datum
{
char flags;
char bytes[ MAX( sizeof(BSPTree_Node),
sizeof(BSPTree_Leaf) ];
};

that way you write functions that work on either a Node or a Leaf and they have no possibility of touching variables that are in the other identity.

(of course this is fucked in the new aliasing world).

For the other usage of unions, I prefer to have explicit casts as imperatives in the code so they really stand out at the point of usage to make it super clear that you are type-punning there.

But that also might be fucked so I guess I lose.

nothings said...

Yeah, the whole thing about casting through unions is new to me (well, new in the last ten years or whatever) so I'm not even used to thinking about that aspect of it.

I equivocate between using multiple structures and using unions for the "tagged-type" sort of thing.

old rants