-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathrewrite.zig
More file actions
306 lines (271 loc) · 10.4 KB
/
Copy pathrewrite.zig
File metadata and controls
306 lines (271 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
//! Contains the common rewrites pass. This pass will find basic patterns in
//! the graph, and convert them to another one. These rewrites won't always
//! be strict improvements in the graph, but they expose future passes to
//! find more advanced patterns.
const std = @import("std");
const SExpr = @import("rewrite/SExpr.zig");
const Oir = @import("../Oir.zig");
const log = std.log.scoped(.rewrite);
const Node = Oir.Node;
const Class = Oir.Class;
const assert = std.debug.assert;
const Rewrite = struct {
name: []const u8,
from: SExpr,
to: SExpr,
};
const RewriteError = error{ OutOfMemory, InvalidCharacter, Overflow };
const RewriteResult = struct {
root: Node.Index,
rw: Rewrite,
bindings: std.StringHashMapUnmanaged(Node.Index),
fn deinit(result: *RewriteResult, gpa: std.mem.Allocator) void {
result.bindings.deinit(gpa);
}
};
const rewrites: []const Rewrite = blk: {
const table: []const struct {
name: []const u8,
from: []const u8,
to: []const u8,
} = @import("rewrite/table.zon");
@setEvalBranchQuota(table.len * 20_000);
var list: [table.len]Rewrite = undefined;
for (&list, table) |*entry, op| {
entry.* = Rewrite{
.name = op.name,
.from = SExpr.parse(op.from),
.to = SExpr.parse(op.to),
};
}
const copy = list;
break :blk ©
};
pub fn run(oir: *Oir) !bool {
const gpa = oir.allocator;
var matches = std.ArrayList(RewriteResult).init(gpa);
defer {
for (matches.items) |*item| {
item.deinit(gpa);
}
matches.deinit();
}
{
const trace = oir.trace.start(@src(), "searching for matches", .{});
defer trace.end();
for (rewrites) |rewrite| {
const from_matches = try search(oir, rewrite);
defer gpa.free(from_matches);
try matches.appendSlice(from_matches);
}
}
const trace = oir.trace.start(@src(), "applying matches", .{});
defer trace.end();
log.debug("num matches: {d}", .{matches.items.len});
for (matches.items) |*item| {
log.debug(
"applying {} -> {} to {}",
.{ item.rw.from, item.rw.to, item.root },
);
if (try applyRewrite(oir, item.root, item.rw.to, &item.bindings)) {
log.debug("change happened!", .{});
return true;
}
}
return false;
}
fn search(oir: *Oir, rewrite: Rewrite) RewriteError![]RewriteResult {
const trace = oir.trace.start(@src(), "running search ({s})", .{rewrite.name});
defer trace.end();
const gpa = oir.allocator;
var matches = std.ArrayList(RewriteResult).init(gpa);
for (0..oir.nodes.items.len) |node_idx| {
const node_index: Node.Index = @enumFromInt(node_idx);
var bindings: std.StringHashMapUnmanaged(Node.Index) = .{};
const matched = try match(oir, node_index, rewrite.from, &bindings);
if (matched) try matches.append(.{
.root = node_index,
.rw = rewrite,
.bindings = bindings,
}) else bindings.deinit(gpa);
}
return matches.toOwnedSlice();
}
fn match(
oir: *Oir,
node_idx: Node.Index,
from: SExpr,
bindings: *std.StringHashMapUnmanaged(Node.Index),
) RewriteError!bool {
const trace = oir.trace.start(@src(), "finding match ({})", .{node_idx});
defer trace.end();
const allocator = oir.allocator;
const root_node = oir.getNode(node_idx);
switch (from.data) {
.list => |list| {
assert(list.len != 0); // there shouldn't be any empty lists
// we cant immediately tell that it isn't equal if the tags don't match.
// i.e, root_node is a (mul 10 20), and the pattern wants (div_exact ?x ?y)
// as you can see, they could never match.
if (root_node.tag != from.tag) return false;
// if the amount of children isn't equal, they couldn't match.
// i.e root_node is a (mul 10 20), and the pattern wants (abs ?x)
// this is more of a sanity check, since the tag check above would probably
// remove all cases of this.
const operands = root_node.operands(oir);
if (list.len != operands.len) return false;
// now we're left with a list of expressions and a graph.
// since the "out" field of the nodes is ordered from left to right, we're going to
// iterate through it inline with the expression list, and just recursively match with match()
for (operands, list) |sub_node_idx, expr| {
if (!try matchClass(oir, sub_node_idx, expr, bindings)) {
return false;
}
}
return true;
},
.atom => |constant| {
// is this an identifier?
if (constant[0] == '?') {
const identifier = constant[1..];
const gop = try bindings.getOrPut(allocator, identifier);
if (gop.found_existing) {
// we've already found this! is it the same as we found before?
// NOTE: you may think the order in which we match identifiers
// matters. fortunately, it doesn't! if "x" was found first,
// and was equal to 10, it doesn't matter if another "x" was
// found equal to 20. they would never match.
// if both nodes are in the same class, they *must* be equal.
// this is one of the reasons why we need to rebuild before
// doing rewrites, to allow checks like this.
return gop.value_ptr.* == node_idx;
} else {
// make sure to remember for further matches
gop.value_ptr.* = node_idx;
// we haven't seen this class yet. it's a match, since unique identifiers
// could mean anything.
return true;
}
} else {
// must be a number
if (root_node.tag != .constant) return false;
const value = root_node.data.constant;
const parsed_value = try std.fmt.parseInt(i64, constant, 10);
return value == parsed_value;
}
},
.builtin => |builtin| {
const tag = builtin.tag;
const param = builtin.expr;
if (tag.location() != .src) @panic("called dst builtin in matching");
switch (tag) {
.known_pow2 => {
const class_idx = oir.findClass(node_idx);
if (oir.classContains(class_idx, .constant)) |constant_idx| {
const constant_node = oir.getNode(constant_idx);
const value = constant_node.data.constant;
if (value > 0 and std.math.isPowerOfTwo(value)) {
try bindings.put(allocator, param, constant_idx);
return true;
}
}
return false;
},
else => unreachable,
}
},
}
}
/// Given an class index, returns whether any nodes in it match the given pattern.
fn matchClass(
oir: *Oir,
class_idx: Class.Index,
sub_pattern: SExpr,
bindings: *std.StringHashMapUnmanaged(Node.Index),
) RewriteError!bool {
const class = oir.getClassPtr(class_idx);
for (class.bag.items) |sub_node_idx| {
const is_match = try match(
oir,
sub_node_idx,
sub_pattern,
bindings,
);
if (is_match) return true;
}
return false;
}
/// Given the root node index and an expression to which it should be set,
/// we generate a class that represents the expression and then union it to
/// the class which the root node index is in.
///
/// Returns whether a union happened, indicating a change happened.
fn applyRewrite(
oir: *Oir,
root_node_idx: Node.Index,
to: SExpr,
bindings: *const std.StringHashMapUnmanaged(Node.Index),
) !bool {
const root_class = oir.findClass(root_node_idx);
switch (to.data) {
.list, .atom => {
const new_node = try expressionToNode(oir, to, bindings);
const new_class_idx = try oir.add(new_node);
return try oir.@"union"(root_class, new_class_idx);
},
.builtin => unreachable,
}
}
fn expressionToNode(
oir: *Oir,
expr: SExpr,
bindings: *const std.StringHashMapUnmanaged(Node.Index),
) !Node {
switch (expr.data) {
.list => |list| {
var node = Node.init(expr.tag, undefined);
for (list, 0..) |item, i| {
const sub_node = try expressionToNode(oir, item, bindings);
const sub_class_idx = try oir.add(sub_node);
node.mutableOperands(oir)[i] = sub_class_idx;
}
return node;
},
.atom => |atom| {
return node: {
if (atom[0] == '?') {
const ident = atom[1..];
const from_idx = bindings.get(ident).?;
break :node oir.getNode(from_idx);
} else {
const number = try std.fmt.parseInt(i64, atom, 10);
break :node .{
.tag = .constant,
.data = .{ .constant = number },
};
}
};
},
.builtin => |builtin| {
const tag = builtin.tag;
const param = builtin.expr;
if (tag.location() != .dst) @panic("called src builtin in applying");
switch (tag) {
.log2 => {
const constant_idx = bindings.get(param).?;
const constant_node = oir.getNode(constant_idx);
assert(constant_node.tag == .constant);
const value = constant_node.data.constant;
if (value < 1) @panic("how do we handle @log2 of a negative?");
const log_value = std.math.log2_int(u64, @intCast(value));
const new_node: Node = .{
.tag = .constant,
.data = .{ .constant = log_value },
};
return new_node;
},
else => unreachable,
}
},
}
}