使用 SSE(IA32 汇编)执行简单的算术运算

Posted

技术标签:

【中文标题】使用 SSE(IA32 汇编)执行简单的算术运算【英文标题】:Performing a simple arithmetic operation using SSE (IA32 assembly) 【发布时间】:2013-11-08 15:57:38 【问题描述】:

在我的大学里,我们刚刚被介绍给 IA32 SSE。我要做的是添加两个向量(他们称之为“打包值”,这意味着该向量包含四个 32 位单精度浮点数。一个向量的大小是 128 位。)这就是我正在尝试要做的事:

%xmm0      | 5.5 | 1.2 | 2.4 | 7.0 |
%xmm1      | 3.0 | 1.5 | 3.5 | 2.2 |
              |     |     |     |
              +     +     +     +
              |     |     |     |
              V     V     V     V
%xmm0      | 8.5 | 2.7 | 5.9 | 9.2 |

但是,在幻灯片上,他们只显示了以下代码 sn-p,我根本无法使用:

# %eax and %ebx contain the addresses of the two vectors that are to be added
movups (%eax), %xmm0
movups (%ebx), %xmm1
addps %xmm1, %xmm0
movups %xmm0, result

这提出了两个问题:

1.我什至如何首先创建这些向量以及如何使 %eax 和 %ebx 指向它们?

2。如何打印结果以检查操作是否成功?

这是我尝试过的。以下代码在我运行时编译并且不会崩溃。但是,根本没有输出...:/

.data
    x0: .float 7.0
    x1: .float 2.4
    x2: .float 1.2
    x3: .float 5.5
    y0: .float 2.2
    y1: .float 3.5
    y2: .float 1.5
    y3: .float 3.0
    result: .float 0
    intout: .string "Result: %f.\n"

.text
.global main

main:
    pushl x3
    pushl x2
    pushl x1
    pushl x0
    movl %esp, %eax
    pushl y3
    pushl y2
    pushl y1
    pushl y0
    movl %esp, %ebx

    movups (%eax), %xmm0
    movups (%ebx), %xmm1
    addps %xmm1, %xmm0
    movups %xmm0, result

    pushl result
    pushl $intout
    call printf
    addl $40, %esp
    movl $1, %eax
    int $0x80

【问题讨论】:

movups %xmm0, result 会将xmm0 的所有128 位写入result,但您已将result 声明为float(32 位),因此它会覆盖部分intout 字符串。 您的结果应该足够长以包含 4 个值,现在 movups %xmm0, result 也会丢弃您的字符串。 感谢您的快速回复!如何以 128 位大的方式声明 result 【参考方案1】:

printf%f 说明符表示 double 参数,而不是浮点参数。因此,您需要隐藏结果向量中的单浮点数并将它们移动到堆栈中。我就是这样做的:

.section ".rodata"
fmt:    .string "%f %f %f %f\n"
        .align 16
vec1:
        .float 7.0
        .float 2.4
        .float 1.2
        .float 5.5
vec2:
        .float 2.2
        .float 3.5
        .float 1.5
        .float 3.0    

.data
        .align 16
result:
        .float 0.0
        .float 0.0
        .float 0.0
        .float 0.0

        .text
.globl main
main:
        movl    %esp, %ebp

        andl    $-16, %esp      # align stack

        movaps  vec1, %xmm0
        movaps  vec2, %xmm1
        addps   %xmm1, %xmm0
        movaps  %xmm0, result

        subl    $36, %esp
        movl    $fmt, (%esp)
        movss   result, %xmm0
        cvtss2sd %xmm0, %xmm0
        movsd   %xmm0, 4(%esp)
        movss   result+4, %xmm0
        cvtss2sd %xmm0, %xmm0
        movsd   %xmm0, 12(%esp)
        movss   result+8, %xmm0
        cvtss2sd %xmm0, %xmm0
        movsd   %xmm0, 20(%esp)
        movss   result+12, %xmm0
        cvtss2sd %xmm0, %xmm0
        movsd   %xmm0, 28(%esp)
        call    printf
        addl    $36, %esp

        xorl    %eax, %eax
        movl    %ebp, %esp
        ret

【讨论】:

谢谢,这对我有用!我知道由于%f 需要双精度浮点数,我们必须执行转换。但是,那里没有需要单精度的通配符吗?这将使整个事情变得容易得多! 确实会,但是没有这样的说明符。请注意,C 将透明地进行转换,因此从打算使用 printf 的级别看不到任何笨拙。 您忘记保存/恢复来电者的%ebp。此外,如果您使用 sub $36(不是 16 的倍数),那么您将无法将 %esp 对齐 16,因为 call 指令未对齐它。 (现代版本的 i386 System V ABI 需要在函数调用之前进行 16 字节堆栈对齐。)但是,是的,否则使用 SSE2 为%f 存储堆栈参数的好例子。 (有关 %f 的更多详细信息,以及 C 的工作原理,请参阅 How to print a single-precision float with printf)【参考方案2】:

您似乎对如何在多个数据项上声明标签以及如何将标签加载到寄存器中感到困惑。标签只是一个地址——内存中的一个点——没有任何大小或与之相关的任何其他内容。标签后面的东西在内存中的连续地址中。因此,您将引用向量的标签声明为:

x:
    .float 7.0
    .float 2.4
    .float 1.2
    .float 5.5

现在您可以通过简单的移动将该地址加载到寄存器中,然后使用该寄存器加载向量:

    movl   $x, %eax
    movups (%eax), %xmm0

或者,您可以直接从标签加载

    movups x, %xmm0

【讨论】:

非常感谢,克里斯!但是,我还有两个问题:不是必须是leal x, %eax 吗?我们希望%eax 保存 x 的地址,而不是在那里找到的值,对吧?其次,如何打印我的结果以确保它有效? @baerenfaenger:是的,你需要一个$ 来获取标签的值作为立即数(而不是从标签加载),我最初打错了(重要的错字)。或者你可以使用leal

以上是关于使用 SSE(IA32 汇编)执行简单的算术运算的主要内容,如果未能解决你的问题,请参考以下文章

IA-32汇编语言笔记—— 基础概念

IA-32汇编语言笔记—— 分支程序设计

汇编语言中的寻址模式 (IA-32 NASM)

IA-32汇编语言笔记——堆栈的作用

计算机系统之汇编---IA32处理器数据格式及数据操作

IA-32汇编语言笔记—— 基础知识