www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - inline ASM function calling conventions.

reply Sjoerd Nijboer <dlang sjoerdnijboer.com> writes:
I'm kinda puzzled.

I'm having trouble getting started with inline asm in D.
Suppowse I have the following:

void Foo(MyStrunct* first_arg, MyStrunct* second_arg)
{
     asm
     {
         naked;
         version(X86)
         {
             /* Do something with the content of I and J. */
         }
         version(X86_64)
         {
             /* Do something with the content of I and J. *?
         }
     }
}

void Bar()
{
     MyStrunct heapStructA, heapStructB;

     // do some initialization.

     Foo(heapStructA, heapStructB);
}

Suppose I would like to pass first_arg and second_arg by register 
to `Foo`, what calling convention should I use? I'm having 
trouble identifying the correct calling convention I should be 
using for X86 and X86_64 in D.

I've tried a couple using https://godbolt.org/ and DMD parameters 
'-g -m32', but I can't seem to find any that will pass by 
register. Is there one? Will it also work for LDC and GDC?
Sep 30 2018
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 30 September 2018 at 10:46:33 UTC, Sjoerd Nijboer 
wrote:
 I'm kinda puzzled.

 I'm having trouble getting started with inline asm in D.
 Suppowse I have the following:

 void Foo(MyStrunct* first_arg, MyStrunct* second_arg)
 {
     asm
     {
         naked;
         version(X86)
         {
             /* Do something with the content of I and J. */
         }
         version(X86_64)
         {
             /* Do something with the content of I and J. *?
         }
     }
 }

 void Bar()
 {
     MyStrunct heapStructA, heapStructB;

     // do some initialization.

     Foo(heapStructA, heapStructB);
 }

 Suppose I would like to pass first_arg and second_arg by 
 register to `Foo`, what calling convention should I use? I'm 
 having trouble identifying the correct calling convention I 
 should be using for X86 and X86_64 in D.

 I've tried a couple using https://godbolt.org/ and DMD 
 parameters '-g -m32', but I can't seem to find any that will 
 pass by register. Is there one? Will it also work for LDC and 
 GDC?
Hello, i think this should be here (https://dlang.org/spec/abi.html) because myself i never remember them correctly without playing a bit with a disassembler. After playing a bit: Linux x86_64: extern(D) void Foo(MyStruct* s1, MyStruct* s2, MyStruct* s3, MyStruct* s4) { asm { naked; // rcx -> s1 // rdx -> s2 // rsi -> s3 // rdi -> s4 ret; } } ==================== Linux x86 / Window x86: extern(D) void Foo(MyStruct* s1, MyStruct* s2, MyStruct* s3, MyStruct* s4) { asm { naked; // ebx -> s1 // edx -> s2 // ecx -> s3 // eax -> s4 ret; } } ==================== Win64 has a different calling convention than Linux64, i cant remember the difference. ==================== Linux x86_64 example: module runnable; import std.stdio; alias writeInt = writeln!int; struct MyStruct{int i;} extern(D) void foo(MyStruct* s1, MyStruct* s2, MyStruct* s3, MyStruct* s4) { asm { naked; mov RAX, RDI; // move s4 here since we call something... push RDX; // save s2 push RSI; // save s3 push RAX; // save s4 mov RDI, MyStruct.i.offsetof[RCX]; call writeInt; pop RAX; // restore... pop RSI; pop RDX; push RSI; // save s3 push RAX; // save s4 mov RDI, MyStruct.i.offsetof[RDX]; call writeInt; pop RAX; // restore pop RSI; push RAX; // save s4 mov RDI, MyStruct.i.offsetof[RSI]; call writeInt; pop RAX; // restore mov RDI, MyStruct.i.offsetof[RAX]; call writeInt; ret; } } void main() { MyStruct s1 = MyStruct(1); MyStruct s2 = MyStruct(2); MyStruct s3 = MyStruct(3); MyStruct s4 = MyStruct(4); foo(&s1, &s2, &s3, &s4); } here too : https://run.dlang.io/is/CWd4aO
Sep 30 2018
parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 30 September 2018 at 11:53:17 UTC, Basile B. wrote:
 On Sunday, 30 September 2018 at 10:46:33 UTC, Sjoerd Nijboer 
 wrote:
 [...]
Hello, i think this should be here (https://dlang.org/spec/abi.html) because myself i never remember them correctly without playing a bit with a disassembler. After playing a bit:
Without all the save/restore BS: module runnable; import std.stdio; alias write4Int = writeln!(int,int,int,int); struct MyStruct{int i;} extern(D) void foo(ref MyStruct s1, ref MyStruct s2, ref MyStruct s3, ref MyStruct s4) { asm { naked; mov RCX, MyStruct.i.offsetof[RCX]; mov RDX, MyStruct.i.offsetof[RDX]; mov RSI, MyStruct.i.offsetof[RSI]; mov RDI, MyStruct.i.offsetof[RDI]; call write4Int; ret; } } void main() { MyStruct s1 = MyStruct(1); MyStruct s2 = MyStruct(2); MyStruct s3 = MyStruct(3); MyStruct s4 = MyStruct(4); foo(s1, s2, s3, s4); }
Sep 30 2018
parent Sjoerd Nijboer <dlang sjoerdnijboer.com> writes:
On Sunday, 30 September 2018 at 12:07:53 UTC, Basile B. wrote:
 On Sunday, 30 September 2018 at 11:53:17 UTC, Basile B. wrote:
 Hello, i think this should be here 
 (https://dlang.org/spec/abi.html) because myself i never 
 remember them correctly without playing a bit with a 
 disassembler.
 After playing a bit:
Thank you for the link, very informative! I'm spitting it through right now.
 Without all the save/restore BS:

 module runnable;

 import std.stdio;

 alias write4Int = writeln!(int,int,int,int);

 struct MyStruct{int i;}

 extern(D) void foo(ref MyStruct s1, ref MyStruct s2, ref 
 MyStruct s3, ref MyStruct s4)
 {
     asm
     {
         naked;
         mov     RCX, MyStruct.i.offsetof[RCX];
         mov     RDX, MyStruct.i.offsetof[RDX];
         mov     RSI, MyStruct.i.offsetof[RSI];
         mov     RDI, MyStruct.i.offsetof[RDI];
         call    write4Int;
         ret;
     }
 }

 void main()
 {
     MyStruct s1 = MyStruct(1);
     MyStruct s2 = MyStruct(2);
     MyStruct s3 = MyStruct(3);
     MyStruct s4 = MyStruct(4);
     foo(s1, s2, s3, s4);
 }
In X86_64 it works beautiful! But in X86 it will pass them via the stack, and I'm trying to get rid of that.
Sep 30 2018
prev sibling parent reply kinke <noone nowhere.com> writes:
On Sunday, 30 September 2018 at 10:46:33 UTC, Sjoerd Nijboer 
wrote:
 I'm kinda puzzled.

 I'm having trouble getting started with inline asm in D.
 Suppowse I have the following:

 void Foo(MyStrunct* first_arg, MyStrunct* second_arg)
 {
     asm
     {
         naked;
         version(X86)
         {
             /* Do something with the content of I and J. */
         }
         version(X86_64)
         {
             /* Do something with the content of I and J. *?
         }
     }
 }

 void Bar()
 {
     MyStrunct heapStructA, heapStructB;

     // do some initialization.

     Foo(heapStructA, heapStructB);
 }

 Suppose I would like to pass first_arg and second_arg by 
 register to `Foo`, what calling convention should I use? I'm 
 having trouble identifying the correct calling convention I 
 should be using for X86 and X86_64 in D.

 I've tried a couple using https://godbolt.org/ and DMD 
 parameters '-g -m32', but I can't seem to find any that will 
 pass by register. Is there one? Will it also work for LDC and 
 GDC?
1) `asm {}` is supported by DMD and LDC, but not by GDC. 2) `extern(D)` reverses the args - `foo(a, b)` is actually `foo(b, a)` on a lower level. 3) The 32-bit x86 D calling convention (independent from OS) is specified here: https://dlang.org/spec/abi.html#function_calling_conventions All args are passed on the stack, except for the last one under certain circumstances, see point 38.12.3.3. 4) For x86_64, there are 2 (completely different) ABIs, Win64 and the System V one. Specs can be found online. In your case: void Foo(MyStrunct* first_arg, MyStrunct* second_arg) { asm { naked; } version (D_InlineAsm_X86) { // first_arg is on the stack, at [ESP+4] // second_arg is in EAX } else version (D_InlineAsm_X86_64) { version (Win64) { // first_arg is in RDX // second_arg is in RCX } else { // first_arg is in RSI // second_arg is in RDI } } }
Sep 30 2018
parent Sjoerd Nijboer <dlang sjoerdnijboer.com> writes:
On Sunday, 30 September 2018 at 12:32:08 UTC, kinke wrote:
 1) `asm {}` is supported by DMD and LDC, but not by GDC.
Good to know. Guess I will be targeting DMD and LDC then.
 4) For x86_64, there are 2 (completely different) ABIs, Win64 
 and the System V one. Specs can be found online.

 In your case:

 void Foo(MyStrunct* first_arg, MyStrunct* second_arg)
 {
     asm { naked; }
     version (D_InlineAsm_X86)
     {
         // first_arg is on the stack, at [ESP+4]
         // second_arg is in EAX
     }
     else version (D_InlineAsm_X86_64)
     {
         version (Win64)
         {
             // first_arg is in RDX
             // second_arg is in RCX
         }
         else
         {
             // first_arg is in RSI
             // second_arg is in RDI
         }
     }
 }
So in X86 asm, if I want to pas two parameters to a function I can have one in a register, but not two. The second will go via the stack. But on X86_64 it won't? Guess I'll just have to live with that. Thank you!
Sep 30 2018