用于 Power 体系结构的汇编语言,第 4 部分: 函数调用和 PowerPC 64 位 ABI

创建可以与其他程序共享的函数

ABI,或称为应用程序二进制接口,是一组允许使用不同语言编写的程序或使用不同编译器链接的程序相互调用彼此的函数的约定集。本文是 4 部分系列文章 的最后一部分,讨论了用于 64 位 ELF (类 UNIX)系统上的 PowerPC® ABI;不管您是否使用汇编语言编写程序,它们都可以帮助您为 POWER5™ 和其他基于 PowerPC 的处理器更加有效地编写 64 位应用程序。32 位的 ABI 也是存在的,但在本文中并没有介绍。

Jonathan Bartlett (johnnyb@eskimo.com), 技术总监, New Medio

Jonathan Bartlett 是 Programming from the Ground Up 一书的作者,这本书对使用 Linux 汇编语言的编程进行了简介。他是 New Medio 的开发技术总监,为客户开发 Web 应用程序、视频应用程序、kiosk 应用程序和桌面应用程序。



2007 年 4 月 26 日

简化的 ABI

关于这个“用于 Power 体系结构的汇编语言”系列

POWER5 处理器是一个在各种设置中广泛使用的 64 位系统。这个 4 部分的系列文章对汇编语言进行了概要介绍,并针对 POWER5 上的汇编语言编程专门进行了介绍:

本系列的上一篇 “使用 PowerPC 分支处理器进行编程”对 “简化”ABI 简要进行了讨论。使用它可以花费最少的努力就能编写出符合特定标准的函数。函数要想使用简化 ABI 必须满足以下标准:

  • 不能调用其他函数。
  • 只能修改寄存器 3 到 12(不过有一些例外,请参看下面的 非易失性寄存器保存区)。
  • 只能修改寄存器的以下域:cr0cr1cr5cr6cr7

如果您的代码还使用了 PowerPC 向量处理扩展,那就还会有几个其他限制,不过这已经超出了本文的范围。

有趣的是,在使用简化 ABI 时,您并不需要以任何方式进行声明,因为它是常用 ABI 的一个完全兼容的子集,用于不需要堆栈帧 的函数,这将在下一节中详细讨论。

在使用 PowerPC ABI 语义调用一个函数时,它会使用寄存器将自己的参数传递给函数。寄存器 3 里面保存了第一个定点参数,寄存器 4 中保存的是第二个参数,依此类推,直到寄存器 10。同理,浮点值是通过浮点寄存器 1 到 13 传递的。当这个函数完成时,返回值会通过寄存器 3 返回,函数本身使用 blr 指令退出。

为了展示简化 PowerPC ABI 的用法,下面让我们来看这样一个函数:它接受一个参数,计算它的平方,然后返回结果。下面是使用汇编语言编写的这个函数(请将下面的代码输入到 my_square.s 文件中):

清单 1. 使用简化 ABI 计算一个数字平方的函数
###FUNCTION ENTRY POINT DECLARATION###
.section .opd, "aw"
.align 3

.global my_square
my_square:   #this is the name of the function as seen 
   .quad .my_square, .TOC.@tocbase, 0
#Tell the linker that this is a function reference
.type my_square, @function

###FUNCTION CODE HERE###
.text 
.my_square:  #This is the label for the code itself (referenced in the "opd")
   #Parameter 1 -- number to be squared -- in register 3

   #Multiply it by itself, and store it back into register 3
   mulld 3, 3, 3

   #The return value is now in register 3, so we just need to leave
   blr

之前,您一直使用 .opd 段来声明该程序的入口,不过现在它被用来声明一个函数。它们称为 正式过程描述符(official procedure descriptor),其中包含了链接器将不同共享对象中与位置无关的代码组合在一起所需要的信息。最重要的一个域是第一个域,它是过程的起点代码的地址。第二个域是这个函数使用的 TOC 指针。第三个域是语言的一个环境指针(如果该语言使用了环境指针的话),不过通常都会被设置为 0。注意只有全局导出的符号定义才是正式过程描述符。

这个函数的 C 语言原型如下所示:

清单 2. 计算数字平方的函数的 C 原型
typedef long long int64;
int64 my_square(int64 val);

下面是使用这个函数的 C 代码(请将下面的代码输入到 my_square_tester.c 文件中):

清单 3. 调用 my_square 函数的 C 代码
#include <stdio.h>

/* make declarations easier to write */
typedef long long int64; 

int64 my_square(int64);

int main() {
    int a = 32;
    printf("The square of %lld is %lld.\n", a, my_square(a));
    return 0;
}

编译并运行这段代码的简单方法如下所示:

清单 4. 编译并运行 my_square_tester
gcc -m64 my_square.s my_square_tester.c -o my_square_tester
./my_square_tester

-m64 标志告诉编译器使用 64 位指令,使用 64 位 ABI 和库进行编译,使用 64 位 ABI 进行链接。它然后会负责处理所有的链接问题(通常还有几个问题 —— 您可以在命令行后面加上 -v 来查看完整的链接命令)。

正如您可以看到的一样,使用简化的 PowerPC ABI 编写函数非常简单。当函数不满足这些标准时就会出现问题。


堆栈

现在让我们开始介绍 ABI 中更加复杂的部分。任何 ABI 中最重要的部分都是具体如何使用堆栈,即保存本地函数数据的内存区域。

对于堆栈的需要

了解为什么需要堆栈的最好方法是查看递归函数的情况。为了简单起见,让我们了解一下阶乘函数的递归实现:

清单 5. 阶乘函数
typedef long long int64;
int64 factorial(int64 num) {
      //BASE CASE    
      if (num == 0) {
         return 1;
      //RECURSIVE CASE
      } else {
         return num * factorial(num - 1);
      }
}

从概念上来讲,这很容易理解,不过让我们具体看一下它是如何工作的。此处到底做了什么?例如,如果我们要计算 4 的阶乘为多少,到底会发生什么呢?下面让我们来顺序看一下:

首先,这个函数会被调用,num 会设置为 4。然后,由于 num 大于 0,因此会再次调用 factorial,不过这次是计算 3 的阶乘了。现在,在新一次调用 factorial 时,num 被设置为 3。然而,尽管它们共享了相同的名字和代码,但它所引用的内存地址与前一次不同。尽管这是相同代码中的相同变量名,不过 num 这次不同了。这是由于每次调用一个函数时,都有一个相关活动记录(activation record) (也称为 堆栈帧)。活动记录包含了这个函数的所有与调用有关的数据,包括参数和本地变量。这就是递归函数是如何保证其他活动函数调用中的变量值不受影响的。每个调用都有自己的活动记录,因此每次被调用时,变量都会在活动记录中获得自己的存储空间。直到函数调用彻底完成 时,活动记录使用的空间才会被释放以便重用(更多信息请参看后面的介绍)。

因此,使用 3 作为 num 的值时,我们需要再次执行这个函数,然后使用 2、1、0 来逐一执行这个函数。然而,在使用 0 调用这个函数时,此函数就达到了自己的基线条件(base case)。基线条件就是函数终止对自身的调用并返回的条件。因此,使用 0 作为 num 时,就返回 1 作为结果。之前的函数会在这个函数退出时接收这个值(调用 factorial(0)),并使用结果 1 乘以 num 的值(也是 1)。然后将这个结果返回,并重新激活下一个等待的函数。这个函数用结果 1 乘以 num 的值(为 2),结果为 2,然后将这个结果返回。然后重新激活下一个等待的函数调用,使用前一次调用的结果乘以这个函数的 num 的值(为 3),结果是 6。这个结果返回给原始的函数,它的 num 值为 4。它与前一个结果的乘积为 24。

正如您可以看到的一样,每次一个函数调用另外一个函数时,在下一次发生调用时,它自己的值和状态都会被挂起。这对于所有的函数来说都是这样,而不仅仅是递归函数如此。如果这个函数又一次调用了其他函数,其状态也会被挂起。当一个函数返回时,调用它的函数就会被唤醒,然后从此处继续执行。因此,正如我们看到的一样,“活动”函数调用在其他函数调用之上被压入堆栈,然后在每个函数返回时从堆栈中删除。结果看起来如下所示(factorial 缩写为 fac):

  1. fac(4) [active]
  2. fac(4) [suspended], fac(3) [active]
  3. fac(4) [suspended], fac(3) [suspended], fac(2) [active]
  4. fac(4) [suspended], fac(3) [suspended], fac(2) [suspended], fac(1) [active]
  5. fac(4) [suspended], fac(3) [suspended], fac(2) [suspended], fac(1) [suspended], fac(0) [active]
  6. fac(4) [suspended], fac(3) [suspended], fac(2) [suspended], fac(1) [active]
  7. fac(4) [suspended], fac(3) [suspended], fac(2) [active]
  8. fac(4) [suspended], fac(3) [active]
  9. fac(4) [active]

正如您可以看到的一样,挂起的函数的活动记录“压入堆栈”,然后在每个函数返回时,就从堆栈中弹出。

堆栈布局

为了实现这种思想,每个程序都分配了一定的内存,称为程序堆栈(program stack)。所有的 PowerPC 程序都需要由一个指向寄存器 1 中的这个堆栈的指针启动。在 PowerPC ABI 中,寄存器 1 通常都是指向堆栈顶部。这对函数了解自己的活动记录在什么地方提供了方便 —— 它们可以使用堆栈指针的形式简单地进行定义。如果一个函数正在执行,那么堆栈指针就会指向整个堆栈的顶部,这也是该函数活动记录的顶部。由于活动记录是在堆栈上实现的,它们通常也会被称为堆栈帧,这两个术语是对等的。

现在,在使用 “栈顶”这个术语时,这通常都指的是概念上的说法。从物理上来说,堆栈是是向下伸展的,即从内存高地址向内存低地址伸展。因此,寄存器 1 会有一个指向堆栈的概念顶部的指针,它使用正偏移量引用的堆栈位置从概念上来说实际上是在堆栈顶部 之下,负的偏移量从概念上来说反而是在堆栈顶部之上。因此, 0(1) 引用的是概念上的栈顶,4(1) 引用的是栈顶之下 4 个字节的位置(概念上的),24(1) 从概念上来说位置更低,而 100(1) 又低一些。

现在您已经理解了堆栈在概念和物理上是如何组织的,接下来让我们了解一下各个堆栈帧里面到底保存了什么内容。下面是 64 位 PowerPC ABI 中规定的堆栈的布局,这是从物理内存的观点来说的(在给出堆栈偏移量时,它所引用的是内存中这个位置的 起点):

表 1. 堆栈帧布局
包含内容大小起始堆栈偏移量
浮点非易失性寄存器保存区可变的可变的
通用非易失性寄存器保存区可变的可变的
VRSAVE4 字节可变的
对齐补齐4 字节或 12 字节可变的
向量非易失性寄存器保存区可变的可变的(必须是按照 4 字对齐的)
本地变量存储可变的可变的
函数调用使用的参数可变的(至少 64 字节)48(1)
TOC 保存区840(1)
链接编辑器区832(1)
编译器区824(1)
链接寄存器保存区816(1)
条件寄存器保存区88(1)
指向前一个堆栈帧顶部的指针80(1)

我无意让您过分关注浮点、VRSAVE、Vector 或对齐空间的主题。这些主题涉及的是浮点和向量处理,已经超出了本文的范围。所有堆栈的值都必须是双字(8 个字节)对齐的,整个帧应该是 4 字(16 个字节)对齐的。所有的参数都是双字对齐的。

现在,让我们介绍一下堆栈祯中的每个部分都实现什么功能。

非易失性寄存器保存区

堆栈帧的第一个部分是非易失性寄存器保存区。PowerPC ABI 中的寄存器被划分成 3 种基本类型:专用寄存器、易失性寄存器和非易失性寄存器。专用寄存器 是那些有预定义的永久功能的寄存器,例如堆栈指针(寄存器 1)和 TOC 指针(寄存器 2)。寄存器 3 到 12 是易失性寄存器,这意味着任何函数都可以自由地对这些寄存器进行修改,而不用恢复这些寄存器之前的值。然而,这意味着一个函数在任何时候调用另外一个函数时,都应假设寄存器 3 到 12 均有可能被这个函数改写。

而寄存器 13 及其之上的寄存器都是非易失性寄存器。这意味着函数可以使用这些寄存器, 前提是从函数返回之前这些寄存器的值已被恢复。因此,在函数中使用非易失性寄存器之前,它的值必须保存到该函数的堆栈帧中,然后在函数返回之前恢复。类似地,函数也可以假设它给非易失性寄存器赋的值在调用其他函数时都不会被修改(至少会重新恢复)。函数可以根据需要使用这个保存区中任意大小的内存。

现在您可以看到为什么简化 ABI 之前的规则要求只使用寄存器 3 到寄存器 12:其他寄存器都是非易失性的,需要堆栈空间来保存这些寄存器的值。因此,为了使用其他寄存器,就必须将其保存到堆栈中。然而,ABI 实际上有一种方法能够解决这个限制。函数可以自由使用 288 字节的内存,对于不调用其他函数的函数来说,这段内存物理上在堆栈指针之下。因此,使用简化 ABI 的函数实际上可以通过从堆栈指针开始的负偏移量来保存、使用和恢复非易失性寄存器。

本地变量存储

本地变量存储区是用来保存函数专用数据的通用区域。通常这并不需要,因为在 PowerPC 体系结构中有大量寄存器可以使用。然而,这些空间通常可以用于本地数组。这个区域的大小可以按照函数需要而变。

函数调用的参数

函数参数与其他本地数据的处理稍有不同。PowerPC ABI 实际上会将函数参数使用的存储空间放入调用函数的堆栈空间 中。现在,正如您之前看到的一样,函数调用实际上是通过寄存器来传递参数的。然而,在需要保存值的情况下,依然需要为参数预留空间;尤其在需要使用易失性寄存器传递参数时更是如此。这个空间也用来在溢出情况中使用:如果参数个数多于可用寄存器的数目,那么它们就需要进入堆栈空间中。由于这个参数区是由从当前函数调用的所有函数共享的,因此当函数建立自己的堆栈空间时,就需要为在函数调用中使用的参数的最大个数来预留空间。

这样,函数就可以知道自己的参数在什么地方,参数是从内存底部向顶部来存储的。第一个参数在 48(1) 中,第二个参数在 56(1) 中。不管参数列表区域多大,被调用的函数总可以知道每个参数的精确偏移量。记住,参数列表区是针对函数的所有 调用定义的,因此可能会比任何单次函数调用需要的空间都大。

现在,由于传递给函数的参数的保存区实际上都位于调用函数的堆栈帧中,因此当函数建立自己的堆栈帧时,到参数列表的偏移量现在只能进行调整来适应函数自己的堆栈帧大小。让我们假设函数 func1 使用 3 个参数来调用 func2,并且 func2 有一个 112 字节的堆栈帧。如果 func2 希望访问自己第一个参数的内存,就可以使用 160(1) 来引用它,这是因为它要先经过自己的堆栈帧(112 字节),然后到达最后一个帧中的第一个参数(48 字节)。

幸运的是,函数很少需要访问自己的参数保存区,因为大部分参数都是通过寄存器传递的,而不会保存在参数保存区中。然而,即使 参数保存区没有保存任何内容,也需要为参数分配空间。函数必须假设对于自己的前 8 个参数,它们只会 通过寄存器传递,但是如果需要在程序中对参数进行存储,就仍然需要一个可用的保存区。这段空间也必须至少是 64 个字节。

TOC、链接编辑器和编译器区

TOC 保存区、编译器区和链接器区都会为系统使用而预留出来,程序员不能对它们进行修改,但是必须要为它们预留空间。

链接寄存器保存区

链接寄存器保存区与 ABI 的其他部分不同。当函数开始时,它实际上会将链接寄存器保存到调用函数的堆栈帧中,而不是自己的堆栈帧中,然后只有在需要时才会保存它。大部分调用其他函数的函数都会需要它。

条件寄存器保存区

如果条件寄存器的其他非易失性域被修改了,那就需要条件寄存器保存区。非易失性域有 cr2cr3cr4。在对任何域进行修改之前,都应该将条件寄存器保存到堆栈的这个区域中,然后在返回之前恢复。

指向前一堆栈帧

堆栈帧中的最后一个条目是一个指向前一堆栈帧的指针,通常被称为后向指针(back pointer)

编写使用堆栈的函数

函数在函数开始过程中(称为函数序言(function prologue))创建堆栈帧,并在函数结束时(称为函数尾声(function epilogue))销毁它。

函数的序言通常遵循以下顺序:

  1. 预留堆栈空间,并使用 stdu 1, -SIZE_OF_STACK(1)(其中 SIZE_OF_STACK 是这个函数堆栈帧的大小)保存原来的堆栈指针。这会保存原来的堆栈指针,并自动分配堆栈内存。
  2. 如果这个函数调用了另外一个函数,或者以任何方式使用了链接寄存器,就会由 mflr 0 指令进行保存,然后再存储进调用这个函数的函数的链接寄存器保存区(使用 std 0, SIZE_OF_STACK+16(1) 指令)。
  3. 保存这个函数中使用的所有非易失性寄存器(包括条件寄存器,如果使用了该寄存器的任何非易失性域的话)。

该函数的尾声则遵循相反的顺序,恢复已经保存的值,然后使用 ld 1, 0(1) 销毁堆栈帧,它会将前一个堆栈指针加载回堆栈指针寄存器中。

现在,让我们回到最初未使用堆栈实现的函数上来,了解一下使用了堆栈的情况是怎样的(请将下面的代码输入到 my_square.s 中,并按照以前的方法对其进行编译和运行):

清单 6. 使用堆栈计算数字平方的函数
###FUNCTION ENTRY POINT DECLARATION###
.section .opd, "aw"
.align 3

.global my_square
my_square:   #this is the name of the function as seen
        .quad .my_square, .TOC.@tocbase, 0
.type my_square, @function

###FUNCTION CODE HERE###
.text
.my_square:  #This is the label for the code itself (Referenced in the "opd")
        ##PROLOGUE##
        #Set up stack frame & back pointer (112 bytes -- minimum stack)
        stdu 1, -112(1)
        #Save LR (optional)
        mflr 0
        std 0, 128(1)
        #Save non-volatile registers (we don't have any)

        ##FUNCTION BODY##
   #Parameter 1 -- number to be squared -- in register 3
        mulld 3, 3, 3

   #The return value is now in register 3, so we just need to leave

        ##EPILOGUE##
        #Restore non-volatile registers (we don't have any)
        #Restore LR (not needed in this function, but here anyway)
        ld 0, 128(1)
        mtlr 0
        #Restore stack frame atomically
        ld 1, 0(1)
        #Return
      blr

这与之前的代码完全相同,不过增加了序言和尾声代码。正如前面介绍的一样,这段代码非常简单,并不需要序言和尾声代码,使用简化 ABI 就完全可以。不过它却是如何建立和销毁堆栈帧的一个很好的例子。

现在,让我们回到阶乘函数上来。这个函数,从它调用自己开始,就很好地使用了堆栈帧。让我们来看一下阶乘函数在汇编语言中是如何工作的(请将下面的代码输入到 factorial.s)中):

清单 7. 汇编语言中的阶乘函数
###ENTRY POINT###
.section .opd, "aw"
.align 3

.global factorial
factorial:
         .quad .factorial, .TOC.@tocbase, 0
.type factorial, @function

###CODE###
.text
.factorial:
        #Prologue
   #Reserve Space
   #48 (save areas) + 64 (parameter area) + 8 (local variable) = 120 bytes.
   #aligned to 16-byte boundary = 128 bytes
   stdu 1, -128(1) 
   #Save Link Register
   mflr 0
        std 0, 144(1)

   #Function body

   #Base Case? (register 3 == 0)
   cmpdi 3, 0
   bt- eq, return_one

   #Not base case - recursive call
   #Save local variable
   std 3, 112(1)
   #NOTE - it could also have been stored in the parameter save area.
   #       parameter 1 would have been at 176(1) 

   #Subtract One
   subi 3, 3, 1

   #Call the function (branch and set the link register to the return address)
   bl factorial
   #Linker word
   nop 

   #Restore local variable (but to a different register - 
   #register 3 is now the return value from the last factorial 
   #function)
   ld 4, 112(1)
   #Multiply by return value
   mulld 3, 3, 4
   #Result is in register 3, which is the return value register

factorial_return:
   #Epilogue
        #Restore Link Register
        ld 0, 144(1)
        mtlr 0
        #Restore stack
        ld 1, 0(1)
        #Return
      blr

return_one:
   #Set return value to 1
        li 3, 1
   #Return
        b factorial_return

要在 C 语言中对这段代码进行测试,请输入下面的代码(请将下面的代码输入到 factorial_caller.c 文件中):

清单 8. 调用阶乘函数的程序
#include <stdio.h>
typedef long long int64;
int64 factorial(int64);

int main() {
    int64 a = 10;
    printf("The factorial of %lld is %lld\n", factorial(a));
    return 0;
}

请按照下面的方式编译并运行这段代码:

清单 9. 编译并运行阶乘程序
gcc -m64 factorial.s factorial_caller.c -o factorial
./factorial

这个阶乘函数有几个非常有趣的地方。首先,我们使用了本地变量存储空间,另外,我们还会将当前参数保存到 112(1) 中。现在,由于这是一个函数参数,因此我们要保存另外一个双字堆栈空间,并将其保存到调用函数的参数区中。

这个程序中另外一个有趣之处是函数调用之后的 nop 指令。这是 ABI 所需要的。如果在链接过程中需要,这条额外指令会允许链接器插入其他代码。例如,如果有一个程序具有足够多的符号可供多个 TOC 使用(TOC 在 “第 2 部分:PowerPC 上加载和存储数据的艺术” 中进行了介绍),链接器就会发出一条指令(或使用分支的多条指令)来为您在多个 TOC 之间进行切换。

最后,请注意函数调用的分支目标并不是启动它的代码,而是 .opd 入点描述符。链接器会负责将它转换为指向正确的代码。然而,这可以让链接器知道有关函数的其他信息,包括它使用的是哪个 TOC,这样如果需要,就可以产生代码来在 TOC 之间进行切换了。


创建动态库

现在您已经知道如何创建函数了,接下来可以将它们一起放到一个库中。实际上您并不需要编写任何其他代码,只需要将它们编译在一起就可以了。要将 factorialmy_square 函数编译到一个库中(让我们将其称为 libmymath.so),只需要输入下面的内容:

清单 10. 编译共享库
gcc -m64 -shared factorial.s my_square.s -o libmymath.so

这会指示编译器生成一个名为 libmymath.so 的共享对象。要将其链接到可执行程序中,需要启用编译时链接器和运行时动态链接器来定位它。要编译这个阶乘调用函数来使用共享对象,可以按照下面的方式来编译和链接:

清单 11. 使用共享库
#-L tells what directories to search, -l tells what libraries to find
gcc -m64 factorial_caller.c -o factorial -L. -lmymath
#Tell the dynamic linker what additional directories to search
export LD_LIBRARY_PATH=.
#Run the program
./factorial

当然,如果这个库被安装到了一个标准的库位置,就不用使用这些目录标志了。

正如在 “第 2 部分:PowerPC 上加载和存储数据的艺术” 中介绍的一样,应用程序的 TOC(或目录表)只有 64KB 的空间来存储全局数据引用。因此,当几个共享对象都要加载到相同的应用程序空间并且目录表太大时又该如何呢?这就是 .TOC.@tocbase 引用对正式过程描述符的用处所在。链接器可以在单个应用程序中管理多个 TOC。 .TOC.@tocbase 会指示链接器将这个函数的 TOC 的地址放到这里。然后,当链接器设置对函数的引用时,就会将当前函数的 TOC 与所调用的函数的 TOC 进行比较。如果二者相同,就保留调用不变。如果二者不同,就修改代码以切换函数调用和函数返回上的 TOC 引用。这是采用正式过程描述符的主要原因之一,也是在函数调用之后要再加上一条 nop 指令的原因之一。由于这个原因,您永远都不用担心会由于链接了太多共享对象而导致全局符号空间用光的问题。


结束语

简化 64 位 ABI 只是程序中使用的一个很小部分,不过完整的 ABI 也并不会困难多少。最困难的部分是确定堆栈帧不同部分的不同偏移量,了解每个部分应该放到哪里,以及大小应该是多少。

使用汇编语言创建可重用的库非常迅速,也非常简单。要将使用 64 位 ABI 的函数转换到共享库中,您所需要的就是另外几个编译器标志,仅此而已。

希望本系列文章能够让您了解 PowerPC 编程是多么地简单,而功能又是多么地强大。在您的下一个项目中,您就可以考虑通过使用汇编语言来充分利用 POWER5 芯片所提供的全部资源。

参考资料

学习

获得产品和技术

  • 获得免费的 SEK for Linux,共包含两张 DVD,其中有用于 Linux 的最新 IBM 试用软件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
  • 利用可直接从 developerWorks 下载的 IBM 试用版软件 在 Linux 上构建您的下一个开发项目。

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux, Multicore acceleration
ArticleID=215819
ArticleTitle=用于 Power 体系结构的汇编语言,第 4 部分: 函数调用和 PowerPC 64 位 ABI
publish-date=04262007