15.5 翻译 Pcode 中的自定义函数命令和变量声明命令

上一节我们已经实现了将简单 TinyC 语句手工翻译成 Pcode ,然后编写了 NASM 宏将这些 Pcode 翻译成 x86 指令,最后汇编、链接成可执行程序。我们已经编写了大部分 Pcode 命令所对应的宏,本节将编写 NASM 宏来翻译 Pcode 中最复杂、也是最难翻译的命令–自定义函数命令和变量声明命令(FUNC / ENDFUNC / arg / ret / $func_name / var)。

具体来说,我们需要将以下 Pcode 翻译成 x86 指令:

  1. ; int main() {
  2. FUNC @main:
  3. ; int a;
  4. var a
  5.  
  6. ; a = 3;
  7. push 3
  8. pop a
  9.  
  10. ; print("sum = %d", sum(4, a));
  11. push 4
  12. push a
  13. $sum
  14. print "sum = %d"
  15.  
  16. ; return 0;
  17. ret 0
  18. ; }
  19. ENDFUNC
  20.  
  21. ; int sum(int a, int b) {
  22. FUNC @sum:
  23. arg a, b
  24.  
  25. ; int c;
  26. var c
  27.  
  28. ; c = a + b;
  29. push a
  30. push b
  31. add
  32. pop c
  33.  
  34. ; return c;
  35. ret c
  36. ; }
  37. ENDFUNC

最难的部分在于如何避免不同函数中的同名变量的冲突。 NASM 的宏虽然强大,但无法满足如此复杂的需要。为降低翻译的难度,将以上 Pcode 稍微改写一下,保存为 test.pcode

  1. ; int main() {
  2. FUNC @main:
  3. ; int a;
  4. main.var a
  5.  
  6. ; a = 3;
  7. push 3
  8. pop a
  9.  
  10. ; print("sum = %d", sum(4, a));
  11. push 4
  12. push a
  13. $sum
  14. print "sum = %d"
  15.  
  16. ; return 0;
  17. ret 0
  18. ; }
  19. ENDFUNC@main
  20.  
  21. ; int sum(int a, int b) {
  22. FUNC @sum:
  23. sum.arg a, b
  24.  
  25. ; int c;
  26. sum.var c
  27.  
  28. ; c = a + b;
  29. push a
  30. push b
  31. add
  32. pop c
  33.  
  34. ; return c;
  35. ret c
  36. ; }
  37. ENDFUNC@sum

首先编写 FUNC 和 ret 宏,放到 macro.inc 文件中,同时在该文件的最后增加调用 @main 函数及退出的指令:

  1. %MACRO FUNC 1
  2. %1
  3. PUSH EBP
  4. MOV EBP, ESP
  5. %ENDMACRO
  6.  
  7. %MACRO ret 0-1
  8. %IFIDN %0, 1
  9. %IFIDN %1, ~
  10. MOV EAX, [ESP]
  11. %ELSE
  12. MOV EAX, %1
  13. %ENDIF
  14. %ENDIF
  15. LEAVE
  16. RET
  17. %ENDMACRO
  18.  
  19. EXTERN PRINT, READINT
  20. GLOBAL _start
  21.  
  22. [SECTION .TEXT]
  23. _start:
  24. CALL @main
  25. PUSH EAX
  26. exit [ESP]

然后,编写 main.var, ENDFUNC@main, $sum, sum.arg, sum.var 和 ENDFUNC@sum 宏,保存为 test.funcmacro

  1. ; ==== begin function `main` ====
  2. %define main.varc 1
  3.  
  4. %MACRO main.var main.varc
  5. %define a [EBP - 4*1]
  6. SUB ESP, 4*main.varc
  7. %ENDMACRO
  8.  
  9. %MACRO ENDFUNC@main 0
  10. LEAVE
  11. RET
  12. %undef a
  13. %ENDMACRO
  14. ; ==== end function `main` ====
  15.  
  16. ; ==== begin function `sum` ====
  17. %define sum.argc 2
  18. %define sum.varc 1
  19.  
  20. %MACRO $sum 0
  21. CALL @sum
  22. ADD ESP, 4*sum.argc
  23. PUSH EAX
  24. %ENDMACRO
  25.  
  26. %MACRO sum.arg sum.argc
  27. %define a [EBP + 8 + 4*sum.argc - 4*1]
  28. %define b [EBP + 8 + 4*sum.argc - 4*2]
  29. %ENDMACRO
  30.  
  31. %MACRO sum.var sum.varc
  32. %define c [EBP - 4*1]
  33. SUB ESP, 4*sum.varc
  34. %ENDMACRO
  35.  
  36. %MACRO ENDFUNC@sum 0
  37. LEAVE
  38. RET
  39. %undef a
  40. %undef b
  41. %undef c
  42. %ENDMACRO
  43. ; ==== end function `sum` ====

这些宏会将 @sum 函数展开为如下形式:

  1. @sum: PUSH EBP MOV EBP, ESP SUB ESP, 4*1

  2. PUSH DWORD [EBP + 12]   ; push a
  3. PUSH DWORD  [EBP + 8]   ; push b
  4. add
  5. POP DWORD [EBP - 4*1]   ; pop c
  6. MOV EAX, [EBP - 4*1]    ; MOV EAX, c
  7. LEAVE
  8. RET

最后,改写 makefile 文件中的以下两行:

  1. test.o: test.pcode test.funcmacro macro.inc
  2. nasm -f elf32 -P"macro.inc" -P"test.funcmacro" -o test.o test.pcode

将以上四个文件以及库函数文件 tio.c 放到用一个目录,输入 make run 即可编译并运行测试代码。

至此所有 Pcode 命令对应的 NASM 宏编写完毕。

第 15 章完