lib/choir/src/eval/evaluator.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_arena = @import("alloc_arena");
3 const alloc_observe = @import("alloc_observe");
4 const ir = @import("../core/root.zig");
5 const interfaces = @import("../core/root.zig").interfaces;
6 const rewrite_builder = @import("rewrite.zig");
7
8 pub const Evaluator = struct {
9 allocator: std.mem.Allocator,
10 ir_ctx: *ir.Context,
11
12 branch_fuel: u32 = 10_000,
13 iteration_fuel: u32 = 100_000,
14 recursion_depth: u32 = 0,
15 max_recursion: u32 = 256,
16 allocation_used: u64 = 0,
17 allocation_cap: u64 = 64 * 1024 * 1024,
18
19 comptime_gpa: alloc_observe.debug.Allocator(.{}),
20
21 current_loc: ir.Location,
22 diagnostics: std.ArrayListUnmanaged(Diagnostic),
23
24 frames: std.ArrayListUnmanaged(ValueFrame),
25
26 root_op: ?*ir.Operation = null,
27 symbol_table: ir.SymbolTable,
28
29 handle_table: std.ArrayList(HandleEntry),
30 rewrite_builders: std.ArrayListUnmanaged(*rewrite_builder.RewriteBuilder),
31
32 pub const Diagnostic = struct {
33 kind: interfaces.DiagnosticKind,
34 message: []const u8,
35 loc: ir.Location,
36 };
37
38 pub const HandleEntry = struct {
39 slice: []u8,
40 valid: bool = true,
41 };
42
43 pub const ValueFrame = struct {
44 values: std.AutoHashMap(*ir.Value, ir.Attribute),
45 inherits_parent: bool = false,
46 };
47
48 pub const EvalError = error{
49 UnknownEffect,
50 EffectViolation,
51 LocationViolation,
52 DeviceLocationForbidden,
53 UnifiedLocationForbidden,
54 BranchQuotaExceeded,
55 IterationQuotaExceeded,
56 RecursionDepthExceeded,
57 InvalidOperand,
58 InvalidConstant,
59 DivisionByZero,
60 InvalidPredicate,
61 InvalidShiftAmount,
62 InvalidSize,
63 NegativeSize,
64 InvalidCondition,
65 UnsupportedOperation,
66 EvaluationFailed,
67 YieldMissingOperand,
68 NoYield,
69 ValueNotFound,
70 InvalidHandle,
71 HandleAlreadyDropped,
72 BorrowOfInvalidHandle,
73 MoveOfInvalidHandle,
74 OutOfMemory,
75 };
76
77 pub fn init(allocator: std.mem.Allocator, ir_ctx: *ir.Context) Evaluator {
78 return .{
79 .allocator = allocator,
80 .ir_ctx = ir_ctx,
81 .comptime_gpa = .init(allocator),
82 .current_loc = ir.Location.getUnknown(),
83 .frames = .empty,
84 .symbol_table = ir.SymbolTable.init(allocator),
85 .handle_table = .empty,
86 .diagnostics = .empty,
87 .rewrite_builders = .empty,
88 };
89 }
90
91 pub fn initMeta(allocator: std.mem.Allocator, ir_ctx: *ir.Context) Evaluator {
92 return init(allocator, ir_ctx);
93 }
94
95 pub fn deinit(self: *Evaluator) void {
96 self.discardRewriteBuilders();
97 for (self.frames.items) |*frame| {
98 frame.values.deinit();
99 }
100 self.frames.deinit(self.allocator);
101 self.symbol_table.deinit();
102 for (self.handle_table.items) |h| {
103 self.comptime_gpa.allocator().free(h.slice);
104 }
105 self.handle_table.deinit(self.allocator);
106 for (self.diagnostics.items) |diag| {
107 self.allocator.free(@constCast(diag.message));
108 }
109 self.diagnostics.deinit(self.allocator);
110 _ = self.comptime_gpa.deinit();
111 }
112
113 pub fn getDiagnostics(self: *const Evaluator) []const Diagnostic {
114 return self.diagnostics.items;
115 }
116
117 pub fn clearDiagnostics(self: *Evaluator) void {
118 for (self.diagnostics.items) |diag| {
119 self.allocator.free(@constCast(diag.message));
120 }
121 self.diagnostics.clearRetainingCapacity();
122 }
123
124 pub fn evaluate(self: *Evaluator, op: *ir.Operation) EvalError!ir.Attribute {
125 self.current_loc = op.location;
126 const num_operands = op.getNumOperands();
127 const iface = op.interface(interfaces.Evaluatable) orelse {
128 emitUnsupportedDiagnostic(self, op, num_operands, "missing Evaluatable interface");
129 return error.UnsupportedOperation;
130 };
131 if (!iface.call(.canEval, .{})) {
132 emitUnsupportedDiagnostic(self, op, num_operands, "canEval returned false");
133 return error.UnsupportedOperation;
134 }
135
136 var operand_stack: [16]ir.Attribute = undefined;
137 var operand_attrs: []ir.Attribute = operand_stack[0..@min(num_operands, operand_stack.len)];
138 var heap_operands: ?[]ir.Attribute = null;
139 if (num_operands > operand_stack.len) {
140 const allocated = self.allocator.alloc(ir.Attribute, num_operands) catch |err| {
141 emitUnsupportedDiagnostic(self, op, num_operands, "failed to allocate operand buffer");
142 return err;
143 };
144 heap_operands = allocated;
145 operand_attrs = allocated;
146 }
147 defer if (heap_operands) |buf| self.allocator.free(buf);
148
149 for (0..num_operands) |i| {
150 const operand = op.getOperand(@intCast(i)) orelse return error.InvalidOperand;
151 operand_attrs[i] = if (self.getValue(operand)) |attr| attr else resolved_attr: {
152 if (operand.getDefiningOp()) |op_ptr| {
153 const defining_op: *ir.Operation = @ptrCast(@alignCast(op_ptr));
154 const result = try self.evaluate(defining_op);
155 try self.registerResults(defining_op, result);
156 if (self.getValue(operand)) |attr| break :resolved_attr attr;
157 }
158 return error.ValueNotFound;
159 };
160 }
161
162 const ctx = self.buildEvalContext();
163 const result = iface.call(.evaluate, .{ operand_attrs[0..num_operands], &ctx }) catch |err| {
164 return mapEvalInterfaceError(err);
165 };
166
167 return result;
168 }
169
170 fn enterCall(self: *Evaluator) !void {
171 if (self.recursion_depth >= self.max_recursion) return error.RecursionDepthExceeded;
172 self.recursion_depth += 1;
173 }
174
175 fn exitCall(self: *Evaluator) void {
176 if (self.recursion_depth > 0) {
177 self.recursion_depth -= 1;
178 }
179 }
180
181 fn addDiagnostic(self: *Evaluator, kind: interfaces.DiagnosticKind, message: []const u8) !void {
182 const owned_message = try self.allocator.dupe(u8, message);
183 try self.diagnostics.append(self.allocator, .{
184 .kind = kind,
185 .message = owned_message,
186 .loc = self.current_loc,
187 });
188 }
189
190 fn ensureFrame(self: *Evaluator) !*ValueFrame {
191 if (self.frames.items.len == 0) {
192 try self.frames.append(self.allocator, .{
193 .values = std.AutoHashMap(*ir.Value, ir.Attribute).init(self.allocator),
194 });
195 }
196 return &self.frames.items[self.frames.items.len - 1];
197 }
198
199 fn pushFrame(self: *Evaluator, inherits_parent: bool) !void {
200 try self.frames.append(self.allocator, .{
201 .inherits_parent = inherits_parent,
202 .values = std.AutoHashMap(*ir.Value, ir.Attribute).init(self.allocator),
203 });
204 }
205
206 fn popFrame(self: *Evaluator) void {
207 var frame = self.frames.pop() orelse return;
208 frame.values.deinit();
209 }
210
211 /// A structured region reads lexical parents, but never crosses a call.
212 pub fn getValue(self: *Evaluator, value: *ir.Value) ?ir.Attribute {
213 var remaining = self.frames.items.len;
214 while (remaining > 0) {
215 remaining -= 1;
216 const frame = &self.frames.items[remaining];
217 if (frame.values.get(value)) |attr| return attr;
218 if (!frame.inherits_parent) break;
219 }
220 return null;
221 }
222
223 pub fn setValue(self: *Evaluator, value: *ir.Value, attr: ir.Attribute) !void {
224 const frame = try self.ensureFrame();
225 try frame.values.put(value, attr);
226 }
227
228 pub fn setRootOperation(self: *Evaluator, op: *ir.Operation) !void {
229 self.root_op = op;
230 try self.symbol_table.buildFromOperation(op);
231 }
232
233 fn finishRewriteBuilders(self: *Evaluator, finalize: bool) void {
234 for (self.rewrite_builders.items) |builder| {
235 if (finalize) {
236 builder.finalize();
237 }
238 builder.deinit();
239 self.allocator.destroy(builder);
240 }
241 self.rewrite_builders.deinit(self.allocator);
242 self.rewrite_builders = .empty;
243 }
244
245 pub fn finalizeRewriteBuilders(self: *Evaluator) void {
246 self.finishRewriteBuilders(true);
247 }
248
249 pub fn discardRewriteBuilders(self: *Evaluator) void {
250 self.finishRewriteBuilders(false);
251 }
252
253 fn buildEvalContext(self: *Evaluator) interfaces.EvalContext {
254 return .{
255 .state = self,
256 .allocator = self.allocator,
257 .emitDiagnostic = evalContextEmitDiagnostic,
258 .evaluateRegion = evalContextEvaluateRegion,
259 .evaluateRegionWithArgs = evalContextEvaluateRegionWithArgs,
260 .evaluateSymbol = evalContextEvaluateSymbol,
261 .consumeBranchFuel = evalContextConsumeBranchFuel,
262 .consumeIterationFuel = evalContextConsumeIterationFuel,
263 .allocHandle = evalContextAllocHandle,
264 .borrowHandle = evalContextBorrowHandle,
265 .borrowMutHandle = evalContextBorrowMutHandle,
266 .moveHandle = evalContextMoveHandle,
267 .dropHandle = evalContextDropHandle,
268 .createRewriteBuilder = evalContextCreateRewriteBuilder,
269 };
270 }
271
272 fn evaluateRegionInternal(self: *Evaluator, region: *ir.Region) EvalError!ir.Attribute {
273 if (region.blocks.head) |block_opaque| {
274 const block: *ir.Block = @ptrCast(@alignCast(block_opaque));
275 var op_ptr = block.operations.head;
276 while (op_ptr) |o_opaque| {
277 const op: *ir.Operation = @ptrCast(@alignCast(o_opaque));
278
279 const result = try self.evaluate(op);
280
281 try self.registerResults(op, result);
282
283 if (op.hasTrait("is_terminator")) {
284 return result;
285 }
286
287 op_ptr = op.next_op;
288 }
289 }
290 return error.NoYield;
291 }
292
293 fn registerResults(self: *Evaluator, op: *ir.Operation, result: ir.Attribute) EvalError!void {
294 const num_results = op.getNumResults();
295 if (num_results == 0) return;
296 if (num_results == 1) {
297 const res = op.getResult(0) orelse return error.InvalidOperand;
298 try self.setValue(res, result);
299 return;
300 }
301 const iface = result.interface(interfaces.AttributeArrayInterface) orelse return error.InvalidOperand;
302 const count = iface.call(.getCount, .{});
303 if (count != num_results) return error.InvalidOperand;
304 var i: usize = 0;
305 while (i < count) : (i += 1) {
306 const attr = iface.call(.getElement, .{i}) orelse return error.InvalidOperand;
307 const res = op.getResult(@intCast(i)) orelse return error.InvalidOperand;
308 try self.setValue(res, attr);
309 }
310 }
311
312 fn bindBlockArgs(self: *Evaluator, region: *ir.Region, args: []const ir.Attribute) EvalError!void {
313 const block = region.getEntryBlock() orelse return error.NoYield;
314 if (block.arguments.items.len != args.len) return error.InvalidOperand;
315 for (args, 0..) |arg, i| {
316 const arg_value = block.getArgument(i) orelse return error.InvalidOperand;
317 try self.setValue(arg_value, arg);
318 }
319 }
320
321 fn evaluateRegionWithArgs(self: *Evaluator, region: *ir.Region, args: []const ir.Attribute) EvalError!ir.Attribute {
322 try self.pushFrame(true);
323 defer self.popFrame();
324 try self.bindBlockArgs(region, args);
325 return try self.evaluateRegionInternal(region);
326 }
327
328 pub fn evaluateRegion(self: *Evaluator, region: *ir.Region) EvalError!ir.Attribute {
329 return self.evaluateRegionWithArgs(region, &.{});
330 }
331
332 fn evaluateFunction(self: *Evaluator, func_op: *ir.Operation, args: []const ir.Attribute) EvalError!ir.Attribute {
333 try self.enterCall();
334 defer self.exitCall();
335 const region = func_op.getRegion(0) orelse return error.NoYield;
336 try self.pushFrame(false);
337 defer self.popFrame();
338 try self.bindBlockArgs(region, args);
339 return self.evaluateRegionInternal(region);
340 }
341
342 pub fn evaluateFunctionOp(self: *Evaluator, func_op: *ir.Operation, args: []const ir.Attribute) EvalError!ir.Attribute {
343 return self.evaluateFunction(func_op, args);
344 }
345
346 pub fn evaluateSymbol(self: *Evaluator, symbol: []const u8, args: []const ir.Attribute) EvalError!ir.Attribute {
347 if (self.root_op == null) return error.UnsupportedOperation;
348 if (self.symbol_table.lookup(symbol)) |func_op| {
349 return self.evaluateFunction(func_op, args);
350 }
351 return error.UnsupportedOperation;
352 }
353
354 pub fn evaluateSymbolOptional(
355 self: *Evaluator,
356 symbol: []const u8,
357 args: []const ir.Attribute,
358 ) EvalError!?ir.Attribute {
359 if (self.root_op == null) return null;
360 if (self.symbol_table.lookup(symbol)) |func_op| {
361 return try self.evaluateFunction(func_op, args);
362 }
363 return null;
364 }
365 };
366
367 fn emitUnsupportedDiagnostic(
368 evaluator: *Evaluator,
369 op: *ir.Operation,
370 num_operands: usize,
371 reason: []const u8,
372 ) void {
373 const msg = std.fmt.allocPrint(
374 evaluator.allocator,
375 "cannot evaluate '{s}' ({d} operands): {s}",
376 .{ op.name.name, num_operands, reason },
377 ) catch return;
378 defer evaluator.allocator.free(msg);
379 evaluator.addDiagnostic(.error_, msg) catch {};
380 }
381
382 fn mapEvalInterfaceError(err: interfaces.EvalError) Evaluator.EvalError {
383 return switch (err) {
384 error.UnsupportedOperation => error.UnsupportedOperation,
385 error.UnknownEffect => error.UnknownEffect,
386 error.EffectViolation => error.EffectViolation,
387 error.LocationViolation => error.LocationViolation,
388 error.DivisionByZero => error.DivisionByZero,
389 error.InvalidOperand => error.InvalidOperand,
390 error.InvalidConstant => error.InvalidConstant,
391 error.RequiresDynamicInfo => error.UnsupportedOperation,
392 error.InvalidShiftAmount => error.InvalidShiftAmount,
393 error.InvalidPredicate => error.InvalidPredicate,
394 error.EvaluationFailed => error.EvaluationFailed,
395 error.Overflow => error.UnsupportedOperation,
396 error.RecursionDepthExceeded => error.RecursionDepthExceeded,
397 error.InvalidCondition => error.InvalidCondition,
398 error.InvalidSize => error.InvalidSize,
399 error.NegativeSize => error.NegativeSize,
400 error.InvalidHandle => error.InvalidHandle,
401 error.HandleAlreadyDropped => error.HandleAlreadyDropped,
402 error.BorrowOfInvalidHandle => error.BorrowOfInvalidHandle,
403 error.MoveOfInvalidHandle => error.MoveOfInvalidHandle,
404 error.BranchQuotaExceeded => error.BranchQuotaExceeded,
405 error.IterationQuotaExceeded => error.IterationQuotaExceeded,
406 error.DeviceLocationForbidden => error.DeviceLocationForbidden,
407 error.UnifiedLocationForbidden => error.UnifiedLocationForbidden,
408 error.YieldMissingOperand => error.YieldMissingOperand,
409 error.NoYield => error.NoYield,
410 error.ValueNotFound => error.ValueNotFound,
411 error.OutOfMemory => error.OutOfMemory,
412 };
413 }
414
415 fn mapEvalError(err: Evaluator.EvalError) interfaces.EvalError {
416 return switch (err) {
417 error.UnknownEffect => error.UnknownEffect,
418 error.EffectViolation => error.EffectViolation,
419 error.LocationViolation => error.LocationViolation,
420 error.DeviceLocationForbidden => error.DeviceLocationForbidden,
421 error.UnifiedLocationForbidden => error.UnifiedLocationForbidden,
422 error.BranchQuotaExceeded => error.BranchQuotaExceeded,
423 error.IterationQuotaExceeded => error.IterationQuotaExceeded,
424 error.RecursionDepthExceeded => error.RecursionDepthExceeded,
425 error.InvalidOperand => error.InvalidOperand,
426 error.InvalidConstant => error.InvalidConstant,
427 error.DivisionByZero => error.DivisionByZero,
428 error.InvalidPredicate => error.InvalidPredicate,
429 error.InvalidShiftAmount => error.InvalidShiftAmount,
430 error.InvalidSize => error.InvalidSize,
431 error.NegativeSize => error.NegativeSize,
432 error.InvalidCondition => error.InvalidCondition,
433 error.UnsupportedOperation => error.UnsupportedOperation,
434 error.EvaluationFailed => error.EvaluationFailed,
435 error.YieldMissingOperand => error.YieldMissingOperand,
436 error.NoYield => error.NoYield,
437 error.ValueNotFound => error.ValueNotFound,
438 error.InvalidHandle => error.InvalidHandle,
439 error.HandleAlreadyDropped => error.HandleAlreadyDropped,
440 error.BorrowOfInvalidHandle => error.BorrowOfInvalidHandle,
441 error.MoveOfInvalidHandle => error.MoveOfInvalidHandle,
442 error.OutOfMemory => error.OutOfMemory,
443 };
444 }
445
446 fn evalContextEmitDiagnostic(
447 state: *anyopaque,
448 kind: interfaces.DiagnosticKind,
449 message: []const u8,
450 ) interfaces.EvalError!void {
451 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
452 evaluator.addDiagnostic(kind, message) catch return error.OutOfMemory;
453 }
454
455 fn evalContextEvaluateRegion(state: *anyopaque, region_opaque: *const anyopaque) interfaces.EvalError!ir.Attribute {
456 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
457 const region_const: *const ir.Region = @ptrCast(@alignCast(region_opaque));
458 const region = @constCast(region_const);
459 return evaluator.evaluateRegion(region) catch |err| return mapEvalError(err);
460 }
461
462 fn evalContextEvaluateRegionWithArgs(
463 state: *anyopaque,
464 region_opaque: *const anyopaque,
465 args: []const ir.Attribute,
466 ) interfaces.EvalError!ir.Attribute {
467 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
468 const region_const: *const ir.Region = @ptrCast(@alignCast(region_opaque));
469 const region = @constCast(region_const);
470 return evaluator.evaluateRegionWithArgs(region, args) catch |err| return mapEvalError(err);
471 }
472
473 fn evalContextEvaluateSymbol(
474 state: *anyopaque,
475 symbol: []const u8,
476 args: []const ir.Attribute,
477 ) interfaces.EvalError!ir.Attribute {
478 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
479 return evaluator.evaluateSymbol(symbol, args) catch |err| return mapEvalError(err);
480 }
481
482 fn evalContextConsumeBranchFuel(state: *anyopaque) interfaces.EvalError!void {
483 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
484 if (evaluator.branch_fuel == 0) return error.BranchQuotaExceeded;
485 evaluator.branch_fuel -= 1;
486 }
487
488 fn evalContextConsumeIterationFuel(state: *anyopaque) interfaces.EvalError!void {
489 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
490 if (evaluator.iteration_fuel == 0) return error.IterationQuotaExceeded;
491 evaluator.iteration_fuel -= 1;
492 }
493
494 fn evalContextAllocHandle(state: *anyopaque, size: i64) interfaces.EvalError!i64 {
495 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
496 if (size < 0) return error.NegativeSize;
497
498 const slice = evaluator.comptime_gpa.allocator().alloc(u8, @intCast(size)) catch return error.OutOfMemory;
499 errdefer evaluator.comptime_gpa.allocator().free(slice);
500
501 evaluator.handle_table.append(evaluator.allocator, .{ .slice = slice }) catch return error.OutOfMemory;
502 return @intCast(evaluator.handle_table.items.len - 1);
503 }
504
505 fn evalContextBorrowHandle(state: *anyopaque, handle: i64) interfaces.EvalError!i64 {
506 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
507 if (handle < 0) return error.InvalidHandle;
508
509 const idx: usize = @intCast(handle);
510 if (idx >= evaluator.handle_table.items.len) return error.InvalidHandle;
511 if (!evaluator.handle_table.items[idx].valid) return error.BorrowOfInvalidHandle;
512
513 return @intCast(idx);
514 }
515
516 fn evalContextBorrowMutHandle(state: *anyopaque, handle: i64) interfaces.EvalError!i64 {
517 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
518 if (handle < 0) return error.InvalidHandle;
519
520 const idx: usize = @intCast(handle);
521 if (idx >= evaluator.handle_table.items.len) return error.InvalidHandle;
522 if (!evaluator.handle_table.items[idx].valid) return error.BorrowOfInvalidHandle;
523
524 return @intCast(idx);
525 }
526
527 fn evalContextMoveHandle(state: *anyopaque, handle: i64) interfaces.EvalError!i64 {
528 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
529 if (handle < 0) return error.InvalidHandle;
530
531 const idx: usize = @intCast(handle);
532 if (idx >= evaluator.handle_table.items.len) return error.InvalidHandle;
533 if (!evaluator.handle_table.items[idx].valid) return error.MoveOfInvalidHandle;
534
535 return @intCast(idx);
536 }
537
538 fn evalContextDropHandle(state: *anyopaque, handle: i64) interfaces.EvalError!void {
539 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
540 if (handle < 0) return error.InvalidHandle;
541
542 const idx: usize = @intCast(handle);
543 if (idx >= evaluator.handle_table.items.len) return error.InvalidHandle;
544 if (!evaluator.handle_table.items[idx].valid) return error.HandleAlreadyDropped;
545
546 evaluator.handle_table.items[idx].valid = false;
547 }
548
549 fn evalContextCreateRewriteBuilder(
550 state: *anyopaque,
551 root: *ir.Operation,
552 ) interfaces.EvalError!*anyopaque {
553 const evaluator: *Evaluator = @ptrCast(@alignCast(state));
554 const builder = evaluator.allocator.create(rewrite_builder.RewriteBuilder) catch return error.OutOfMemory;
555 builder.* = rewrite_builder.RewriteBuilder.init(
556 evaluator.allocator,
557 root.getContext(),
558 root,
559 );
560 evaluator.rewrite_builders.append(evaluator.allocator, builder) catch return error.OutOfMemory;
561 return @ptrCast(builder);
562 }
563
564 test "Evaluator handles variadic evaluatable ops beyond stack buffer" {
565 const testing = std.testing;
566 const test_dialect = @import("../dialects/fixture/root.zig");
567
568 var arena = alloc_arena.Arena.init(std.testing.allocator);
569 defer arena.deinit();
570 const allocator = arena.allocator();
571
572 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
573 defer ctx.deinit(allocator);
574
575 const loc = ir.Location.getUnknown();
576 const op_name = "test.sum";
577 _ = try ctx.registerOperation(op_name, .{});
578
579 const SumEval = struct {
580 fn canEval(op_ptr: *const anyopaque) bool {
581 _ = op_ptr;
582 return true;
583 }
584
585 fn evaluate(
586 op_ptr: *const anyopaque,
587 operands: []const ir.Attribute,
588 eval_ctx: *const interfaces.EvalContext,
589 ) interfaces.EvalError!ir.Attribute {
590 _ = eval_ctx;
591 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
592 const op_ctx = op.getContext();
593
594 var total: i64 = 0;
595 for (operands) |attr| {
596 const value = test_dialect.TestDialect.getIntegerValue(attr) orelse return error.InvalidOperand;
597 total += value;
598 }
599
600 return test_dialect.TestDialect.getIntegerAttr(op_ctx, total) catch |err| switch (err) {
601 error.OutOfMemory => error.OutOfMemory,
602 else => error.UnsupportedOperation,
603 };
604 }
605 };
606
607 try ctx.registerOperationInterface(
608 op_name,
609 interfaces.Evaluatable.entryFor(SumEval.canEval, SumEval.evaluate),
610 );
611
612 const result_type = try test_dialect.TestDialect.getI64Type(&ctx);
613 var constants: std.ArrayListUnmanaged(test_dialect.TestDialect.ConstantOp) = .empty;
614 defer constants.deinit(allocator);
615
616 var evaluator = Evaluator.init(allocator, &ctx);
617 defer evaluator.deinit();
618
619 const operand_count: usize = 32;
620 var expected_total: i64 = 0;
621 var operand_values = try allocator.alloc(*ir.Value, operand_count);
622 defer allocator.free(operand_values);
623
624 for (0..operand_count) |i| {
625 const value: i64 = @intCast(i + 1);
626 expected_total += value;
627 var constant = try test_dialect.TestDialect.ConstantOp.create(&ctx, loc, result_type, value);
628 try constants.append(allocator, constant);
629 operand_values[i] = constant.getResult();
630
631 const attr = try test_dialect.TestDialect.getIntegerAttr(&ctx, value);
632 try evaluator.setValue(operand_values[i], attr);
633 }
634
635 var builder = ir.OperationBuilder.init(&ctx);
636 var state = ir.Operation.State.init(op_name, loc);
637 state.addOperands(operand_values);
638 state.addTypes(&.{result_type});
639 const sum_op = try builder.create(state);
640
641 const result_attr = try evaluator.evaluate(sum_op);
642 try testing.expectEqual(@as(i64, expected_total), test_dialect.TestDialect.getIntegerValue(result_attr).?);
643 try testing.expectEqual(@as(usize, 0), evaluator.getDiagnostics().len);
644 }
645
646 test "Evaluator emits diagnostic for unsupported operations" {
647 const testing = std.testing;
648
649 var arena = alloc_arena.Arena.init(std.testing.allocator);
650 defer arena.deinit();
651 const allocator = arena.allocator();
652
653 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
654 defer ctx.deinit(allocator);
655 try ctx.allowUnregistered();
656
657 var evaluator = Evaluator.init(allocator, &ctx);
658 defer evaluator.deinit();
659
660 const loc = ir.Location.getUnknown();
661 var builder = ir.OperationBuilder.init(&ctx);
662 const op = try builder.create(ir.Operation.State.init("test.unknown_eval", loc));
663
664 try testing.expectError(error.UnsupportedOperation, evaluator.evaluate(op));
665
666 const diagnostics = evaluator.getDiagnostics();
667 try testing.expectEqual(@as(usize, 1), diagnostics.len);
668 try testing.expect(std.mem.indexOf(u8, diagnostics[0].message, "test.unknown_eval") != null);
669 try testing.expect(std.mem.indexOf(u8, diagnostics[0].message, "missing Evaluatable interface") != null);
670 }