我们知道调用约定“前六个整数或指针参数在寄存器RDI,RSI,RDX,RCX(Linux内核接口中的R10 [124]:124),R8和R9”中传递给c / Linux平台中的c ++代码基于以下文章。 https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions
然而,Linux平台中Java代码的调用约定是什么(假设JVM是热点)?以下是示例,什么寄存器存储了四个参数?
protected void caller( ) {
callee(1,"123", 123,1)
}
protected void callee(int a,String b, Integer c,Object d) {
}
答案 0 :(得分:10)
没有指定JVM如何在内部调用Java方法。各种JVM实现可以遵循不同的调用约定。以下是 Linux x64 中 HotSpot JVM 的工作原理。
每个Java方法都有一个进入解释器的入口点。此条目用于从解释方法跳转到另一个解释方法。
rbx
包含指向Method*
结构的指针 - a的内部元数据
方法被召唤。r13
保存sender_sp
- 调用方法的堆栈指针。如果使用rsp + 8
适配器,它可能与c2i
不同(见下文)。有关HotSpot源代码中解释器条目的更多详细信息:templateInterpreter_x86_64.cpp。
编译的方法有自己的入口点。编译的代码通过此条目调用编译的方法。
rsi
,rdx
,rcx
,r8
,r9
,rdi
。非静态方法接收this
引用作为rsi
中的第一个参数。xmm0
... xmm7
个寄存器中最多传递8个浮点参数。 |-------------------------------------------------------|
| c_rarg0 c_rarg1 c_rarg2 c_rarg3 c_rarg4 c_rarg5 |
|-------------------------------------------------------|
| rcx rdx r8 r9 rdi* rsi* | windows (* not a c_rarg)
| rdi rsi rdx rcx r8 r9 | solaris/linux
|-------------------------------------------------------|
| j_rarg5 j_rarg0 j_rarg1 j_rarg2 j_rarg3 j_rarg4 |
|-------------------------------------------------------|
您可能会注意到Java调用约定看起来类似于C调用约定但右移一个参数。这样做是为了避免在调用JNI方法时额外的寄存器重排(你知道,JNI方法在方法参数之前有额外的JNIEnv*
参数)。
Java方法可能还有两个入口点:c2i
和i2c
适配器。这些适配器是动态生成的代码片段,它们将编译的调用约定转换为解释器布局,反之亦然。 с2i
和i2c
入口点用于分别从已编译的代码和已解释的代码中编译的方法调用解释的方法。
P.S。 JVM内部调用方法通常并不重要,因为这些只是对最终用户不透明的实现细节。此外,即使在较小的JDK更新中,这些细节也可能会发生变化。但是,我知道至少有一种情况,当Java调用约定的知识可能看起来很有用时 - 分析JVM崩溃转储时。
答案 1 :(得分:0)
没有特定的规则,因为调用者和被调用者都在JVM的控制之下,因此不需要遵守约定。
特别是在考虑两种方法都已编译为本机代码的情况下,因为这通常在此代码路径成为热点时触发。在这种情况下,调用方法的代码很可能被内联到调用者代码中,从而启用后续代码转换,这将使其变成几乎与您编写的源代码类似的东西。调用方法的内联版本可以引用最初派生调用参数的值或常量,而不是引用参数变量。 (这尤其适用于所有参数都是常量值的示例)
有关详细信息,请参阅Static single assignment form,Global value numbering和Sparse conditional constant propagation。只有在对剩余变量进行所有这些更高级别的优化之后,才会将变量分配给寄存器,因此如果它们的变量仍然存在,它就不适用于任何固定方案中的参数。
如果调用没有内联,有几种不同的场景,每种场景可能都有自己的调用约定: