getelementptr 的第一个索引操作数为 -1
getelementptr has -1 as the first index operand
我正在阅读由 Clang 生成的 nginx 的 IR。在函数 ngx_event_expire_timers
中,有一些 getelementptr
指令以 i64 -1
作为第一个索引操作数。例如,
%handler = getelementptr inbounds %struct.ngx_rbtree_node_s, %struct.ngx_rbtree_node_s* %node.addr.0.i, i64 -1, i32 2
我知道第一个索引操作数将用作第一个操作数的偏移量。但是负偏移量是什么意思?
GEP 指令非常适合负索引。
在这种情况下,你有类似的东西:
node arr[100];
node* ptr = arr[50];
if ( (ptr-1)->value == ptr->value)
// then ...
具有负索引的 GEP 只计算基指针到另一个方向的偏移量。没有什么问题。
考虑到 nginx 源代码中的内容,getelementptr 指令的语义很有趣。这是两行C源代码的结果:
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
ev->handler(ev);
node
属于 ngx_rbtree_node_t
类型,它是 ev
类型 ngx_event_t
的成员。这就像:
struct ngx_event_t {
....
struct ngx_rbtree_node_t time;
....
};
struct ngx_event_t *ev;
struct ngx_rbtree_node_t *node;
timer
是结构 ngx_event_t
成员的名称,其中 node
应该指向。
|<- ngx_rbtree_node_t ->|
|<- ngx_event_t ->|
------------------------------------------------------
| (some data) | "time" | (some data)
------------------------------------------------------
^ ^
ev node
上图显示了 ngx_event_t
实例的布局。 offsetof(ngx_event_t, time)
的结果是 40。也就是说 time
之前的 some data
是 40 个字节。而 ngx_rbtree_node_t
的大小也是 40 字节,巧合的是。所以 getelementptr 指令的第一个索引操作数中的 i64 -1
计算包含 node
的 ngx_event_t
的基地址,它比 node
提前 40 个字节。
handler
是 ngx_event_t
的另一个成员,它在 ngx_event_t
的基数后面 16 个字节。巧合的是,ngx_rbtree_node_t
的第三个成员也在 ngx_rbtree_node_t
的基地址后面 16 个字节。所以getelementptr指令中的i32 2
会将ev
加上16个字节,得到handler
的地址。
请注意,16 个字节是根据 ngx_rbtree_node_t
的布局计算得出的,而不是 ngx_event_t
。 Clang 必须进行一些计算以确保 getelementptr 指令的正确性。在使用 %handler
的值之前,有一个位转换指令将 %handler
转换为函数指针类型。
Clang 的所作所为打破了 C 源代码中定义的类型转换过程。但是结果完全一样
我正在阅读由 Clang 生成的 nginx 的 IR。在函数 ngx_event_expire_timers
中,有一些 getelementptr
指令以 i64 -1
作为第一个索引操作数。例如,
%handler = getelementptr inbounds %struct.ngx_rbtree_node_s, %struct.ngx_rbtree_node_s* %node.addr.0.i, i64 -1, i32 2
我知道第一个索引操作数将用作第一个操作数的偏移量。但是负偏移量是什么意思?
GEP 指令非常适合负索引。 在这种情况下,你有类似的东西:
node arr[100];
node* ptr = arr[50];
if ( (ptr-1)->value == ptr->value)
// then ...
具有负索引的 GEP 只计算基指针到另一个方向的偏移量。没有什么问题。
考虑到 nginx 源代码中的内容,getelementptr 指令的语义很有趣。这是两行C源代码的结果:
ev = (ngx_event_t *) ((char *) node - offsetof(ngx_event_t, timer));
ev->handler(ev);
node
属于 ngx_rbtree_node_t
类型,它是 ev
类型 ngx_event_t
的成员。这就像:
struct ngx_event_t {
....
struct ngx_rbtree_node_t time;
....
};
struct ngx_event_t *ev;
struct ngx_rbtree_node_t *node;
timer
是结构 ngx_event_t
成员的名称,其中 node
应该指向。
|<- ngx_rbtree_node_t ->|
|<- ngx_event_t ->|
------------------------------------------------------
| (some data) | "time" | (some data)
------------------------------------------------------
^ ^
ev node
上图显示了 ngx_event_t
实例的布局。 offsetof(ngx_event_t, time)
的结果是 40。也就是说 time
之前的 some data
是 40 个字节。而 ngx_rbtree_node_t
的大小也是 40 字节,巧合的是。所以 getelementptr 指令的第一个索引操作数中的 i64 -1
计算包含 node
的 ngx_event_t
的基地址,它比 node
提前 40 个字节。
handler
是 ngx_event_t
的另一个成员,它在 ngx_event_t
的基数后面 16 个字节。巧合的是,ngx_rbtree_node_t
的第三个成员也在 ngx_rbtree_node_t
的基地址后面 16 个字节。所以getelementptr指令中的i32 2
会将ev
加上16个字节,得到handler
的地址。
请注意,16 个字节是根据 ngx_rbtree_node_t
的布局计算得出的,而不是 ngx_event_t
。 Clang 必须进行一些计算以确保 getelementptr 指令的正确性。在使用 %handler
的值之前,有一个位转换指令将 %handler
转换为函数指针类型。
Clang 的所作所为打破了 C 源代码中定义的类型转换过程。但是结果完全一样