ART世界探险(5) - 计算指令

简介: 计算机的核心功能就是计算,Android为我们提供了一个让JVM,ARM, ARM v7a, ARM64, x86, x86_64, mips, mips64共舞的舞台,让我们一起看看它们的计算分别是如何实现的吧

ART世界探险(5) - 计算指令

整数运算

Java的整型运算

我们先看看JVM是如何处理这些基本整数运算的吧。

    public static long add(long a, long b){
        return a+b;
    }

    public static long sub(long a,long b){
        return a-b;
    }

    public static long mul(long a, long b){
        return a*b;
    }

    public static long div(long a,long b){
        return a/b;
    }

    public static long mod(long a,long b){
        return a%b;
    }

翻译成字节码是这样的,非常整齐:

  public static long add(long, long);
    Code:
       0: lload_0
       1: lload_2
       2: ladd
       3: lreturn

  public static long sub(long, long);
    Code:
       0: lload_0
       1: lload_2
       2: lsub
       3: lreturn

  public static long mul(long, long);
    Code:
       0: lload_0
       1: lload_2
       2: lmul
       3: lreturn

  public static long div(long, long);
    Code:
       0: lload_0
       1: lload_2
       2: ldiv
       3: lreturn

  public static long mod(long, long);
    Code:
       0: lload_0
       1: lload_2
       2: lrem
       3: lreturn

加是add,减是sub,乘是mul,除是div,取模是rem。

转换成Dalvik指令的话,连lload都省了,更是看起来赏心悦目。

  1: long com.yunos.xulun.testcppjni2.TestART.add(long, long) (dex_method_idx=16778)
    DEX CODE:
      0x0000: 9b00 0204                    | add-long v0, v2, v4
      0x0002: 1000                         | return-wide v0

我们看看在ARM上的实现。

ARM的整型运算

C++代码和Java基本上是同出一辙的:

long add(long a, long b){
    return a+b;
}

long sub(long a,long b){
    return a-b;
}

long mul(long a, long b){
    return a*b;
}

long div(long a,long b){
    return a/b;
}

long mod(long a,long b){
    return a%b;
}

ARM v8a的整数运算

我们看看在AArch64下译成什么:

0000000000000694 <_Z3addll>:
 694:    8b010000     add    x0, x0, x1
 698:    d65f03c0     ret

000000000000069c <_Z3subll>:
 69c:    cb010000     sub    x0, x0, x1
 6a0:    d65f03c0     ret

00000000000006a4 <_Z3mulll>:
 6a4:    9b017c00     mul    x0, x0, x1
 6a8:    d65f03c0     ret

00000000000006ac <_Z3divll>:
 6ac:    9ac10c00     sdiv    x0, x0, x1
 6b0:    d65f03c0     ret

00000000000006b4 <_Z3modll>:
 6b4:    9ac10c02     sdiv    x2, x0, x1
 6b8:    9b018040     msub    x0, x2, x1, x0
 6bc:    d65f03c0     ret

ARM v7a的整数运算

AArch32模式下,加减乘都是一条指令

00000fc0 <_Z3addll>:
     fc0:    4408          add    r0, r1
     fc2:    4770          bx    lr

00000fc4 <_Z3subll>:
     fc4:    1a40          subs    r0, r0, r1
     fc6:    4770          bx    lr

00000fc8 <_Z3mulll>:
     fc8:    4348          muls    r0, r1
     fca:    4770          bx    lr

但是除法和取模就不是指令了,得调用函数来处理。

00000fcc <_Z3divll>:
     fcc:    b508          push    {r3, lr}
     fce:    f000 e830     blx    1030 <__aeabi_idiv>
     fd2:    bd08          pop    {r3, pc}

00000fd4 <_Z3modll>:
     fd4:    b508          push    {r3, lr}
     fd6:    f000 e89a     blx    110c <__aeabi_idivmod>
     fda:    4608          mov    r0, r1
     fdc:    bd08          pop    {r3, pc}

算除法的这个函数可是不短啊,我们先看一下,这个将来可供我们学完指令集之后复习用:

00001030 <__aeabi_idiv>:
    1030:    e3510000     cmp    r1, #0
    1034:    0a000030     beq    10fc <__aeabi_idiv+0xcc>
    1038:    e020c001     eor    ip, r0, r1
    103c:    42611000     rsbmi    r1, r1, #0
    1040:    e2512001     subs    r2, r1, #1
    1044:    0a00001f     beq    10c8 <__aeabi_idiv+0x98>
    1048:    e1b03000     movs    r3, r0
    104c:    42603000     rsbmi    r3, r0, #0
    1050:    e1530001     cmp    r3, r1
    1054:    9a00001e     bls    10d4 <__aeabi_idiv+0xa4>
    1058:    e1110002     tst    r1, r2
    105c:    0a000020     beq    10e4 <__aeabi_idiv+0xb4>
    1060:    e16f2f11     clz    r2, r1
    1064:    e16f0f13     clz    r0, r3
    1068:    e0420000     sub    r0, r2, r0
    106c:    e3a02001     mov    r2, #1
    1070:    e1a01011     lsl    r1, r1, r0
    1074:    e1a02012     lsl    r2, r2, r0
    1078:    e3a00000     mov    r0, #0
    107c:    e1530001     cmp    r3, r1
    1080:    20433001     subcs    r3, r3, r1
    1084:    21800002     orrcs    r0, r0, r2
    1088:    e15300a1     cmp    r3, r1, lsr #1
    108c:    204330a1     subcs    r3, r3, r1, lsr #1
    1090:    218000a2     orrcs    r0, r0, r2, lsr #1
    1094:    e1530121     cmp    r3, r1, lsr #2
    1098:    20433121     subcs    r3, r3, r1, lsr #2
    109c:    21800122     orrcs    r0, r0, r2, lsr #2
    10a0:    e15301a1     cmp    r3, r1, lsr #3
    10a4:    204331a1     subcs    r3, r3, r1, lsr #3
    10a8:    218001a2     orrcs    r0, r0, r2, lsr #3
    10ac:    e3530000     cmp    r3, #0
    10b0:    11b02222     lsrsne    r2, r2, #4
    10b4:    11a01221     lsrne    r1, r1, #4
    10b8:    1affffef     bne    107c <__aeabi_idiv+0x4c>
    10bc:    e35c0000     cmp    ip, #0
    10c0:    42600000     rsbmi    r0, r0, #0
    10c4:    e12fff1e     bx    lr
    10c8:    e13c0000     teq    ip, r0
    10cc:    42600000     rsbmi    r0, r0, #0
    10d0:    e12fff1e     bx    lr
    10d4:    33a00000     movcc    r0, #0
    10d8:    01a00fcc     asreq    r0, ip, #31
    10dc:    03800001     orreq    r0, r0, #1
    10e0:    e12fff1e     bx    lr
    10e4:    e16f2f11     clz    r2, r1
    10e8:    e262201f     rsb    r2, r2, #31
    10ec:    e35c0000     cmp    ip, #0
    10f0:    e1a00233     lsr    r0, r3, r2
    10f4:    42600000     rsbmi    r0, r0, #0
    10f8:    e12fff1e     bx    lr
    10fc:    e3500000     cmp    r0, #0
    1100:    c3e00102     mvngt    r0, #-2147483648    ; 0x80000000
    1104:    b3a00102     movlt    r0, #-2147483648    ; 0x80000000
    1108:    ea000007     b    112c <__aeabi_idiv0>

0000110c <__aeabi_idivmod>:
    110c:    e3510000     cmp    r1, #0
    1110:    0afffff9     beq    10fc <__aeabi_idiv+0xcc>
    1114:    e92d4003     push    {r0, r1, lr}
    1118:    ebffffc6     bl    1038 <__aeabi_idiv+0x8>
    111c:    e8bd4006     pop    {r1, r2, lr}
    1120:    e0030092     mul    r3, r2, r0
    1124:    e0411003     sub    r1, r1, r3
    1128:    e12fff1e     bx    lr

传统armeabi的整数运算

加减乘还是没有问题:adds,subs,muls,改状态位。

00001248 <_Z3addll>:
    1248:    1840          adds    r0, r0, r1
    124a:    4770          bx    lr

0000124c <_Z3subll>:
    124c:    1a40          subs    r0, r0, r1
    124e:    4770          bx    lr

00001250 <_Z3mulll>:
    1250:    4348          muls    r0, r1
    1252:    4770          bx    lr

除法和取模也是调函数:

00001254 <_Z3divll>:
    1254:    b508          push    {r3, lr}
    1256:    f001 ff47     bl    30e8 <_Unwind_GetTextRelBase+0x8>
    125a:    bd08          pop    {r3, pc}

0000125c <_Z3modll>:
    125c:    b508          push    {r3, lr}
    125e:    f001 ff4b     bl    30f8 <_Unwind_GetTextRelBase+0x18>
    1262:    1c08          adds    r0, r1, #0
    1264:    bd08          pop    {r3, pc}

OAT编译出来的结果

Dalvik和ARM都学完之后,我们就可以看看Dalvik翻成OAT之后的结果是什么样子的了。

先看个加法的吧:

    CODE: (code_offset=0x0050270c size_offset=0x00502708 size=76)...
      0x0050270c: d1400bf0    sub x16, sp, #0x2000 (8192)
      0x00502710: b940021f    ldr wzr, [x16]
      suspend point dex PC: 0x0000
      0x00502714: f81e0fe0    str x0, [sp, #-32]!

复习一下,还是先备份参数到栈里:lr到sp+24,第一个参数到sp+40,第二个参数到sp+48。
然后判断是不是suspend。

      0x00502718: f9000ffe    str lr, [sp, #24]
      0x0050271c: f90017e1    str x1, [sp, #40]
      0x00502720: f9001be2    str x2, [sp, #48]
      0x00502724: 79400250    ldrh w16, [tr](state_and_flags)
      0x00502728: 35000130    cbnz w16, #+0x24 (addr 0x50274c)

开始干活了,将那两个参数从sp+40和sp+48里面读回来,到x0和x1中。
然后算加法,结果在x2中。
x2值再送到栈里,再从栈里读回来到x0,最后返回。

      0x0050272c: f94017e0    ldr x0, [sp, #40]
      0x00502730: f9401be1    ldr x1, [sp, #48]
      0x00502734: 8b010002    add x2, x0, x1
      0x00502738: f800c3e2    stur x2, [sp, #12]
      0x0050273c: f840c3e0    ldur x0, [sp, #12]
      0x00502740: f9400ffe    ldr lr, [sp, #24]
      0x00502744: 910083ff    add sp, sp, #0x20 (32)
      0x00502748: d65f03c0    ret
      0x0050274c: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x00502750: d63f03c0    blr lr
      suspend point dex PC: 0x0000
      0x00502754: 17fffff6    b #-0x28 (addr 0x50272c)

减法和乘法也是类似,我们直接看除法:
这时候64位的好处又体现出来了,不用调函数,直接有指令:
Dalvik代码是这样的:

  3: long com.yunos.xulun.testcppjni2.TestART.div(long, long) (dex_method_idx=16780)
    DEX CODE:
      0x0000: 9e00 0204                    | div-long v0, v2, v4
      0x0002: 1000                         | return-wide v0

OAT代码,sdiv就搞定了。跟前面我们看到的C代码的结果,吻合得非常好。

    CODE: (code_offset=0x0050280c size_offset=0x00502808 size=96)...
      0x0050280c: d1400bf0    sub x16, sp, #0x2000 (8192)
      0x00502810: b940021f    ldr wzr, [x16]
      suspend point dex PC: 0x0000
      0x00502814: f81d0fe0    str x0, [sp, #-48]!
      0x00502818: f90017fe    str lr, [sp, #40]
      0x0050281c: f9001fe1    str x1, [sp, #56]
      0x00502820: f90023e2    str x2, [sp, #64]
      0x00502824: 79400250    ldrh w16, [tr](state_and_flags)
      0x00502828: 35000190    cbnz w16, #+0x30 (addr 0x502858)

先从sp+64中把除数读进来。
Java先做一件事情,判断除数是不是0。如果除数是0,则cbz会跳转到执行pThrowDivZero去抛出一个除0异常出来。

      0x0050282c: f94023e0    ldr x0, [sp, #64]
      0x00502830: b40001a0    cbz x0, #+0x34 (addr 0x502864)

判断是否为0之后,还是把x0存到栈里。
再把被除数和除数都从栈里读出来。

      0x00502834: f80143e0    stur x0, [sp, #20]
      0x00502838: f9401fe0    ldr x0, [sp, #56]
      0x0050283c: f84143e1    ldur x1, [sp, #20]

开始做除法,结果在x2中,然后存栈里面。再从栈里读回来到x0里,返回。

      0x00502840: 9ac10c02    sdiv x2, x0, x1
      0x00502844: f801c3e2    stur x2, [sp, #28]
      0x00502848: f841c3e0    ldur x0, [sp, #28]
      0x0050284c: f94017fe    ldr lr, [sp, #40]
      0x00502850: 9100c3ff    add sp, sp, #0x30 (48)
      0x00502854: d65f03c0    ret
      0x00502858: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x0050285c: d63f03c0    blr lr
      suspend point dex PC: 0x0000
      0x00502860: 17fffff3    b #-0x34 (addr 0x50282c)
      0x00502864: f9422a5e    ldr lr, [tr, #1104](pThrowDivZero)
      0x00502868: d63f03c0    blr lr
      suspend point dex PC: 0x0000

浮点运算

Java浮点运算

Java真是门好语言啊,JVM已经封装了所有跟浮点相关的细节,基本上从字节码上看,跟长整型只有细节的不同。

    public static double dadd(double a,double b){
        return a+b;
    }

    public static double dsub(double a,double b){
        return a-b;
    }

    public static double dmul(double a,double b){
        return a*b;
    }

    public static double ddiv(double a,double b){
        return a/b;
    }

字节码如下:

  public static double dadd(double, double);
    Code:
       0: dload_0
       1: dload_2
       2: dadd
       3: dreturn

  public static double dsub(double, double);
    Code:
       0: dload_0
       1: dload_2
       2: dsub
       3: dreturn

  public static double dmul(double, double);
    Code:
       0: dload_0
       1: dload_2
       2: dmul
       3: dreturn

  public static double ddiv(double, double);
    Code:
       0: dload_0
       1: dload_2
       2: ddiv
       3: dreturn

基本上就是将l换成d,其它没有什么变化。

ARM浮点运算

强大的ARM v8A芯片,已经不输于JVM的设计了,也是很简单。
源代码:

double dadd(double a,double b){
    return a+b;
}

double dsub(double a,double b){
    return a-b;
}

double dmul(double a,double b){
    return a*b;
}

double ddiv(double a,double b){
    return a/b;
}

ARM v8a的浮点运算

汇编代码:

0000000000000760 <_Z4dadddd>:
 760:    1e612800     fadd    d0, d0, d1
 764:    d65f03c0     ret

0000000000000768 <_Z4dsubdd>:
 768:    1e613800     fsub    d0, d0, d1
 76c:    d65f03c0     ret

0000000000000770 <_Z4dmuldd>:
 770:    1e610800     fmul    d0, d0, d1
 774:    d65f03c0     ret

0000000000000778 <_Z4ddivdd>:
 778:    1e611800     fdiv    d0, d0, d1
 77c:    d65f03c0     ret

我们可以看到,寄存器已经不是x开头的通用寄存器了,而变成了d开头的NEON寄存器。我们实际上是借用了ARM v7a才出现的NEON指令才使得指令变得这么简单。

ARM v7a的浮点运算:

同样是NEON指令,但是v7a的就比v8a的看起来要复杂一点。不过倒更清晰地反映了逻辑事实。
v7a的NEON指令需要用vmov将通用寄存器中的数传送到NEON寄存器中,然后再进行计算。结果再通过vmov送回到通用寄存器中。

00000fde <_Z4dadddd>:
     fde:    ec41 0b17     vmov    d7, r0, r1
     fe2:    ec43 2b16     vmov    d6, r2, r3
     fe6:    ee37 7b06     vadd.f64    d7, d7, d6
     fea:    ec51 0b17     vmov    r0, r1, d7
     fee:    4770          bx    lr

00000ff0 <_Z4dsubdd>:
     ff0:    ec41 0b17     vmov    d7, r0, r1
     ff4:    ec43 2b16     vmov    d6, r2, r3
     ff8:    ee37 7b46     vsub.f64    d7, d7, d6
     ffc:    ec51 0b17     vmov    r0, r1, d7
    1000:    4770          bx    lr

00001002 <_Z4dmuldd>:
    1002:    ec41 0b17     vmov    d7, r0, r1
    1006:    ec43 2b16     vmov    d6, r2, r3
    100a:    ee27 7b06     vmul.f64    d7, d7, d6
    100e:    ec51 0b17     vmov    r0, r1, d7
    1012:    4770          bx    lr

00001014 <_Z4ddivdd>:
    1014:    ec41 0b17     vmov    d7, r0, r1
    1018:    ec43 2b16     vmov    d6, r2, r3
    101c:    ee87 7b06     vdiv.f64    d7, d7, d6
    1020:    ec51 0b17     vmov    r0, r1, d7
    1024:    4770          bx    lr

传统ARM的浮点运算

没啥说的,都得函数实现了:

00001248 <_Z3addll>:
    1248:    1840          adds    r0, r0, r1
    124a:    4770          bx    lr

0000124c <_Z3subll>:
    124c:    1a40          subs    r0, r0, r1
    124e:    4770          bx    lr

00001250 <_Z3mulll>:
    1250:    4348          muls    r0, r1
    1252:    4770          bx    lr

00001254 <_Z3divll>:
    1254:    b508          push    {r3, lr}
    1256:    f001 ff47     bl    30e8 <_Unwind_GetTextRelBase+0x8>
    125a:    bd08          pop    {r3, pc}

0000125c <_Z3modll>:
    125c:    b508          push    {r3, lr}
    125e:    f001 ff4b     bl    30f8 <_Unwind_GetTextRelBase+0x18>
    1262:    1c08          adds    r0, r1, #0
    1264:    bd08          pop    {r3, pc}

x86芯片的运算指令

几种ARM下的RISC指令集的结果,我们都分析过了。下面我们看看32位的x86芯片上的整数和浮点运算吧。

x86的整数运算

32位的标志是使用32位的寄存器,比如eax,esp,esi。而64位下就是rax等等了。

00005f0 <_Z3addll>:
 5f0:    8b 44 24 08              mov    0x8(%esp),%eax
 5f4:    03 44 24 04              add    0x4(%esp),%eax
 5f8:    c3                       ret    
 5f9:    8d b4 26 00 00 00 00     lea    0x0(%esi,%eiz,1),%esi

00000600 <_Z3subll>:
 600:    8b 44 24 04              mov    0x4(%esp),%eax
 604:    2b 44 24 08              sub    0x8(%esp),%eax
 608:    c3                       ret    
 609:    8d b4 26 00 00 00 00     lea    0x0(%esi,%eiz,1),%esi

00000610 <_Z3mulll>:
 610:    8b 44 24 08              mov    0x8(%esp),%eax
 614:    0f af 44 24 04           imul   0x4(%esp),%eax
 619:    c3                       ret    
 61a:    8d b6 00 00 00 00        lea    0x0(%esi),%esi

00000620 <_Z3divll>:
 620:    8b 44 24 04              mov    0x4(%esp),%eax
 624:    89 c2                    mov    %eax,%edx
 626:    c1 fa 1f                 sar    $0x1f,%edx
 629:    f7 7c 24 08              idivl  0x8(%esp)
 62d:    c3                       ret    
 62e:    66 90                    xchg   %ax,%ax

00000630 <_Z3modll>:
 630:    8b 44 24 04              mov    0x4(%esp),%eax
 634:    89 c2                    mov    %eax,%edx
 636:    c1 fa 1f                 sar    $0x1f,%edx
 639:    f7 7c 24 08              idivl  0x8(%esp)
 63d:    89 d0                    mov    %edx,%eax
 63f:    c3                       ret    

x86的CISC的好处是总不至于要调一段复杂的函数来实现除法。

x86_64的整数运算

我们看看64位的rn寄存器出场之后的x86_64的整型指令吧:

00000000000006e0 <_Z3addll>:
 6e0:    48 8d 04 37              lea    (%rdi,%rsi,1),%rax
 6e4:    c3                       retq   
 6e5:    66 66 2e 0f 1f 84 00     data16 nopw %cs:0x0(%rax,%rax,1)
 6ec:    00 00 00 00 

00000000000006f0 <_Z3subll>:
 6f0:    48 89 f8                 mov    %rdi,%rax
 6f3:    48 29 f0                 sub    %rsi,%rax
 6f6:    c3                       retq   
 6f7:    66 0f 1f 84 00 00 00     nopw   0x0(%rax,%rax,1)
 6fe:    00 00 

0000000000000700 <_Z3mulll>:
 700:    48 89 f8                 mov    %rdi,%rax
 703:    48 0f af c6              imul   %rsi,%rax
 707:    c3                       retq   
 708:    0f 1f 84 00 00 00 00     nopl   0x0(%rax,%rax,1)
 70f:    00 

0000000000000710 <_Z3divll>:
 710:    48 89 fa                 mov    %rdi,%rdx
 713:    48 89 f8                 mov    %rdi,%rax
 716:    48 c1 fa 3f              sar    $0x3f,%rdx
 71a:    48 f7 fe                 idiv   %rsi
 71d:    c3                       retq   
 71e:    66 90                    xchg   %ax,%ax

0000000000000720 <_Z3modll>:
 720:    48 89 fa                 mov    %rdi,%rdx
 723:    48 89 f8                 mov    %rdi,%rax
 726:    48 c1 fa 3f              sar    $0x3f,%rdx
 72a:    48 f7 fe                 idiv   %rsi
 72d:    48 89 d0                 mov    %rdx,%rax
 730:    c3                       retq   
 731:    66 66 66 66 66 66 2e     data16 data16 data16 data16 data16 nopw %cs:0x0(%rax,%rax,1)
 738:    0f 1f 84 00 00 00 00 
 73f:    00 

x86的符点运算

从30多年前的80486开始,x86芯片就自带FPU,不再需要8087或者80387这样的专用FPU。1998年,AMD在k6-2处理器中使用的3D Now!指令集开创了SIMD与浮点数的结合。1999年,Intel也随之推出了支持单精度浮点的SSE指令。后来一直发展到SSE 4.2.
以我们的双精度计算的例子为例,这使用到了2000年发布的Pentium 4才引入的SSE2指令集。movsd,addsd,divsd等这些指令都是SSE2指令。

00000640 <_Z4dadddd>:
 640:    8d 64 24 f4              lea    -0xc(%esp),%esp
 644:    f2 0f 10 44 24 18        movsd  0x18(%esp),%xmm0
 64a:    f2 0f 58 44 24 10        addsd  0x10(%esp),%xmm0
 650:    f2 0f 11 04 24           movsd  %xmm0,(%esp)
 655:    dd 04 24                 fldl   (%esp)
 658:    8d 64 24 0c              lea    0xc(%esp),%esp
 65c:    c3                       ret    
 65d:    8d 76 00                 lea    0x0(%esi),%esi

00000660 <_Z4dsubdd>:
 660:    8d 64 24 f4              lea    -0xc(%esp),%esp
 664:    f2 0f 10 44 24 10        movsd  0x10(%esp),%xmm0
 66a:    f2 0f 5c 44 24 18        subsd  0x18(%esp),%xmm0
 670:    f2 0f 11 04 24           movsd  %xmm0,(%esp)
 675:    dd 04 24                 fldl   (%esp)
 678:    8d 64 24 0c              lea    0xc(%esp),%esp
 67c:    c3                       ret    
 67d:    8d 76 00                 lea    0x0(%esi),%esi

00000680 <_Z4dmuldd>:
 680:    8d 64 24 f4              lea    -0xc(%esp),%esp
 684:    f2 0f 10 44 24 18        movsd  0x18(%esp),%xmm0
 68a:    f2 0f 59 44 24 10        mulsd  0x10(%esp),%xmm0
 690:    f2 0f 11 04 24           movsd  %xmm0,(%esp)
 695:    dd 04 24                 fldl   (%esp)
 698:    8d 64 24 0c              lea    0xc(%esp),%esp
 69c:    c3                       ret    
 69d:    8d 76 00                 lea    0x0(%esi),%esi

000006a0 <_Z4ddivdd>:
 6a0:    8d 64 24 f4              lea    -0xc(%esp),%esp
 6a4:    f2 0f 10 44 24 10        movsd  0x10(%esp),%xmm0
 6aa:    f2 0f 5e 44 24 18        divsd  0x18(%esp),%xmm0
 6b0:    f2 0f 11 04 24           movsd  %xmm0,(%esp)
 6b5:    dd 04 24                 fldl   (%esp)
 6b8:    8d 64 24 0c              lea    0xc(%esp),%esp
 6bc:    c3                       ret    
 6bd:    8d 76 00                 lea    0x0(%esi),%esi

x86_64的浮点运算

与arm64有异曲同工之妙,不再需要进出xmm的时候做movsd了,retq指令可以直接从xmm寄存器中返回数据。

0000000000000740 <_Z4dadddd>:
 740:    f2 0f 58 c1              addsd  %xmm1,%xmm0
 744:    c3                       retq   
 745:    66 66 2e 0f 1f 84 00     data16 nopw %cs:0x0(%rax,%rax,1)
 74c:    00 00 00 00 

0000000000000750 <_Z4dsubdd>:
 750:    f2 0f 5c c1              subsd  %xmm1,%xmm0
 754:    c3                       retq   
 755:    66 66 2e 0f 1f 84 00     data16 nopw %cs:0x0(%rax,%rax,1)
 75c:    00 00 00 00 

0000000000000760 <_Z4dmuldd>:
 760:    f2 0f 59 c1              mulsd  %xmm1,%xmm0
 764:    c3                       retq   
 765:    66 66 2e 0f 1f 84 00     data16 nopw %cs:0x0(%rax,%rax,1)
 76c:    00 00 00 00 

0000000000000770 <_Z4ddivdd>:
 770:    f2 0f 5e c1              divsd  %xmm1,%xmm0
 774:    c3                       retq   
 775:    66 66 2e 0f 1f 84 00     data16 nopw %cs:0x0(%rax,%rax,1)
 77c:    00 00 00 00 

MIPS指令集下的计算指令

最后我们看下可能多数同学们都不熟悉的MIPS指令集吧,其实还是很清爽的:

000006b0 <_Z3addll>:
 6b0:    03e00008     jr    ra
 6b4:    00851021     addu    v0,a0,a1

000006b8 <_Z3subll>:
 6b8:    03e00008     jr    ra
 6bc:    00851023     subu    v0,a0,a1

000006c0 <_Z3mulll>:
 6c0:    03e00008     jr    ra
 6c4:    70851002     mul    v0,a0,a1

000006c8 <_Z3divll>:
 6c8:    0085001a     div    zero,a0,a1
 6cc:    00a001f4     teq    a1,zero,0x7
 6d0:    03e00008     jr    ra
 6d4:    00001012     mflo    v0

000006d8 <_Z3modll>:
 6d8:    0085001a     div    zero,a0,a1
 6dc:    00a001f4     teq    a1,zero,0x7
 6e0:    03e00008     jr    ra
 6e4:    00001010     mfhi    v0

000006e8 <_Z4dadddd>:
 6e8:    03e00008     jr    ra
 6ec:    462e6000     add.d    $f0,$f12,$f14

000006f0 <_Z4dsubdd>:
 6f0:    03e00008     jr    ra
 6f4:    462e6001     sub.d    $f0,$f12,$f14

000006f8 <_Z4dmuldd>:
 6f8:    03e00008     jr    ra
 6fc:    462e6002     mul.d    $f0,$f12,$f14

00000700 <_Z4ddivdd>:
 700:    03e00008     jr    ra
 704:    462e6003     div.d    $f0,$f12,$f14

mips64位与上面也很相似,整型指令基本是在指令前多个d。浮点除了寄存器变长了,也没什么大的变化。

0000000000000b80 <_Z3addll>:
 b80:    03e00009     jr    ra
 b84:    0085102d     daddu    v0,a0,a1

0000000000000b88 <_Z3subll>:
 b88:    03e00009     jr    ra
 b8c:    0085102f     dsubu    v0,a0,a1

0000000000000b90 <_Z3mulll>:
 b90:    03e00009     jr    ra
 b94:    0085109c     dmul    v0,a0,a1

0000000000000b98 <_Z3divll>:
 b98:    0085109e     ddiv    v0,a0,a1
 b9c:    00a001f4     teq    a1,zero,0x7
 ba0:    d81f0000     jrc    ra
 ba4:    00000000     nop

0000000000000ba8 <_Z3modll>:
 ba8:    008510de     dmod    v0,a0,a1
 bac:    00a001f4     teq    a1,zero,0x7
 bb0:    d81f0000     jrc    ra
 bb4:    00000000     nop

0000000000000bb8 <_Z4dadddd>:
 bb8:    03e00009     jr    ra
 bbc:    462d6000     add.d    $f0,$f12,$f13

0000000000000bc0 <_Z4dsubdd>:
 bc0:    03e00009     jr    ra
 bc4:    462d6001     sub.d    $f0,$f12,$f13

0000000000000bc8 <_Z4dmuldd>:
 bc8:    03e00009     jr    ra
 bcc:    462d6002     mul.d    $f0,$f12,$f13

0000000000000bd0 <_Z4ddivdd>:
 bd0:    03e00009     jr    ra
 bd4:    462d6003     div.d    $f0,$f12,$f13

编译出的OAT的浮点运算

节省篇幅,我们先看个dadd的. Dalvik指令很简单,就是一条add-double.

  3: double com.yunos.xulun.testcppjni2.TestART.dadd(double, double) (dex_method_idx=16780)
    DEX CODE:
      0x0000: ab00 0204                    | add-double v0, v2, v4
      0x0002: 1000                         | return-wide v0

跟C++编译出来的一样,OAT生成的arm64指令也是用fadd来实现的,同样没有vmov什么事儿。

    CODE: (code_offset=0x0050280c size_offset=0x00502808 size=76)...
      0x0050280c: d1400bf0    sub x16, sp, #0x2000 (8192)
      0x00502810: b940021f    ldr wzr, [x16]
      suspend point dex PC: 0x0000
      0x00502814: f81e0fe0    str x0, [sp, #-32]!
      0x00502818: f9000ffe    str lr, [sp, #24]
      0x0050281c: fd0017e0    str d0, [sp, #40]
      0x00502820: fd001be1    str d1, [sp, #48]
      0x00502824: 79400250    ldrh w16, [tr](state_and_flags)
      0x00502828: 35000130    cbnz w16, #+0x24 (addr 0x50284c)

存到栈里的两个加数,用ldr可以直接装载到NEON寄存器里。然后直接调用fadd去计算。

      0x0050282c: fd4017e0    ldr d0, [sp, #40]
      0x00502830: fd401be1    ldr d1, [sp, #48]
      0x00502834: 1e612802    fadd d2, d0, d1
      0x00502838: fc00c3e2    stur d2, [sp, #12]

双精度浮点数的返回值并不在通用寄存器x0中,而是直接在NEON寄存器d0中。

      0x0050283c: fc40c3e0    ldur d0, [sp, #12]
      0x00502840: f9400ffe    ldr lr, [sp, #24]
      0x00502844: 910083ff    add sp, sp, #0x20 (32)
      0x00502848: d65f03c0    ret
      0x0050284c: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x00502850: d63f03c0    blr lr
      suspend point dex PC: 0x0000
      0x00502854: 17fffff6    b #-0x28 (addr 0x50282c)

然后我们再看除法,Dalvik是div-double。

  4: double com.yunos.xulun.testcppjni2.TestART.ddiv(double, double) (dex_method_idx=16781)
    DEX CODE:
      0x0000: ae00 0204                    | div-double v0, v2, v4
      0x0002: 1000                         | return-wide v0

翻译出来的效果也真不错,真接使用NEON寄存器的fdiv。

    CODE: (code_offset=0x0050287c size_offset=0x00502878 size=76)...
      0x0050287c: d1400bf0    sub x16, sp, #0x2000 (8192)
      0x00502880: b940021f    ldr wzr, [x16]
      suspend point dex PC: 0x0000
      0x00502884: f81e0fe0    str x0, [sp, #-32]!
      0x00502888: f9000ffe    str lr, [sp, #24]
      0x0050288c: fd0017e0    str d0, [sp, #40]
      0x00502890: fd001be1    str d1, [sp, #48]
      0x00502894: 79400250    ldrh w16, [tr](state_and_flags)
      0x00502898: 35000130    cbnz w16, #+0x24 (addr 0x5028bc)
      0x0050289c: fd4017e0    ldr d0, [sp, #40]
      0x005028a0: fd401be1    ldr d1, [sp, #48]

除了fadd换成了fdiv,其余跟加法没有区别。

      0x005028a4: 1e611802    fdiv d2, d0, d1
      0x005028a8: fc00c3e2    stur d2, [sp, #12]
      0x005028ac: fc40c3e0    ldur d0, [sp, #12]
      0x005028b0: f9400ffe    ldr lr, [sp, #24]
      0x005028b4: 910083ff    add sp, sp, #0x20 (32)
      0x005028b8: d65f03c0    ret
      0x005028bc: f9421e5e    ldr lr, [tr, #1080](pTestSuspend)
      0x005028c0: d63f03c0    blr lr
      suspend point dex PC: 0x0000
      0x005028c4: 17fffff6    b #-0x28 (addr 0x50289c)
目录
相关文章
|
3月前
|
存储 移动开发 JavaScript
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(1)
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(1)
49 0
|
3月前
|
存储 JavaScript 前端开发
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(3)
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(3)
67 0
|
3月前
|
存储 监控 JavaScript
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(2)
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(2)
41 1
|
3月前
|
存储 自然语言处理 JavaScript
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(4)
NUS CS1101S:SICP JavaScript 描述:五、使用寄存器机进行计算(4)
48 0
|
11月前
|
机器学习/深度学习 人工智能 自然语言处理
ChatGPT背后的指令学习是什么?PSU发布首篇「指令学习」全面综述
ChatGPT背后的指令学习是什么?PSU发布首篇「指令学习」全面综述
137 0
|
C语言 Perl
西门子S7-200 SMART编程实例,整数运算指令如何使用?
本节我们来学习西门子S7-200 SMART整数运算指令,并通过一个实例还讲解整数运算指令如何使用。S7-200 SMART的整数运算指令主要包括加、减、乘、除运算指令和递增、递减运算指令。
西门子S7-200 SMART编程实例,整数运算指令如何使用?
|
存储 Java Go
自制操作系统番外:编程语言中变量是如何存储的
在之前写操作系统的过程中,我们初步接触了一些寄存器和内存的基本概念,这篇将结合这些知识重新认识下C和Go中的变量的存储
|
机器学习/深度学习 算法 数据可视化
使用简单算法两小时实现猎杀乌姆帕斯(Hunt the Wumpus)Python小游戏
使用Python语言可视化实现1973 年开发的一款基于文本的冒险游戏。 原著较为复杂,这里我们作出如下简化: 1. 原著十二面体可以展开为拥有20个顶点(洞穴)的地图。我们简化为更简单的 N*N 矩形地图。每个点代表一个洞穴。暂定为 5 x 5 共25个洞穴。 2. 每个洞穴(点)上下左右四通(非八达)🤪 3. 仅拥有一颗箭,即只有一次命中机会。如果错过未能将怪兽消灭即失败。 4. 如果不慎跌入怪兽洞也算失败。😏 5. 该地图不会出现 无底洞, 超级蝙蝠 等其它元素。😝 6. 隐藏信息,怪兽的洞穴是不可见的,当接近怪兽洞穴一个格子的距离时,会提示“你嗅到了乌姆帕斯的气息”。
200 0
使用简单算法两小时实现猎杀乌姆帕斯(Hunt the Wumpus)Python小游戏
|
测试技术 Windows
Windows程序设计——CreateFont详细解释
Windows程序设计——CreateFont详细解释
389 0
|
存储 安全 程序员
代码还原的技术 ARM汇编入门教程(二) 加减乘除
代码还原的技术 ARM汇编入门教程(二) 加减乘除
代码还原的技术 ARM汇编入门教程(二) 加减乘除