再次感谢开源安全公司和Embecosm对该项目的持续支持。
2月对GCC来说是一个重要的月份;我们之前的谷歌代码暑期学生阿瑟·科恩(Arthur Cohen)在德国的Embecosm加入我们,全职从事编译器的工作。有了额外的资源,我们可以拆分工作并委派任务,从而允许进行多个复杂的工作流,这将使Philip能够处理类型系统中的切片和bug。
关于我们的宏扩展里程碑,我们已经准备好了构建块,并且支持大多数声明性宏,尽管仍然有一些怪癖和错误需要解决。这个里程碑的剩余时间将用于设置内置宏和修复bug。
除了宏之外,2月份的工作重点是代码清理和bug修复,这是富有成效的。
是我们下一个社区电话的时候了,欢迎加入!🙂
添加对索引和范围项目的支持,以及用于切片类型检查PR974的锅炉板
我们已经改进了我们的规范路径跟踪,以便我们可以为遗留的损坏方案建立路径。例如,嵌套在模块下的impl块在其路径中被赋予一个impl前缀。
struct Foo(i32);mod A{impl Foo{fn test(&;self)->i32{self.0}}}fn test(){let A=Foo(123);let b:i32=A.test();}
如您所见,我们有示例的板条箱名称->;结构A->;impl块例如::A->;函数名测试。
i32示例::A::<;impl示例::Foo>;::test(const struct example::Foo&;const self){i32 D.85;D.85=self->;0;返回D.85;}void example::test(){const struct example::Foo a;const i32 b;try{a.0=123;b=example::a:<;impl example::Foo>;::test(&;a);}最后{a={CLOBBER};}
我们在cfg扩展中添加了对any、all和not谓词的支持,因此在本例中,这确保了为all谓词指定A和B。
struct Foo;impl Foo{#[cfg(all(A,B))]fn test(&;self){}fn main(){let A=Foo;A.test()}
Rust允许我们为配置扩展指定键值对,这主要与主机/os/cpu选项有关,例如os=“linux”,但下面是一个示例,您可以在编译器资源管理器中尝试。
struct Foo;impl Foo{#[cfg(A=";B";)]fn test(&;self){}fn main(){let a=Foo;a.test();}
不带任何选项的Inline类似于C风格的Inline关键字,它向编译器提示此函数是内联的良好候选函数。Inline always可以通过GCC的Inline always属性实现:https://gcc.gnu.org/onlinedocs/gcc/Inline.html.最后,我们永远不能将函数标记为DECL_不可线性。一个区别是,内联优化需要启用优化。因此,当在-O0编译时,不会发生内联,任何级别大于此级别,都将强制执行内联传递。
方法解析方面的工作一直在稳步进行,我们现在支持deref_mut lang项目,因此对于需要a&;我们尝试从任何相关的接收者处获取所需的间接引用。
外部人员";C";{fn printf(s:*const i8,…);}#[lang=";deref";]pub trait Deref{type Target;fn Deref(&;self)->self::Target;}#[lang=";deref#mut";]pub trait DerefMut:Deref{fn Deref_mut(&;mut self)>;&;mut self::Target;}impl<;T>;德雷夫;T{type Target=T;fn-deref(&;self)>;&;T{*self}impl<;T>;德雷夫;mut T{type Target=T;fn deref(&;self)>;&;T{*self}发布结构条(i32);impl Bar{pub fn foobar(&;mut self)->i32{self.0}pub struct Foo<;T>;(T) );impl<;T>;为Foo<;T>;{type Target=T;fn deref(&;self)>;&;self::Target{&;self.0}}impl<;T>;对Foo<;T>;{fn deref_mut(&;mut self)>;&;mut self::Target{safe{let a=&#34;mut_deref\n\0";;let b=a as*const str;let c=b as*const i8;printf(c);}&;莫特·赛尔夫。0}pub fn main()->;i32{let bar=bar(123);let mut foo:foo<;bar>;=foo(bar);let foobar=foo.foobar();foobar-123}
我们已经合并了我们的第一个宏观扩张通道。这里采用的方法是,我们重用现有的解析器来调用作为MacroFragmentType枚举的一部分指定的适当函数。如果解析器在解析该项时没有错误,那么它必须是匹配的。然后,一旦我们匹配了一个规则,我们就有了每个片段匹配的令牌开始/结束偏移的映射,然后用它来调整和创建宏规则定义的新令牌流,这样当我们将其提供给解析器时,令牌就已经被替换了。生成的表达式或项随后附加到相应的宏调用,然后解析名称并用于hir降低。
在这个例子中,宏有两个规则,所以我们证明我们匹配了适当的规则,并分别转录了它。
宏规则!添加{($a:expr,$b:expr)=>;{$a+$b};($a:expr)=>;{$a};}fn main()->;i32{let mut x=add!(1);x+=add!(2,3);x-6}
宏规则!测试{($a:ident,$b:ty)=>;{struct$a($b);};}测验(富,i32);fn main()->;i32{let a=Foo(123);a.0-123}
这里我们考虑宏调用的上下文,并将其解析为AST::Items。如果未能匹配规则,编译器错误如下所示:
<;来源>;:11:17:错误:未能匹配宏1 |宏|规则中的任何规则!加{| ~..11 |让mut x=add!(1,2,3)|^
当转录规则实际上没有完全使用时,添加了更多错误处理,例如:
在rust中,范围被转换为结构,因此,指定某种约束的语法片段实际上是可以赋值和操作的。这是我们支持切片过程中的一个组成部分。
#[lang=";RangeFull";]pub struct RangeFull#[lang=";范围";]pub struct Range<;Idx>;{pub start:Idx,pub end:Idx,}#[lang=";RangeFrom";]pub struct RangeFrom<;Idx>;{pub start:Idx,}#[lang=";RangeTo";]酒吧结构RangeTo<;Idx>;{pub end:Idx,}#[lang=";range inclusive";]pub struct range inclusive<;Idx>;{pub start:Idx,pub end:Idx,}fn test(){let a=1..2;//range let b=1..;//range from let c=..3;//range to let d=0..=2;//range inclusive}
支持切片的另一个构建块是支持索引lang item core::ops::index的能力,这样一个范围就可以作为参数,core::slice::index中的代码实际上可以成为从数组中获取切片的起点。
#[lang=";索引";]特质指数<;Idx>;{type Output;fn index(&;self,index:Idx)->;&;self::Output;}struct Foo(i32,i32);impl索引<;isize>;对于Foo{type Output=i32;fn index(&;self,index:isize)->;i32{let a=Foo(1,2);let b=a[0];let c=a[1];c-b-1}
宏匹配臂可以包含重复运算符,它指示传递单个标记或元变量的多个实例的可能性。
您可以使用Kleene运算符表示此类重复:有三种变体可用,?,+及*。每一个都对应于与运算符关联的标记数量的不同界限,类似于正则表达式。
宏规则!kleene{($a:ident$(,)?)=>;{{};($($i:literal tok)+)=>;{{ }}; ($($e:expr)*)=>;{{ }};}
单数标识符,带有零个或一个逗号(模式:<;逗号>;,kleene运算符:?(0->;1))
一个或多个文本后跟分隔符tok(模式$i:literal tok,kleene运算符:+(1->;+inf))
实现宏重复的第一步是匹配给定给用户的实际模式。我们现在能够匹配简单的重复,但仍有一些限制和缺陷。例如,Rust引用指定了在片段说明符之后使用的有效分隔符,我们还没有检查这些分隔符。例如,禁止在$<>;:expr说明符,因为这可能会导致歧义:表达式后唯一允许的分隔符是=>&书信电报;逗号>;或
一旦匹配了这些重复模式,就很容易计算出用户给出了多少重复模式。我们将这些数据与片段的其余部分一起存储,以确保在转录时将所述模式扩展正确的次数。
我们将匹配重复3次,并将重复量3归因于$e元变量。
在匹配这些重复之后,我们可以递归地扩展模式中包含的所有标记。
再次考虑前面的声明和调用,我们可以将以下模式解析为要展开的模式:
然后,该模式被递归地扩展,就好像它是一个常规的宏调用一样。为了确保每个元变量得到正确的扩展,我们只给新的替代上下文提供匹配片段的子集。
宏规则!lit_plus_tok{($($e:literal tok)*)=>;{}lit_plus_tok!(";rustc";tok';v';tok 1.59 tok);//原始匹配片段:{";lit";:[";rustc";,';v';,1.59]}//然后我们用{";lit";:[";rustc";]曾经与{";lit";:[';v';]},//最后一次是{";lit";:[1.59]},
同样,我们还没有实现某些限制:一些说明符急切地得到扩展,而一些保留在用户输入的表单下。
同样,并不是所有的重复模式都被恰当地覆盖。为了全面、正确地实施,有些问题仍有待解决。
为了实现某些特定的行为,rust标准库需要在编译器中内置一些宏。你可以在这里找到完整的清单。
GCCR的实施应考虑到标准rust库的编译,因为core和std都依赖于它们。
这些宏在核心库中定义为空,它们的转录器在编译器中作为一个简单函数提供。我们在GCCR中实现这些内置函数,作为返回抽象语法树片段的函数,这些片段在宏扩展阶段插入,然后与用户代码的其余部分一起降低到中间表示形式。
我们面前有一长串宏,其中一些应该可以轻松实现。如果你有兴趣参与进来,我们已经打开了关于内置宏的3个好的第一个问题,以及如何解决它们的详细指南。
非常感谢bjorn3对内置宏及其实现细节的帮助。