15.4 翻译 Pcode 中的算术命令、 push/pop 命令以及 jmp/jz 命令

算术命令(add / sub / mul / div / mod / cmpeq / cmpne / cmpgt / cmplt / cmpge / cmple / and / or / not / neg 命令)、 push/pop 命令以及 jmp/jz 命令的操作很简单,因此将其翻译成 x86 指令也很简单,结合第 3 、 4 章中介绍的这些命令对栈的操作步骤,典型的宏定义如下:

  1. %MACRO add 0
  2. POP EAX
  3. ADD DWORD [ESP], EAX
  4. %ENDMACRO
  5.  
  6. %MACRO sub 0
  7. POP EAX
  8. SUB DWORD [ESP], EAX
  9. %ENDMACRO
  10.  
  11. %MACRO cmpeq 0
  12. MOV EAX, [ESP+4]
  13. CMP EAX, [ESP]
  14. PUSHF
  15. POP EAX
  16. SHR EAX, 6
  17. AND EAX, 0X1
  18. ADD ESP, 4
  19. MOV [ESP], EAX
  20. %ENDMACRO
  21.  
  22. %MACRO not 0
  23. MOV EAX, [ESP]
  24. OR EAX, EAX
  25. PUSHF
  26. POP EAX
  27. SHR EAX, 6
  28. AND EAX, 0X1
  29. MOV [ESP], EAX
  30. %ENDMACRO
  31.  
  32. %MACRO jz 1
  33. POP EAX
  34. OR EAX, EAX
  35. JZ %1
  36. %ENDMACRO
  37.  
  38. %MACRO jmp 1
  39. JMP %1
  40. %ENDMACRO
  41.  
  42. %MACRO push 1
  43. PUSH DWORD %1
  44. %ENDMACRO
  45.  
  46. %MACRO pop 0-1
  47. %IFIDN %0, 0
  48. ADD ESP, 4
  49. %ELSE
  50. POP DWORD %1
  51. %ENDIF
  52. %ENDMACRO

所有宏定义见 macro.inc

测试文件 test.nasm

  1. MOV EBP, ESP
  2. SUB ESP, 8
  3. %define a [EBP-4]
  4. %define b [EBP-8]
  5.  
  6. ; a = readint("Please input an number `a`: ")
  7. readint "Please input an number `a`: "
  8. pop a ; ==> POP DWORD [EBP-4]
  9.  
  10. ; b = readint("Please input another number `b`: ")
  11. readint "Please input another number `b`: "
  12. pop b ; ==> POP DWORD [EBP-8]
  13.  
  14. ; print("a = %d", a)
  15. push a ; ==> PUSH DWORD [EBP-4]
  16. print "a = %d"
  17.  
  18. ; print("b = %d", b)
  19. push b ; ==> PUSH DWORD [EBP-8]
  20. print "b = %d"
  21.  
  22. ; print("a - b = %d", a - b)
  23. push a
  24. push b
  25. sub
  26. print "a - b = %d"
  27.  
  28. ; if (a > b) { print("a > b"); } else { print("a <= b") }
  29. push a
  30. push b
  31. cmpgt
  32. jz _LESSEQUAL
  33. print "a > b"
  34. jmp _EXIT
  35. _LESSEQUAL:
  36. print "a <= b"
  37. _EXIT:
  38. exit 0

库函数文件( tio.c )和 makefile 文件,和上一节是一样的。

将以上四个文件下载下来放到用一个目录,输入 make run 即可编译并运行测试代码。运行过程如下:

  1. $ make run
  2. ...
  3. ./test
  4. Please input an number `a`: 46
  5. Please input another number `b`: 48
  6. a = 46
  7. b = 48
  8. a - b = -2
  9. a <= b

test.nasm 的开头在栈上分配了 8 个字节的空间(也就是 2 个 int ),并定义了 a 和 b 代表这储存在这两个 int 型空间中的数值:

  1. MOV EBP, ESP
  2. SUB ESP, 8
  3. %define a [EBP-4]
  4. %define b [EBP-8]

在 macro.inc 中定义了 pop 和 push 宏, test.nasm 中的 “pop a” 和 “push a” 将会被展开为: “POP DWORD [EBP-4]” 和 “PUSH DWORD [EBP-8]”。