使用 NASM x86 程序集绘制余弦波

ploting a cosine wave using NASM x86 assembly

我必须使用汇编语言绘制一个简单的余弦 (x) 波。我已经按照教授的指示完成了项目的所有步骤,但我无法正确打印程序。这是我得到的输出,

linux2[14]% cat plot4.out
*****************************************

但应该是...

                  *****                  
                 *     *                 
                *       *                
               *         *               
              *           *              
             *             *             

            *               *            
           *                 *           

          *                   *          

         *                     *         
        *                       *        

       *                         *       
      *                           *      
     *                             *     
    *                               *    
  **                                 **  
**                                     **

这是我的代码。如有任何帮助,我们将不胜感激。

      SECTION .data         ; Data section, initialized variables
nrow:           dq 21       ; 21 rows
ncol:           dq 41       ; 41 columns
fmtc:           db "%c", 0  ; print one character at a time
fmtend:         db 10, 0    ; end a line
star:           db '*'      ; one character '*'
fmtendLen:  equ $-fmtend
pStar:      db  "*"
starLen:    equ $-pStar
pSpace:     db  " "
spaceLen:   equ $-pSpace
len:            equ $-star
spc:            db ' '
af:         dq 1.0, 0.0, -0.5 ; coefficients of polynomial, a_0 first
            dq 0.0, 0.041667, 0.0, -0.001389, 0.0, 0.000025
XF:         dq 0.0      ; computed
Y:          dq 0.0      ; computed
N:          dq 8        ; power of polynomial
X0:         dq -3.14159     ; start XF
DX0:            dq 0.15708      ; increment for XF  ncol-1  times
one:            dq 1.0
ten:            dq 10.0
none:           dq -1.0
nten:           dq -10.0
twenty:         dq 20.0
zero:           dq 0.0
newline:    db 10

    section .bss

a2: resb    21*41       ; two dimensional array of bytes
i:  resq    1       ; row subscript
j:  resq    1       ; col subscript
k:  resq    1

    SECTION .text   ; Code section.

    global _start   ; the standard gcc entry point
_start:                     ; the program label for the entry point


;;;   clear a2 to space
    mov     rax,0           ; i=0
    mov     [i],rax

loopi:
    mov     rax,[i]
    mov     rbx,0       ; j=0
    mov     [j],rbx
loopj:
    mov     rax,[i]
    mov     rbx,[j]
    imul    rax,[ncol]  ; i*ncol
    add     rax, rbx    ; i*ncol + j
    mov     dl, [spc]   ; need just character, byte
    mov     [a2+rax],dl ; store space

    mov     rbx,[j]
    inc     rbx         ; j++
    mov     [j],rbx
    cmp     rbx,[ncol]  ; j<ncol
    jne     loopj

    mov     rax,[i]
    inc     rax         ; i++
    mov     [i],rax
    cmp     rax,[nrow]      ; i<ncol
    jne     loopi

;;;   end clear a2 to space

    mov     rax, 0          ;i = 0
    mov     [i], rax
    mov     rbx, 0          ;j = 0
    mov     [j], rbx

cos:
    mov     rcx,[N]         ; loop iteration count initialization, n
    fld     qword [af+8*rcx] ; accumulate value here, get coefficient a_
h5loop:
    fmul    qword [XF]  ; * XF
    fadd    qword [af+8*rcx-8] ; + aa_n-i
    loop    h5loop         ; decrement rcx, jump on non zero
    fstp    qword [Y]          ; store Y


;;; ; ;  compute k
    fld qword [Y]
    fadd qword [one]
    fmul qword [ten]
    fmul qword [none]
    fadd qword [twenty]
    fistp qword [k]

;;; ; ; ; ; rax gets k * ncol + j
    mov     rax, [k]
    mov     rbx, [j]
    imul    rax, [ncol]
    add     rax, rbx

;;; ; ; put "*" in dl, then dl into [a2+rax]
    mov     dl, [star]
    mov     [a2+rax], dl

;;; ; ; XF = XF + DX0
    fld     qword [XF]
    fadd    qword [DX0]
    fistp   qword [XF]


    mov     rbx, [j]
    inc     rbx         ; j++

    mov     [j], rbx
    cmp     rbx,[ncol]  ; j<ncol
    jne     cos

;;;   print
    mov     rax,0       ; i=0
    mov     [i],rax

ploopi:
    mov     rax,[i]
    mov     rbx,0       ; j=0
    mov     [j],rbx

ploopj:

    mov rax,[i]
    mov rbx,[j]
    mov dl, [spc]

    imul    rax,[ncol]
    add     rax, rbx


    mov     rax, [i]        ; a2+i*ncol+j  is byte
    imul    rax, [ncol]
    add     rax, [j]
    add     rax, a2
    mov     rsi, rax ; address of character to print
    mov     rax, 1       ; system call 1 is write
    mov     rdi, 1       ; file handle 1 is stdout
    mov rdx, 1       ; number of bytes
    syscall          ; invoke operating system to do the write

;;;  print here

    mov     rbx,[j]
    inc     rbx         ; j++
    mov     [j],rbx
    cmp     rbx,[ncol]  ; j<ncol
    jne     ploopj

    mov     rdi, fmtend
    mov     rax, 1
    mov     rdi, 1  ; file handle 1 is stdout
    mov     rsi, newline ; address of string to output
    mov     rdx, 1       ; number of bytes
    syscall

;;;  print here

    mov     rax,[i]
    inc     rax         ; i++
    mov     [i],rax
    cmp     rax,[nrow]      ; i<ncol
    jne     ploopi

;;;   print a2

    mov     eax, 60 ; system call 60 is exit
    xor     rdi, rdi    ; exit code 0
    syscall             ; invoke operating system to exit

代码未按预期运行,因为 [XF] 更新错误:

fistp   qword [XF]  ; will store integer.

修复 XF 后,它会崩溃,因为计算出的 y 值超出了 [0,0] -> [41,21] 坐标。

您可以通过在绘制星星之前添加 min/max 坐标钳位来使您的代码更健壮,因此如果您的计算产生错误的 [x,y],它不会将星星写入某些内存,而是写入其他内容(我把 '#' 放在 y=0 处,看看你的图表哪里出了问题)。

之后您可能会想要修复图表...这取决于您。

无论如何,我对您的组装技术有更多的评论(或者正如我从评论中注意到的,您的导师的技术)。作为学生的作品,我可以容忍,但仅此而已。

要成为计算机程序员,您不应该只是将每一个愚蠢的字面意思翻译成计算机命令。如果人们这样编程,排序在任何情况下都仍然是完整的 O(n^2),并且没有人会创建任何压缩算法。你应该从根本上理解你想要实现什么样的计算,并尽可能简化。

让我从你的代码中给你举个例子。初始部分是将 space 字符放入 a2 数组的每个位置。所以基本上它在做:

for (i = 0; i < 21; ++i)
  for (j = 0; j < 41; ++j)
    a2[i * ncol + j] = ' ';

我有一个小问题..它是按字面意思完成的。就像每个表达式中的每个 f*cking 符号一样,所有内容都来回 loaded/stored 到内存中,就像 CPU 上的寄存器不存在一样。

但更糟糕的是,我也遇到了很大的问题。如果你戴上程序员的帽子,想想那部分正在进行什么计算,你应该弄清楚该计算的最终状态是,分配给 a2 的整个内存都被值 32 填充了(' ')。而a2在内存中占据了连续的21*41字节。

因此,要执行相同的操作,您可以编写以下代码:

lea rdi,[a2]    ; address of first byte of a2
lea ecx,[i-a2]  ; rcx = size of a2 array in bytes
; (using label "i" after it) And only ecx as 21*41 < 2^32
mov al,' '      ; space value directly (why [spc]?)
rep stosb       ; fill rcx bytes at rdi with al

它将用 space 填充整个 a2。如果这些循环按列进行,那么这段代码将以不同的方式按行进行填充。但是如果你只对计算结果感兴趣(整个数组设置为 ' '),那么你不关心它是按行、列还是圆来完成的。

等等...该代码中还有一些更奇怪的东西,但我不愿意根据自己的喜好重写它,我希望这足以说明我的想法。


顺便说一句,我不希望学生立即以 rep stosb 变体结束(甚至可以通过将 a2 填充为 16 或 32 乘法大小来进一步优化性能,并且填写一些 SSE 说明或至少 stosd).

但至少认识到内部循环在每次迭代中都在做 i * ncol + j...而你可能会做:

for (i = 0; i < 21; ++i) {
  rowindex = i*ncol + 0;
  for (j = 0; j < 41; ++j) {
    a2[rowindex] = ' ';
    ++rowindex;
  }
}

...这就像最低限度。然后,如果你调试它,你会注意到 rowindex = i*ncol + 0; 等于上一行结束时已经设置的值,所以你只需要在两个 for 循环之前做 rowindex = 0;

charindex = 0;
for (i = 0; i < 21; ++i)
  for (j = 0; j < 41; ++j)
    a2[charindex++] = ' ';

现在你应该已经看到两个 for 循环可以被单个 for (count = 0; count < 21*41; ++count) 代替......但是等一下,这不等于 charindex 吗?哦,是的。

for (i = 0; i < 21*41; ++i) a2[i] = ' ';

这相当于我的 rep stosb,但是如果你用简单的 mov/inc/dec/jnz 指令编写它作为循环(因为你不知道 rep stosb),我会完全没问题(它会在大致相同的时间执行)。

但是执行 21*41 imul 指令就像是……亵渎。曾经有一段时间,对 32 位数字进行 800 次乘法运算大约需要 3-5 秒。现在有人正在使用这种计算能力来清除连续的字节数组。痛...


顺便说一句,这是您原始代码的输出(在修复 [XF] 更新和钳位值之后......我用点替换了 spaces,并且钳位 Y 值变成 * 变成 #.

.......................*#################
.........................................
.........................................
.........................................
.........................................
*.....................*..................
.*.......................................
..*......................................
.........................................
...*.................*...................
.........................................
....*....................................
.....*..............*....................
.........................................
......*............*.....................
.......*.................................
........*.........*......................
.........*.......*.......................
..........*..............................
...........*...**........................
............***..........................