我正在阅读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
我知道第一个索引操作数将用作第一个操作数的偏移量。但负偏移意味着什么?
答案 0 :(得分:1)
GEP指令完全没有负指数。 在这种情况下,你有类似的东西:
node arr[100];
node* ptr = arr[50];
if ( (ptr-1)->value == ptr->value)
// then ...
具有负指数的GEP只计算基指针到另一个方向的偏移量。没有什么问题。
答案 1 :(得分:0)
考虑到在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.这意味着some data
之前的time
为40个字节。而ngx_rbtree_node_t
的大小也是40字节,巧合。因此,getelementptr指令的第一个索引oprand中的i64 -1
计算包含ngx_event_t
的{{1}}的基地址,该地址比node
提前40个字节。
node
是handler
的另一个成员,比ngx_event_t
的基数落后16个字节。通过(另一)巧合,ngx_event_t
的第三个成员也比ngx_rbtree_node_t
的基址低16个字节。因此,getelementptr指令中的ngx_rbtree_node_t
将向i32 2
添加16个字节,以获取ev
的地址。
请注意,16个字节是根据handler
的布局计算的,而不是ngx_rbtree_node_t
的布局。 Clang必须进行一些计算以确保getelementptr指令的正确性。在使用ngx_event_t
的值之前,有一个bitcast指令将%handler
强制转换为函数指针类型。
Clang做了什么打破了C源代码中定义的类型转换过程。但结果完全相同。