前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >egg教程(二):getting_started

egg教程(二):getting_started

作者头像
安全锚Anchor
发布2023-10-17 12:25:57
2100
发布2023-10-17 12:25:57
举报
文章被收录于专栏:安全基础

本教程的目的是让您即使没有什么 Rust 经验,也能开始使用 egg。如果你还没听说过e-graph,可以先阅读背景教程。如果你有 Rust 相关经验,可以略读本节。

开始使用Rust

Rust 是 egg 快速(系统编程 + 优化编译器)和灵活(泛型和特质)的原因之一。

Rust 的工作人员为学习 Rust 编写了一本免费的好书,Rust 网站的 "Learn "页面上还收集了大量其他精彩资源。本教程不能替代这些资源,而是旨在让你尽快上手。

首先,安装 Rust,然后使用 Rust 的软件包管理和构建工具 cargo 创建一个项目: cargo new my-first-egg.1

现在,我们可以在 Cargo.toml 中添加一行,将 egg 添加为项目依赖关系:

代码语言:javascript
复制
[dependencies]
egg = "0.9.5"

下面的代码示例都可以使用,但您必须使用相关类型。您只需在文件顶部添加 use egg::*;,即可将它们全部引入。

现在你说的是我的Language

EGraphs(以及该板块中的几乎所有其他内容)都是根据用户提供的语言进行参数化的。虽然 egg 支持轻松创建自己的语言,但我们将从提供的 SymbolLang 开始。

语言是一种特质,实现语言的类型的值就是 e-node。一个 e-node 可以有任意数量的子节点,这些子节点就是 Id。Id 基本上只是一个数字,egg 用它来协调 e-node 与哪些子节点相关联。在 EGraph 中,e-node的子节点指的是 e-class。在 RecExpr(egg 版本的普通表达式)中,e-node 的子节点指的是该 RecExpr 中的其他 e-node。

包括 SymbolLang 在内的大多数语言都可以进行解析和漂亮打印。这意味着这些语言中的 RecExpr 实现了 Rust 标准库中的 FromStr 和 Display 特性。

代码语言:javascript
复制
// Since parsing can return an error, `unwrap` just panics if the result doesn't return Ok
let my_expression: RecExpr<SymbolLang> = "(foo a b)".parse().unwrap();
println!("this is my expression {}", my_expression);

// let's try to create an e-node, but hmmm, what do I put as the children?
let my_enode = SymbolLang::new("bar", vec![]);

有些 e-node 只是常量,没有子节点(也称为叶子)。但是,如果单独创建有子节点的 e 节点,就会显得有些别扭,因为你必须添加无意义的 Ids 作为子节点。创建有意义 Ids 的方法是将 e-nodes 添加到 EGraph 或 RecExpr 中:

代码语言:javascript
复制
let mut expr = RecExpr::default();
let a = expr.add(SymbolLang::leaf("a"));
let b = expr.add(SymbolLang::leaf("b"));
let foo = expr.add(SymbolLang::new("foo", vec![a, b]));

// we can do the same thing with an EGraph
let mut egraph: EGraph<SymbolLang, ()> = Default::default();
let a = egraph.add(SymbolLang::leaf("a"));
let b = egraph.add(SymbolLang::leaf("b"));
let foo = egraph.add(SymbolLang::new("foo", vec![a, b]));

// we can also add RecExprs to an egraph
let foo2 = egraph.add_expr(&expr);
// note that if you add the same thing to an e-graph twice, you'll get back equivalent Ids
assert_eq!(foo, foo2);

使用Patterns搜索 EGraph

既然我们可以在 EGraph 中添加内容,那就让我们看看能否找到它。我们将使用实现了 Searcher 特性的 Pattern 在e-graph中搜索匹配项:

代码语言:javascript
复制
// let's make an e-graph
let mut egraph: EGraph<SymbolLang, ()> = Default::default();
let a = egraph.add(SymbolLang::leaf("a"));
let b = egraph.add(SymbolLang::leaf("b"));
let foo = egraph.add(SymbolLang::new("foo", vec![a, b]));

// rebuild the e-graph since we modified it
egraph.rebuild();

// we can make Patterns by parsing, similar to RecExprs
// names preceded by ? are parsed as Pattern variables and will match anything
let pat: Pattern<SymbolLang> = "(foo ?x ?x)".parse().unwrap();

// since we use ?x twice, it must match the same thing,
// so this search will return nothing
let matches = pat.search(&egraph);
assert!(matches.is_empty());

egraph.union(a, b);
// recall that rebuild must be called to "see" the effects of adds or unions
egraph.rebuild();

// now we can find a match since a = b
let matches = pat.search(&egraph);
assert!(!matches.is_empty());

使用 Runner 制作优化器

既然我们已经可以创建模式并使用 RecExprs,那么就可以创建一个优化器了!我们将使用 rewrite!宏来轻松创建 Rewrites,其中包括名称、左侧搜索模式和右侧应用模式。然后,我们可以使用 Runner API 运行 egg 的相等饱和算法。最后,我们可以使用提取器获得最佳结果。

代码语言:javascript
复制
use egg::{*, rewrite as rw};

let rules: &[Rewrite<SymbolLang, ()>] = &[
    rw!("commute-add"; "(+ ?x ?y)" => "(+ ?y ?x)"),
    rw!("commute-mul"; "(* ?x ?y)" => "(* ?y ?x)"),

    rw!("add-0"; "(+ ?x 0)" => "?x"),
    rw!("mul-0"; "(* ?x 0)" => "0"),
    rw!("mul-1"; "(* ?x 1)" => "?x"),
];

// While it may look like we are working with numbers,
// SymbolLang stores everything as strings.
// We can make our own Language later to work with other types.
let start = "(+ 0 (* 1 a))".parse().unwrap();

// That's it! We can run equality saturation now.
let runner = Runner::default().with_expr(&start).run(rules);

// Extractors can take a user-defined cost function,
// we'll use the egg-provided AstSize for now
let extractor = Extractor::new(&runner.egraph, AstSize);

// We want to extract the best expression represented in the
// same e-class as our initial expression, not from the whole e-graph.
// Luckily the runner stores the eclass Id where we put the initial expression.
let (best_cost, best_expr) = extractor.find_best(runner.roots[0]);

// we found the best thing, which is just "a" in this case
assert_eq!(best_expr, "a".parse().unwrap());
assert_eq!(best_cost, 1);

本文系外文翻译,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系外文翻译前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 开始使用Rust
  • 现在你说的是我的Language
  • 使用Patterns搜索 EGraph
  • 使用 Runner 制作优化器
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档