< 返回版块

destinyFvcker 发表于 2024-12-19 15:49

Tags:LLVM

最近在看tokio源代码,在tokio/src/util/linked_list.rs之中看到了这样一段注释:

/// Previous / next pointers.
pub(crate) struct Pointers<T> {
    inner: UnsafeCell<PointersInner<T>>,
}
/// We do not want the compiler to put the `noalias` attribute on mutable
/// references to this type, so the type has been made `!Unpin` with a
/// `PhantomPinned` field.
///
/// Additionally, we never access the `prev` or `next` fields directly, as any
/// such access would implicitly involve the creation of a reference to the
/// field, which we want to avoid since the fields are not `!Unpin`, and would
/// hence be given the `noalias` attribute if we were to do such an access. As
/// an alternative to accessing the fields directly, the `Pointers` type
/// provides getters and setters for the two fields, and those are implemented
/// using `ptr`-specific methods which avoids the creation of intermediate
/// references.
///
/// See this link for more information:
/// <https://github.com/rust-lang/rust/pull/82834>
struct PointersInner<T> {
    /// The previous node in the list. null if there is no previous node.
    prev: Option<NonNull<T>>,

    /// The next node in the list. null if there is no previous node.
    next: Option<NonNull<T>>,

    /// This type is !Unpin due to the heuristic from:
    /// <https://github.com/rust-lang/rust/pull/82834>
    _pin: PhantomPinned,
}

这是一个双向链表的指针部分,但是这里的注释的含义我不太理解:好像加入PhantomPinned不是为了避免借出可变引用,改变链表元素在内存之中的位置一样,反而就是为了阻止Rust编译器生成noalias。

然后我就去网上查了一下这个noalias到底是什么,发现在21年在raddit上有很多关于这个的讨论(正好上面这段注释也是在2021年写的)。

大概意思就是说这个noalias可以让LLVM进行更加激进的优化,但是好像一直有bug,经常是修好一阵子就因为另外一些crate出现了编译问题就又关闭了。

所以,我的问题是:

  1. tokio在这里避免编译器生成noalias是因为21年这个特型还不稳定,防止因为Rustc的版本编译期爆炸。还是说从Rust语意上来说,使用PhantomPinned就是为了防止借出可变引用(后面使用Pin钉住),还是两者兼而有之?
  2. noalias 的作用具体是什么?我只找到了零散的描述,有没有什么博客我可以学习一下?
  3. 现在这个noalias Rust默认启用了吗?从讨论里面的描述上来看,应该是有一些性能提升的,如果还不能启用的话,原因是什么?

谢谢大家

评论区

写评论
作者 destinyFvcker 2024-12-21 23:08

感谢!🙏

--
👇
az: 我也关注过这个话题,查过一些相关的内容。但LLVM我不懂,权当抛转引玉吧。

https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462

↑这是Alice遇到帖子上的问题时写的一篇文章,基本把整个问题说清楚了。我按个人理解简单归纳下:通过侵入式链表保存waker可以避免大量的堆分配,但是它在Tokio的场景下会违反Rust的别名模型,而且Rust也没有暴露任何放松别名模型要求的原语,这种实现是unsound的。(其实unsafe链表不违反别名模型,Tokio这里的情况比较特殊)

因为Generator也有类似Tokio的问题,而且它们都和Pin相关,于是编译器给!Unpin的类型开了洞,关闭了别名模型对它们的要求(也就不对它们附加noalias声明了)。后来Ralf又提出了UnsafeAliased(现在改成UnsafePinned了)作为关闭别名要求的原语,它稳定后这块的实现会干净些。

至于现状我也不清楚了。

az 2024-12-20 22:21

我也关注过这个话题,查过一些相关的内容。但LLVM我不懂,权当抛转引玉吧。

https://gist.github.com/Darksonn/1567538f56af1a8038ecc3c664a42462

↑这是Alice遇到帖子上的问题时写的一篇文章,基本把整个问题说清楚了。我按个人理解简单归纳下:通过侵入式链表保存waker可以避免大量的堆分配,但是它在Tokio的场景下会违反Rust的别名模型,而且Rust也没有暴露任何放松别名模型要求的原语,这种实现是unsound的。(其实unsafe链表不违反别名模型,Tokio这里的情况比较特殊)

因为Generator也有类似Tokio的问题,而且它们都和Pin相关,于是编译器给!Unpin的类型开了洞,关闭了别名模型对它们的要求(也就不对它们附加noalias声明了)。后来Ralf又提出了UnsafeAliased(现在改成UnsafePinned了)作为关闭别名要求的原语,它稳定后这块的实现会干净些。

至于现状我也不清楚了。

wangbyby 2024-12-19 21:41

noalias是一个函数参数属性,专门为ptr类型设计的。 define void foo(ptr noalias %a)... 说明指针a不和其他指针指向同一内存区域。 llvm有个pass可以推断noalias信息,名字应该是funcattr什么的。

  1. aa分析确实挺耗时。但应该是为了语义才不加的。找个具体例子会好点
  2. 可以看看llvm aliasanalysis一章
TinusgragLin 2024-12-19 17:18

谷歌搜了一下 llvm noalias ,在这个 LLVM 文档页面上有相关的定义,这个 noalias(no alias,“没有别名”),我猜是来自“在编程语言中,我们用名字来称呼值”这种说法,所以说一个值“没有别名”意思可能就是说它只在一处被用到。

他们要在这里去掉 noalias 标记很可能与“双向链表中存放每一个节点的内存区域都会在两个地方被引用”有关。

至于为啥要加 PhantomPinned,应该是看到了这里那个“如果是 !Unpin 那我们就不加 noalias 了”。

1 共 4 条评论, 1 页