Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

run all test on arm/aarch64 #230

Merged
merged 13 commits into from
Jan 8, 2017
Merged

Conversation

k-okada
Copy link
Member

@k-okada k-okada commented Jan 6, 2017

No description provided.

@k-okada k-okada force-pushed the fix_travis_error branch 6 times, most recently from 359df26 to 1a82928 Compare January 7, 2017 06:09
@k-okada k-okada merged commit a89bd07 into euslisp:master Jan 8, 2017
@k-okada k-okada deleted the fix_travis_error branch January 8, 2017 08:43
This was referenced Jan 8, 2017
@@ -708,6 +710,157 @@ __asm__ (".align 8\n"
"pop %rbx\n\t"
"retq"
);
#endif
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@YoheiKakiuchi このコードってどうやって作るんですか?

#395 見ているんだけど,

#include <stdio.h>

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <setjmp.h>
#include <errno.h>

#include <dlfcn.h>

#include <eus.h> // include eus.h just for eusfloat_t ...                                 

union god{
  int body;
  float spirit;
};

union god human;
union god monkey;

void _test1(int i ) {
  monkey.body = i;
  printf("_test1: %d %f\n", monkey.body, monkey.spirit);
}

void _test2(int i , float f) {
  monkey.body = i;
  printf("_test2: %d %f\n", i, f);
  printf("_test2: %d %f\n", monkey.body, monkey.spirit);
}

int main(int argc, char **argv)
{
  float malice = 2.5;
  human.spirit = malice;
  printf("%ld %ld\n", sizeof(float), sizeof(int));
  printf("main : %d %f\n", human.body, human.spirit);

  printf("_test1(human.body)\n");
  _test1(human.body);

  printf("_test2(human.body, human.spirit)\n");
  _test2(human.body, human.spirit);


  eusinteger_t (*ifunc)(); /* ???? */
  void * handle = dlopen("libtestdefforeign.so", RTLD_LAZY);
  if (!handle) {
    fprintf(stderr, "%s\n", dlerror());
    exit(-1);
  }
  //ifunc = (pointer)dlsym((void *)((eusinteger_t)(handle, "test1")));                    
  ifunc = (pointer)dlsym(handle, "test1");

  double (*ffunc)();
  ffunc=(double (*)())ifunc;

  printf("ffunc(human.body, human.spirit)\n");
  ffunc(human.body, human.spirit);

  eusinteger_t cargv[100];
  cargv[0] = human.body;
  cargv[1] = human.body;
  printf("ffunc(cargv[0], cargv[1])\n");
  ffunc(cargv[0], cargv[1]);

  numunion nargv[100];
  nargv[0].ival = human.body;
  nargv[1].fval = human.spirit;
  printf("ffunc(nargv[0], nargv[1])\n");
  ffunc(nargv[0], nargv[1]);


  float (*ffunc2)(int, float);
  ffunc2=(float (*)(int, float))ifunc;
  printf("ffunc(human.body, human.spirit)\n");
  ffunc(human.body, human.spirit);

  return 0;
}
 (gcc  -DLinux -DGCC -I/root/jskeus/eus/lisp/c -falign-functions=4 -o hoge hoge.c -ldl && ./hoge)

みたいにしているんだけど,これでも上手くいかないので,exec_function_iみたいなのを作らないといけないのかな,って.よく分かっていないんですが.

@YoheiKakiuchi
Copy link
Member

長くなっていますが、function callのアセンブリについての解説です。

問題のarmhfについては最後に書いてあります。

Foreign Function Call でやりたいこと

実行時に、引数と返り値の定義が分かっている、既知アドレスに定義されている関数をコールしたい。

コンパイラがやってくれている、引数の定義から値を適切なレジスタにセットし、コールして、
返り値の定義から適切なレジスタから値を読みだすことが必要。

x86_64

例えばこの資料 ( 'x86_64 ABI' などとgoogleさんに聞く)

https://refspecs.linuxbase.org/elf/x86_64-abi-0.99.pdf

資料の 3.2.3 Parameter Passing に関数を呼ぶ時の引数の渡し方について書いてある。

ここで、eval.c では INTEGER(char,short,int,long,pointer) と SSE(float,double)についてだけ実装している

簡単に書くと、引数については、

  • INTEGERは %rdi, %rsi, %rdx, %rcx, %r8 and %r9 のレジスタで値を渡す(INTEGERのみの引数の登場順)

  • SSEは %xmm0 から %xmm7 のレジスタで値を渡す(SSEのみの引数の登場順)

  • それより多くの引数はスタックに積む(32bitの時と同じ?)
    (原文: Once registers are assigned, the arguments passed in memory are pushed on the stack in reversed (right-to-left) order. )

返り値については、

  • %rax 1st return register

  • %rdx 2nd return register (使っていない, 64bit以上の整数、構造体等)

  • %xmm0, %xmm1 return register(float, double)

資料の Figure 3.5: Parameter Passing Example が分かりやすい

eval.cのコード

実際のcall_foreignでは、
なので、引数の定義を初めから読んでいって、INTEGERならiargvに書く, SSEならfargvに書く,
iargv, fargvが埋まったら vargvに書いてvragcをカウントアップ。

(コードのこの部分 https://github.com/euslisp/EusLisp/blob/EusLisp-9.27/lisp/c/eval.c#L871-L949 )

その後、 long exec_function_i((void (*)())ifunc, iargv, fargv, vargc, vargv); のように呼び出す。

exec_function_xxxの内部では、

iargv -> %rdi, %rsi, %rdx, %rcx, %r8 and %r9 にセット
fargv -> %xmm0 から %xmm7 にセット
vargv -> vargc個分スタックに積む

ifuncをコール

exec_function_fだけ、funcの返り値 %xmm0を INTEGERの返り値レジスタ %rax にバイナリコピー。
(ここだけexec_function_iとexec_function_fでコードが異なる)

呼出し後、floatは型変換する

(コードのこの部分 https://github.com/euslisp/EusLisp/blob/EusLisp-9.27/lisp/c/eval.c#L951-L959 )

上記のように、返り値がlong/doubleの2つ用意してある意味は、返り値の種類によって入っているレジスタが違うのを解消するため。
ですが、exec_function_xxxに引数を増やして内部で場合分けするとか、exec_function_xxxの返り値レジスタを触らずにreturnして、32bitのときのようにキャストして関数種類変えるなどして、1つの関数にできるような気がしてきました。

対応していないのは

  • 構造体の値渡しと値戻し
  • long double
  • 他にもあるかもしれないが、ほとんど見ない形式だと考えている、またeusの側での対応も現状ない。

aarm64

例えばこの資料 ( 'Procedure Call Standard for the ARM 64-bit Architecture' などとgoogleさんに聞く)
https://static.docs.arm.com/100986/0000/abi_sve_aapcs64_100986_0000_00_en.pdf

なんかレジスタの名前が違う気がする。。。

レジスタ
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0801aj/CJACFHCC.html

浮動小数点レジスタ
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0801aj/CJACFHCC.html

簡単に書くと、引数については、

  • INTEGERは x0 から x7 のレジスタで値を渡す(INTEGERのみの引数の登場順)

  • SSEは d0 から d7 のレジスタで値を渡す(SSEのみの引数の登場順)

  • それより多くの引数はスタックに積む(32bitの時と同じ?)
    (原文: Once registers are assigned, the arguments passed in memory are pushed on the stack in reversed (right-to-left) order. )

返り値については、

  • int の場合は x0 のレジスタに戻る

  • floatの場合は d0 のレジスタに戻る

とほぼ、x86_64とやっていることについて同じ。
intのレジスタ数だけ違うので、このように(https://github.com/euslisp/EusLisp/blob/EusLisp-9.27/lisp/c/eval.c#L855-L863)定義されている。

armhf

armで32bitだが浮動小数点演算器が搭載されていてそれを使うもの
(Raspberry Piのdebianが主に使っている)

https://developer.arm.com/docs/ihi0042/latest

  • INTEGERは r0 から r3 のレジスタで値を渡す

  • SSEは s0 から s15 (d0 から d7) のレジスタで値を渡す

  • それより多くの引数はスタックに積む(32bitの時と同じ?)

返り値については、

  • int の場合は r0 のレジスタに戻る

  • floatの場合は s0 (d0) のレジスタに戻る

と書いてあるように見えます。

なので、intelの32bitのようには出来なくて、exec_function_i/fを書く必要があるかと思います。

floatとdoubleで場合分けが入っているようにも見えて、floatとdoubleの混合の場合はどうなるかなどアセンブラコードを見ないと分からないです。

aarch64と微妙に異なるので、新たにarmhfだけに対応するexec_function_i/fと、pointer call_foreign(ifunc,code,n,args)を書く必要があるかと思います。

exec_function_i/fについては、アセンブラのコードを書く必要があります。
http://infocenter.arm.com/help/topic/com.arm.doc.dui0489bj/DUI0489BJ_arm_assembler_reference.pdf

32bitと64bitで違いそうなところ

  • ポインタサイズが4byte (64bitは8byte)
  • doubleをスタックに積んで渡す時にどうするか。
  • double/floatの値をexec_functionに渡す時のfargvをdouble *(64bitの配列)にする必要がありそう

アセンブラは gcc -S test.cとするとtest.sができるので、関数を呼ぶ時どんなアセンブラになっているか、リファレンスを見ながらアセンブラを考えるような感じです。

test.c (exec_function_iと引数が同じ関数をアセンブラ化してみる)

long exec_function_i(void (*f)(), long *a, long *b, long c, long *d)
{
  // 引数の処理 *1
  long aa = *a; // ここなにやっても良くて、値を使わないとコードがよくわからないので。
  long bb = *b;

  long p = aa + bb + c;

  long dd = *d;
  // 引数の処理終わり *2
  
  // 関数呼び出し 
  f();

  return dd;
}

gcc -S test.c とすると、test.sができる。

test.s 引数を渡すところ、返り値をセットするところを参考にして、それ以外の部分はアセンブラを書く。

        .align  2
        .global exec_function_i
        .type   exec_function_i, %function
exec_function_i:
        stp     x29, x30, [sp, -96]!  @ x29,x30の内容をスタック(sp)に退避 **1
        add     x29, sp, 0            @ x29にspの値をロード
        str     x0, [x29, 56]         @ x0(引数1つめ)を [x29, 56] へ入れる (スタック変数)
        str     x1, [x29, 48]         @ x1(引数2つめ)を [x29, 48] へ入れる (スタック変数)
        str     x2, [x29, 40]         @ x2(引数3つめ)を [x29, 40] へ入れる (スタック変数) 
        str     x3, [x29, 32]         @ x3(引数4つめ)を [x29, 32] へ入れる (スタック変数)
        str     x4, [x29, 24]         @ x4(引数5つめ)を [x29, 24] へ入れる (スタック変数)
        ldr     x0, [x29, 48]         @ ここから long aa = *a; だと思われる / test.c の *1
        ldr     x0, [x0]
        str     x0, [x29, 64]
        ldr     x0, [x29, 40]
        ldr     x0, [x0]
        str     x0, [x29, 72]
        ldr     x1, [x29, 64]
        ldr     x0, [x29, 72]
        add     x1, x1, x0
        ldr     x0, [x29, 32]
        add     x0, x1, x0
        str     x0, [x29, 80]
        ldr     x0, [x29, 24]
        ldr     x0, [x0]
        str     x0, [x29, 88]         @ ここまで / test.c の *2
        ldr     x0, [x29, 56]         @ ここで[x29, 56](引数1つめ)をx0に入れて
        blr     x0                    @ x0アドレスの関数呼び出し / f(); だと思われる
        ldr     x0, [x29, 88]         @ exec_function_iのための返り値をx0に書く
        ldp     x29, x30, [sp], 96    @ スタックからx29,x30の内容を書き戻す ( **1 の逆 )
        ret
        .size   exec_function_i, .-exec_function_i

@k-okada
Copy link
Member Author

k-okada commented Jun 1, 2020

@YoheiKakiuchi ありがとうございます

# cat fuga.c
long exec_function_i(void (*f)(), long *a, long *b, long c, long *d)
{
  //
  long aa = *a; //
  long bb = *b;

  long p = aa + bb + c;

  long dd = *d;
  // 
  f();

  return dd;
}
# gcc -fverbose-asm -S fuga.c
# cat fuga.s  
....

        .text
        .align  2
        .global exec_function_i
        .syntax unified
        .thumb
        .thumb_func
        .type   exec_function_i, %function
exec_function_i:
        @ args = 4, pretend = 0, frame = 32
        @ frame_needed = 1, uses_anonymous_args = 0
        push    {r7, lr}        @
        sub     sp, sp, #32     @,,
        add     r7, sp, #0      @,,
        str     r0, [r7, #12]   @ f, f
        str     r1, [r7, #8]    @ a, a
        str     r2, [r7, #4]    @ b, b
        str     r3, [r7]        @ c, c
        ldr     r3, [r7, #8]    @ tmp113, a
        ldr     r3, [r3]        @ tmp114, *a_2(D)
        str     r3, [r7, #16]   @ tmp114, aa
        ldr     r3, [r7, #4]    @ tmp115, b
        ldr     r3, [r3]        @ tmp116, *b_4(D)
        str     r3, [r7, #20]   @ tmp116, bb
        ldr     r2, [r7, #16]   @ tmp117, aa
        ldr     r3, [r7, #20]   @ tmp118, bb
        add     r2, r2, r3      @ D.4237, tmp118
        ldr     r3, [r7]        @ tmp120, c
        add     r3, r3, r2      @ tmp119, D.4237
        str     r3, [r7, #24]   @ tmp119, p
        ldr     r3, [r7, #40]   @ tmp121, d
        ldr     r3, [r3]        @ tmp122, *d_9(D)
        str     r3, [r7, #28]   @ tmp122, dd
        ldr     r3, [r7, #12]   @ tmp123, f
        blx     r3      @ tmp123
        ldr     r3, [r7, #28]   @ D.4237, dd
        mov     r0, r3  @, <retval>
        adds    r7, r7, #32     @,,
        mov     sp, r7  @,
        @ sp needed     @
        pop     {r7, pc}        @
        .size   exec_function_i, .-exec_function_i
        .ident  "GCC: (Ubuntu/Linaro 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
        .section        .note.GNU-stack,"",%progbits

みたいにでてくるコードが違うんですが,上のコードはどうやって作ったでしょうか?

@YoheiKakiuchi
Copy link
Member

すいません。コマンドのヒストリをたどると、arm64での結果だったようです。

こちらで確認したら、岡田先生のと同じでした。

docker run -it osrf/ubuntu_armhf:xenial bash

@k-okada
Copy link
Member Author

k-okada commented Jun 3, 2020

@YoheiKakiuchi
なるほど.
#434
24d1090
みたいな感じでいいのかなあ.まだ:double が対応していないけど...

@YoheiKakiuchi
Copy link
Member

:double は悩ましいですね。
doubleとfloatの引数が混ざっているときは、レジスタもdとsを混ぜて配置しているようで、
fargvへの積み方を考えないといけないですね。

例えば以下の関数
int fuga (float a, double b, float c, double d,
          float e, double f, float g, double h,
          float i, double j, float k, double l)

レジスタ配置
|d0   |d1   |d2   |d3   |d4   |d5     |d6     |d7     |
|s0|s1|s2|s3|s4|s5|s6|s7|s8|s9|s10|s11|s12|s13|s14|s15|

s0 <- a(変数)
d1 <- b
s1 <- c
d2 <- d
s6 <- e
d4 <- f
s7 <- g
d5 <- h
s12<- i
d7 <- j
s13<- k

lはスタックに積まれる

あと、スタックに積まれる場合も、doubleは8byte、floatは4byteで積まれるので、それも対応必要ありそうです。

x86_64はスタックは常に8byteで使うので大丈夫だと思います。

@k-okada
Copy link
Member Author

k-okada commented Jun 7, 2020

なるほどね.
c587680
みたいな感じでしょうか.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants