一起学RISC-V汇编第9讲之RISC-V ABI之函数调用

目录
  • 1 RISC-V ABI接口
  • 2 RISC-V 函数调用约定
    • 2.1 入参的传递
    • 2.2 返回值的传递

1 RISC-V ABI接口

ABI(Application Binary Interface)为应用程序二进制接口,它定义了应用程序之间或应用程序和操作系统之间进行二进制级交互时必须遵循的规则和约定。ABI包括了关于函数调用约定(参数传递,函数返回值等)、数据类型、对齐方式、字节序、 函数栈布局、系统调用等方面的规范。

具体来说,ABI定义了以下内容:

  1. 数据类型、对齐方式和字节序;
  2. 寄存器使用约定:哪些寄存器用于传递函数参数、返回值和保存临时变量;
  3. 函数调用约定:函数调用的具体步骤,包括参数传递、返回值处理等;
  4. 栈的使用:栈的增长方向、如何保存和恢复栈指针等;
  5. 系统调用:操作系统提供的服务调用方式和参数传递规则;

ABI的存在使得不同的编程语言、编译器和操作系统之间能够进行二进制级的互操作,确保它们能够正确地调用和使用彼此所生成的代码。由于不同的体系结构和操作系统可能有不同的ABI,因此跨平台开发时需要考虑ABI的兼容性。

这里只讲函数调用约定、寄存器使用约定,栈帧这几部分内容,内容放在一起比较长,所以文章分三部分。

2 RISC-V 函数调用约定

函数调用约定主要是约定怎么传递函数参数、怎么返回值。

第2讲寄存器这一章,列出了32个通用寄存器以及32个浮点寄存器:

image-20240409222851399

一般规则如下:

  1. 入参的传递:函数调用时,优先使用 a0-a7 这8个标量寄存器(对于浮点类型是fa0-fa7这8个浮点寄存器)来传递入参
  2. 返回值的传递:一般用a0-a1来传递(浮点数用fa0-fa1来传递)返回值

下面讲述更详细的规则。

2.1 入参的传递

但是如果入参超过8个,只用寄存器不够该怎么办?如果函数入参是结构体类型该如何处理呢?如果入参有标量又有浮点数该如何处理呢?

有如下几种情况:

  • 当一个标量位宽不超过XLEN位或者一个浮点实数参数不超过FLEN位时,使用单个参数寄存器传递。若没有可用的参数寄存器,则在栈上传递
  • 当一个标量位宽超过XLEN位但是不超过2×XLEN时,则可以在一对参数寄存器中传递,低XLEN位在小编号寄存器中,高XLEN位在大编号寄存器中;若没有可用的参数寄存器,则在栈上传递标量;若只有一个寄存器可用,则低XLEN位在寄存器中传递,高XLEN位在栈上传递
  • 若一个标量宽度大于2×XLEN,则通过引用传递(栈传递),并在参数列表中用地址替换。通过引用传递的实参可以由被调用方修改

常见的有如下几个场景:

几种场景 参数列表 参数传递
情形1 n1,n2,n3 a0,a1,a2
情形2 n1,n2,n3,n4,n5,n6,n7,n8,n9,n10 a0,a1,a2,a3,a4,a5,a6,a7,a8,stack
情形3 l1, l2 a0,a1,a2,a3
情形4 s1,s2,s3
d1,d2,d3
s1,d1,d2
fa0,fa1,fa2
情形5 s1,s2,s3,s4,s5,s6,s7,s8,s9,s10 fa0,fa1,fa2,fa3,fa4,fa5,fa6,fa7,a0,a1
情形6 n1,n2,n3,s1,d1 a0,a1,a2,fa0,fa1

表中:n1,n2... 表示标量位宽不超过XLEN的整数,l1,l2...表示标量位宽超过XLEN,不超过2×XLEN的整数,s1,s2...表示单精度浮点,d1,d2...表示双精度浮点。

举例如下:

情形1:标量位宽不超过XLEN,且函数入参个数小于8,使用寄存器传递

源码:

#include <stdio.h>int test(int arg0, int arg1, int arg2, int arg3, int arg4,int arg5, int arg6, int arg7)
{int sum = 0;sum += arg0 + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7;return sum;
}int main(void)
{int sum = 0;sum = test(1, 2, 3, 4, 5, 6, 7, 8);printf("sum = %d\r\n", sum);return 0;
}

例子中,标量位宽为32bit,XLEN=32bit(-march=rv32imafdc),从反汇编可以看出,入参使用的是a0-a7寄存器传递,返回值使用的是a0

image-20240618230302545

情形2:标量位宽不超过XLEN,且函数入参个数大于8,前8个参数使用寄存器传递,剩下的使用栈stack传递

源码:

#include <stdio.h>int test(int arg0, int arg1, int arg2, int arg3, int arg4,int arg5, int arg6, int arg7, int arg8, int arg9)
{int sum = 0;sum += arg0 + arg1 + arg2 + arg3 + arg4 + arg5 +arg6 + arg7 + arg8 + arg9;return sum;
}int main(void)
{int sum = 0;sum = test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);printf("sum = %d\r\n", sum);return 0;
}

例子中,标量位宽为32bit,XLEN=32bit(-march=rv32imafdc),从反汇编可以看出,前8个入参使用的是a0-a7寄存器传递,后面两个入参使用的是栈传递(caller 将多的数据存到栈中,callee在栈对应位置取数),返回值使用的是a0

image-20240618230152517

情形3:标量位宽超过XLEN,但是不超过2×XLEN时,则可以使用一对寄存器来保存一个入参

源码:

#include <stdio.h>long long int test(long long int arg0, long long int arg1,long long int arg2, long long int arg3)
{long long int sum = 0;sum += arg0 + arg1 + arg2 + arg3;return sum;
}int main(void)
{long long int sum = 0;sum = test(1, 2, 3, 4);printf("sum = %ld\r\n", sum);return 0;
}

例子中,标量位宽为64bit,XLEN=32bit(-march=rv32imafdc),从反汇编可以看出,使用2个标量寄存器保存一个入参,且低XLEN位在小编号寄存器中,高XLEN位在大编号寄存器中,若入参没有足够可用的参数寄存器,则在栈上传递标量;若只有一个寄存器可用,则低XLEN位在寄存器中传递,高XLEN位在栈上传递。

image-20240618231430562

情形4:如果数据类型是浮点,位宽不超过FLEN,且函数入参个数小于8,使用寄存器传递

源码:

#include <stdio.h>float test(float arg0, float arg1, float arg2, float arg3, float arg4,float arg5, float arg6, float arg7)
{float sum = 0;sum += arg0 + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7;return sum;
}float main(void)
{float sum = 0;sum = test(1, 2, 3, 4, 5, 6, 7, 8);printf("sum = %d\r\n", sum);return 0;
}

例子中,标量位宽为32bit,FLEN=64bit(-march=rv32imafdc),从反汇编可以看出,入参使用的是fa0-fa7寄存器传递,返回值使用的是fa0

image-20240622180443532

情形5:如果数据类型是浮点,位宽不超过FLEN,且函数入参个数大于8,如果标量寄存器有剩余,则可以使用标量寄存器来传递浮点参数

源码:

#include <stdio.h>float test(float arg0, float arg1, float arg2, float arg3, float arg4,float arg5, float arg6, float arg7, float arg8, float arg9)
{float sum = 0;sum += arg0 + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9;return sum;
}float main(void)
{float sum = 0;sum = test(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);printf("sum = %d\r\n", sum);return 0;
}

例子中,FLEN=64bit(-march=rv32imafdc),从反汇编可以看出,前8个入参使用的是fa0-fa7寄存器传递,后面两个入参使用的标量寄存器a0, a1传递

image-20240622181654041

情形6:入参包括整数,单精度浮点,双精度浮点

源码:

#include <stdio.h>double test(int arg0, int arg1, float arg2, double arg3)
{double sum = 0;sum += arg0 + arg1 + arg2 + arg3;return sum;
}double main(void)
{double sum = 0;sum = test(1, 2, 3.0f, 4.0);printf("sum = %f\r\n", sum);return 0;
}

例子中,XLEN=32bit,FLEN=64bit(-march=rv32imafdc),从反汇编可以看出,标量用a0-a7传递,浮点数用fa0-fa7传递。

image-20240622182333942

情形7:结构体作为入参

又分如下几种情况:

  1. 若结构体的宽度不超过XLEN位,则这个结构体可以在寄存器中传递,若没有可用的寄存器,则在栈上传递。

  2. 若结构体的宽度超过XLEN位,不超过2×XLEN位,则可以在一对寄存器中传递。若只有一个寄存器可用,则结构体的前半部分在寄存器中传递,后半部分在栈上传递;若没有可用的寄存器,则在栈上传递结构体。

  3. 若一个结构体的宽度大于2×XLEN位,则通过引用传递,并在参数列表中被替换为地址。传递到栈上的结构体会对齐到类型对齐和XLEN中的较大者,但不会超过栈对齐要求。

2.2 返回值的传递

几种场景 返回值类型 返回值存放
情形1 void 当函数没有返回值时,不需要考虑返回寄存器的处理
情形2 标量(整形或指针类型)位宽不超过XLEN a0
情形3 标量(整形或指针类型)位宽超过XLEN,不超过2xXLEN a0,a1
情形4 单精度浮点(float)或 双精度浮点(double) fa0
情形5 struct 需要细分,如果寄存器能放下使用寄存器,否则用栈

情形1:当函数没有返回值时,不需要考虑返回寄存器的处理

情形2:当函数返回类型是标量(整形或指针类型)位宽不超过XLEN,返回值存放在整型寄存器a0上

情形3:当函数返回类型是标量(整形或指针类型)位宽超过XLEN,不超过2xXLEN,返回值存放在整型寄存器a0,a1上

同样举例:

#include <stdio.h>long long test(int arg0, int arg1, int arg2, int arg3, int arg4,int arg5, int arg6, int arg7)
{long long sum = 0;sum += arg0 + arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7;return sum;
}int main(void)
{long long sum = 0;sum = test(1, 2, 3, 4, 5, 6, 7, 8);printf("sum = %ld\r\n", sum);return 0;
}

例子中,标量位宽为64bit,XLEN=32bit(-march=rv32imafdc),即返回值位宽超过XLEN,但是不超过2×XLEN,从反汇编可以看出,使用的是a0、a1来存放返回值。

image-20241005234556396

情形4:当函数返回类型是单精度浮点或双精度浮点(float 或double),返回值存放在整型寄存器fa0上

情形5:返回值为结构体(struct),返回值需要细分,如果寄存器能放下则使用寄存器(标量使用a0/a1, 浮点使用fa0/fa1),否则用栈传递

寄存器能放下的情况,结构体放到a0 a1上返回:

#include <stdio.h>typedef struct Point {int x;int y;
} Point;Point create_point(int x, int y) {Point p = {x, y};return p;
}int main() 
{Point my_point = create_point(10, 20);printf("Point: (%d, %d)\n", my_point.x, my_point.y);return 0;
}

image-20241006095115309

寄存器不能放下的情况,使用栈返回:

#include <stdio.h>typedef struct Point {int x;int y;int z;
} Point;Point create_point(int x, int y, int z) {Point p = {x, y, z};return p;
}int main() 
{Point my_point = create_point(10, 20, 30);printf("Point: (%d, %d, %d)\n", my_point.x, my_point.y, my_point.z);return 0;
}

image-20241006193633534

参考:

  1. Releases · riscv-non-isa/riscv-elf-psabi-doc (github.com)
  2. RISC-V函数调用规范 - 知乎 (zhihu.com)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/809552.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

在C#中使用适配器Adapter模式和扩展方法解决面向的对象设计问题

之前有阵子在业余时间拓展自己的一个游戏框架,结果在实现的过程中发现一个设计问题。这个游戏框架基于MonoGame实现,在MonoGame中,所有的材质渲染(Texture Rendering)都是通过SpriteBatch类来完成的。举个例子,假如希望在屏幕的某个地方显示一个图片材质(imageTexture)…

React Fiber 原理

React Fiber 在 React 16 之前的版本对比更新 VirtualDOM 的过程是采用 Stack 架构实现的,也就是循环加递归,这种方式的问题是一旦任务开始进行就无法被中断。 如果应用中的组件数量庞大, Virtual DOM 的层级比较深,主线程被长期占用,知道整颗 Virtual DOM 树比对更新完成…

视野修炼-技术周刊第104期 | 下一代 JavaScript 工具链

① 🐙 尤大创办公司 VoidZero ② Tauri 2.0 稳定版发布 ③ Vite 时髦的新主页 ④ qrframe - 漂亮二维码生成 ⑤ HTTP QUERY 方法提案 ⑥ TinyJS - 轻量级的创建DOM元素 ⑦ 9月 Web 平台的新功能 ⑧ ESLint 现在正式支持 Linting JSON 和 Markdown欢迎来到第 104 期的【视野修…

雅礼国庆集训 day1 T2 折射

题面 题面下载 算法 转化题意 说白了就是给了你一堆点,让你数这种折线有多少个 (严格向下走,并且横坐标之间的差越来越小)看着像一种在 y 轴方向排序的 dp 但是由于是折线, 所以需要加一维来判断转向 dp 设计 状态设计 \(dp_{i, 0/1}\) 表示第 i 个点, 是向左下还是右上 状态…

React 中的 diff 算法

React diff为什么使用虚拟 DOM ? 浏览器在处理 DOM 的时候会很慢,处理 JavaScript 会很快,页面复杂的时候,频繁操作 DOM 会有很大的性能开销(每次数据变化都会引起整个 DOM 树的重绘和重排)。 为了避免频繁操作 DOM,React 会维护两个虚拟 DOM,如果有数据更新,会借此计…

20222315 2024-2025-1 《网络与系统攻防技术》实验一实验报告

1.实验内容 1.掌握反汇编与十六进制编程器 2.能正确修改机器指令改变程序执行流程 3.能正确构造payload进行bof攻击 2.实验过程 1.直接修改程序机器指令,改变程序执行流程 将pwn1文件下载至kali中并将pwn1文件改名为pwn20222315,并将其内容复制到pwn2反汇编文件objdump -d…

多校A层冲刺NOIP2024模拟赛03

多校A层冲刺NOIP2024模拟赛03\(T1\) A. 五彩斑斓(colorful) \(90/100pts\)部分分\(20pts\) :枚举左上 \((k,h)\) 、右下端点 \((i,j)\) ,时间复杂度为 \(O(n^{2}m^{2})\) 。 \(90/100pts\) :当 \(a_{i,j} \ne a_{k,j}\) 时任意的 \(h \in [1,j]\) 都符合题意、不妨钦定 \(…

Word中 Endnote 引用标蓝色

1. 打开word中的endnote加载项。如图所示,勾选这两个设置。 确认后会自动变为超链接,显示蓝色以及下划线。 2. 在样式设置中,将超链接的下划线取消。之后就会只显示蓝色引用。 结果显示:

中国大学生程序设计竞赛(秦皇岛)正式赛东北大学秦皇岛分校(SMU Autumn 2024 Team Round 1)

中国大学生程序设计竞赛(秦皇岛)正式赛东北大学秦皇岛分校(SMU Autumn 2024 Team Round 1) Problem A. 贵校是构造王国吗 I 思路 官方题解很清晰明了。代码 #include <bits/stdc++.h> using namespace std; #define int long long #define endl \n #define PII pair&…

多校 A 层冲刺 NOIP2024 模拟赛 03

多校 A 层冲刺 NOIP2024 模拟赛 03 T1 五彩斑斓(colorful) 签到题 直接暴力枚举是 \(O(n^4)\) ,考虑使用 \(bitset\) 优化,对每个点开个 \(bitset\),预处理它所在一行它及它之前相同颜色的位置,这样就只用枚举另一个点所在列,时间复杂度为 \(O(n^3+\frac{n^4}{w})\)。 T…

在浏览器上访问媒体资源配置【文件上传】

1.根urls.py文件中 from django.contrib import admin from django.urls import path, include, re_path from django.views.static import serve from django.conf import settingsurlpatterns = [# path(admin/, admin.site.urls),path(api/shipper/, include(apps.shipper.u…