lib/choir/src/dialects/func.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_arena = @import("alloc_arena");
3 const ir = @import("../core/root.zig");
4 const effects = ir.interfaces.effects;
5
6 pub const attr_names = struct {
7 pub const sym_name = "func.sym_name";
8 pub const func_type = "func.type";
9 pub const kernel = "func.kernel";
10 };
11
12 pub const op_attr_names = struct {
13 pub const sym_name = "sym_name";
14 pub const sym_visibility = ir.SymbolTable.symbol_attr_names.sym_visibility;
15 pub const kernel = "kernel";
16 pub const input_count = "input_count";
17 pub const input_types = "input_types";
18 pub const input_types_text = "input_types_text";
19 };
20
21 pub const FuncVerifyError = error{
22 MissingCallee,
23 UnresolvedCallee,
24 CalleeNotFunction,
25 CallOperandCountMismatch,
26 CallResultCountMismatch,
27 CallOperandTypeMismatch,
28 CallResultTypeMismatch,
29 MissingFunctionSignature,
30 SyscallArgumentCount,
31 SyscallMissingResult,
32 };
33
34 const FuncEval = struct {
35 fn canEval(op_ptr: *const anyopaque) bool {
36 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
37 return std.mem.eql(u8, op.name.name, FuncDialect.ReturnOp.operation_name) or
38 std.mem.eql(u8, op.name.name, FuncDialect.CallOp.operation_name);
39 }
40
41 fn evaluate(
42 op_ptr: *const anyopaque,
43 operands: []const ir.Attribute,
44 eval_ctx: *const ir.interfaces.EvalContext,
45 ) ir.interfaces.EvalError!ir.Attribute {
46 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
47 if (std.mem.eql(u8, op.name.name, FuncDialect.CallOp.operation_name)) {
48 const callee_ref = op.getAttrAs(ir.Attribute.SymbolRefAttr, "callee") orelse return error.InvalidOperand;
49 return eval_ctx.evaluateSymbol(eval_ctx.state, callee_ref.getLeafReference(), operands);
50 }
51 if (operands.len == 0) return error.YieldMissingOperand;
52 if (operands.len == 1) return operands[0];
53 return op.getContext().getArrayAttr(operands) catch error.OutOfMemory;
54 }
55
56 const vtable = ir.interfaces.Evaluatable.VTable{
57 .canEval = canEval,
58 .evaluate = evaluate,
59 };
60
61 fn fallback(_: *const ir.Operation) ?*const anyopaque {
62 return &vtable;
63 }
64 };
65
66 pub const FuncDialect = struct {
67 pub const name = "func";
68 const op_templates = ir.dialects.operationTemplate.dialect(@This());
69 pub const spec = ir.dialects.dialectSpec(@This(), .{
70 .op_interface_fallbacks = &.{
71 .{ .id = ir.interfaces.Evaluatable.id, .fallback = FuncEval.fallback },
72 },
73 });
74
75 const func_symbol_vtable = ir.interfaces.SymbolOpInterface.VTable{
76 .getSymbolName = getFuncSymbolName,
77 .setSymbolName = setFuncSymbolName,
78 .isDeclaration = isFuncDeclaration,
79 };
80
81 const function_vtable = ir.interfaces.FunctionOpInterface.VTable{
82 .hasBody = hasFunctionBody,
83 .getEntryBlock = getFunctionEntryBlock,
84 .getArgumentCount = getFunctionArgumentCount,
85 .getResultCount = getFunctionResultCount,
86 };
87
88 const call_vtable = ir.interfaces.CallOpInterface.VTable{
89 .getCalleeSymbol = getCallCalleeSymbol,
90 .getCalleeValue = getCallCalleeValue,
91 .getArgumentValues = getCallArgumentValues,
92 .getArgumentKeywords = getCallArgumentKeywords,
93 };
94
95 const yield_vtable = ir.interfaces.YieldOpInterface.VTable{
96 .getYieldOperandCount = getYieldOperandCount,
97 .getYieldOperand = getYieldOperand,
98 };
99
100 pub const FuncOp = struct {
101 op: *ir.Operation,
102
103 const def = op_templates.explicit(@This(), .{
104 .mnemonic = "func",
105 .operands = 0,
106 .regions = ir.dialects.shape.atMost(1),
107 .region_names = .{"body"},
108 .successors = 0,
109 .attrs = &.{
110 op_attr_names.kernel,
111 op_attr_names.sym_visibility,
112 },
113 .required_attrs = &.{
114 op_attr_names.input_count,
115 op_attr_names.input_types,
116 op_attr_names.input_types_text,
117 op_attr_names.sym_name,
118 },
119 .interfaces = &.{
120 ir.interfaces.SymbolOpInterface.entry(&func_symbol_vtable),
121 ir.interfaces.FunctionOpInterface.entry(&function_vtable),
122 effects.EffectOpInterface.entryFor(.{
123 .capacity = .{ .per_region = 1 },
124 .enumerate = functionEffects,
125 }),
126 },
127 .dynamic_traits = .{ ir.traits.AtMostNRegions(1), ir.traits.IsolatedFromAbove },
128 });
129 pub const operation_spec = def.operation_spec;
130 pub const operation_name = def.operation_name;
131 pub const createOperation = def.createOperation;
132 pub const getRegion = def.getRegion;
133
134 pub fn create(
135 ctx: *ir.Context,
136 loc: ir.Location,
137 func_name: []const u8,
138 input_types: []const ir.Type,
139 result_types: []const ir.Type,
140 ) !FuncOp {
141 try loadSpec(ctx);
142 var body = ir.context.initRegion(ctx);
143 defer body.deinit();
144 var body_builder = ir.OperationBuilder.init(ctx);
145 _ = try body_builder.createBlockWithLoc(&body, input_types, loc);
146 var regions = [_]*ir.Region{&body};
147 const func = try @This().createOperation(ctx, loc, &.{}, result_types, ®ions, &.{});
148 const op = func.op;
149 errdefer op.erase();
150
151 const name_attr = try getSymNameAttr(ctx, func_name);
152 try op.setAttr(op_attr_names.sym_name, name_attr);
153 try setFunctionSignatureAttrs(ctx, op, input_types);
154
155 return func;
156 }
157
158 pub fn createDeclaration(
159 ctx: *ir.Context,
160 loc: ir.Location,
161 func_name: []const u8,
162 input_types: []const ir.Type,
163 result_types: []const ir.Type,
164 ) !FuncOp {
165 try loadSpec(ctx);
166 const func = try @This().createOperation(ctx, loc, &.{}, result_types, &.{}, &.{});
167 const op = func.op;
168 errdefer op.erase();
169
170 const name_attr = try getSymNameAttr(ctx, func_name);
171 try op.setAttr(op_attr_names.sym_name, name_attr);
172 try setFunctionSignatureAttrs(ctx, op, input_types);
173 try ir.SymbolTable.setSymbolVisibility(op, .private);
174
175 return func;
176 }
177
178 pub fn createKernel(
179 ctx: *ir.Context,
180 loc: ir.Location,
181 kernel_name: []const u8,
182 input_types: []const ir.Type,
183 ) !FuncOp {
184 const func_op = try create(ctx, loc, kernel_name, input_types, &.{});
185 errdefer func_op.op.erase();
186
187 const kernel_attr = try getKernelAttr(ctx);
188 try func_op.op.setAttr(op_attr_names.kernel, kernel_attr);
189
190 return func_op;
191 }
192
193 pub fn getName(self: FuncOp) ?[]const u8 {
194 return ir.SymbolTable.getSymbolName(self.op);
195 }
196
197 pub fn isKernel(self: FuncOp) bool {
198 return self.op.getAttr(op_attr_names.kernel) != null;
199 }
200
201 pub fn hasBody(self: FuncOp) bool {
202 return self.op.getRegion(0) != null;
203 }
204
205 pub fn isDeclaration(self: FuncOp) bool {
206 return !self.hasBody();
207 }
208
209 pub fn getBody(self: FuncOp) *ir.Region {
210 return self.getRegion("body");
211 }
212
213 pub fn getEntryBlock(self: FuncOp) *ir.Block {
214 return self.getBody().getEntryBlock().?;
215 }
216
217 pub fn getArguments(self: FuncOp) []*ir.Value {
218 return self.getEntryBlock().arguments.items;
219 }
220
221 pub fn getNumArguments(self: FuncOp) usize {
222 if (self.op.getRegion(0)) |region| {
223 return region.getEntryBlock().?.arguments.items.len;
224 }
225 if (self.getInputTypes()) |types| {
226 return types.len;
227 }
228 if (self.op.getAttrAs(ir.Attribute.IntegerAttr, op_attr_names.input_count)) |int_attr| {
229 return @intCast(int_attr.getValue());
230 }
231 return 0;
232 }
233
234 pub fn getInputTypes(self: FuncOp) ?[]const ir.Type {
235 const type_list_attr = self.op.getAttrAs(ir.Attribute.TypeListAttr, op_attr_names.input_types) orelse return null;
236 return type_list_attr.getValues();
237 }
238
239 pub fn getInputType(self: FuncOp, index: usize) ?ir.Type {
240 const types = self.getInputTypes() orelse return null;
241 if (index >= types.len) return null;
242 return types[index];
243 }
244
245 pub fn getInputTypesText(self: FuncOp) ?[]const u8 {
246 if (self.op.getAttr(op_attr_names.input_types_text)) |attr| {
247 const string_attr = attr.cast(ir.Attribute.StringAttr) orelse return null;
248 return string_attr.getValue();
249 }
250 const string_attr = self.op.getAttrAs(ir.Attribute.StringAttr, op_attr_names.input_types) orelse return null;
251 return string_attr.getValue();
252 }
253
254 pub fn getArgument(self: FuncOp, index: usize) *ir.Value {
255 return self.getEntryBlock().arguments.items[index];
256 }
257
258 pub fn getResultTypes(self: FuncOp) []const ir.Type {
259 return self.op.getResultTypes();
260 }
261
262 pub fn getNumResults(self: FuncOp) usize {
263 return self.op.results.items.len;
264 }
265 };
266
267 pub const CallOp = struct {
268 op: *ir.Operation,
269
270 const leaf = op_templates.explicitLeaf(@This(), .{
271 .mnemonic = "call",
272 .required_attrs = &.{"callee"},
273 .interfaces = &.{
274 ir.interfaces.CallOpInterface.entry(&call_vtable),
275 effects.EffectOpInterface.entryFor(.{
276 .facts = &.{.{ .requirement = .{
277 .kind = .callee_contract,
278 .subject = .operation,
279 } }},
280 }),
281 },
282 });
283 pub const operation_spec = leaf.operation_spec;
284 pub const operation_name = leaf.operation_name;
285 pub const createLeaf = leaf.createLeaf;
286 pub const verifySymbolUses = verifyCallSymbolUses;
287
288 pub fn create(
289 ctx: *ir.Context,
290 loc: ir.Location,
291 callee_name: []const u8,
292 operands: []const *ir.Value,
293 result_types: []const ir.Type,
294 ) !CallOp {
295 try loadSpec(ctx);
296 const call = try @This().createLeaf(ctx, loc, operands, result_types);
297 const op = call.op;
298 errdefer op.erase();
299
300 const callee_attr = try ctx.getFlatSymbolRefAttr(callee_name);
301 try op.setAttr("callee", callee_attr);
302
303 return call;
304 }
305
306 pub fn getCalleeRef(self: CallOp) ?*const ir.Attribute.SymbolRefAttr {
307 return self.op.getAttrAs(ir.Attribute.SymbolRefAttr, "callee");
308 }
309
310 pub fn getCallee(self: CallOp) ?[]const u8 {
311 const symbol_ref = self.getCalleeRef() orelse return null;
312 return symbol_ref.getLeafReference();
313 }
314
315 pub fn getOperands(self: CallOp) []const *ir.Value {
316 return self.op.getOperandValues();
317 }
318
319 pub fn getNumOperands(self: CallOp) usize {
320 return self.op.operands.items.len;
321 }
322
323 pub fn getResult(self: *const CallOp, index: usize) ?*ir.Value {
324 return self.op.getResult(index);
325 }
326
327 pub fn getNumResults(self: CallOp) usize {
328 return self.op.results.items.len;
329 }
330 };
331
332 /// Crosses into the kernel and answers the one value the kernel returns.
333 ///
334 /// The op belongs to `func` because `func` owns the call boundary. `func.call` names a
335 /// callee the module can see and `func.syscall` names one it cannot, but both carry the
336 /// same shape: arguments in operand order, a register convention the target supplies, and
337 /// a result the callee decides. Nothing here is x86-64. Every Linux port spells this same
338 /// boundary over a number and at most six arguments, so a target dialect would have been
339 /// the wrong home for the operand order and the right home only for the instruction.
340 ///
341 /// Operand 0 is the number and operands 1 through 6 are the kernel's arguments in the
342 /// kernel's order. The effect record is deliberately incomplete: what the kernel reads and
343 /// writes follows from the number, which is an SSA value, so no pass may read the
344 /// enumerated facts as the whole truth.
345 pub const SyscallOp = struct {
346 op: *ir.Operation,
347
348 /// The kernel reads at most six arguments, so seven operands is the whole shape.
349 pub const max_arguments: usize = 6;
350
351 const leaf = op_templates.explicitLeaf(@This(), .{
352 .mnemonic = "syscall",
353 .operands = ir.dialects.shape.between(1, max_arguments + 1),
354 .operand_names = .{
355 "number",
356 "argument0",
357 "argument1",
358 "argument2",
359 "argument3",
360 "argument4",
361 "argument5",
362 },
363 .results = .{"result"},
364 .interfaces = &.{
365 effects.EffectOpInterface.entryFor(.{
366 .facts = &.{.{ .event = .{ .kind = .foreign, .resource = .{ .subject = .operation } } }},
367 }),
368 },
369 });
370 pub const operation_spec = leaf.operation_spec;
371 pub const operation_name = leaf.operation_name;
372 pub const createLeaf = leaf.createLeaf;
373 pub const verify = verifySyscallOp;
374
375 pub fn create(
376 ctx: *ir.Context,
377 loc: ir.Location,
378 number: *ir.Value,
379 arguments: []const *ir.Value,
380 result_type: ir.Type,
381 ) !SyscallOp {
382 try loadSpec(ctx);
383 if (arguments.len > max_arguments) return FuncVerifyError.SyscallArgumentCount;
384 var operands: [max_arguments + 1]*ir.Value = undefined;
385 operands[0] = number;
386 @memcpy(operands[1..][0..arguments.len], arguments);
387 return try @This().createLeaf(ctx, loc, operands[0 .. arguments.len + 1], &.{result_type});
388 }
389
390 pub fn getNumber(self: SyscallOp) *ir.Value {
391 return self.op.getOperand(0).?;
392 }
393
394 pub fn getArguments(self: SyscallOp) []const *ir.Value {
395 return self.op.getOperandValues()[1..];
396 }
397
398 pub fn getNumArguments(self: SyscallOp) usize {
399 return self.op.operands.items.len - 1;
400 }
401
402 pub fn getResult(self: SyscallOp) *ir.Value {
403 return self.op.getResult(0).?;
404 }
405 };
406
407 pub const ReturnOp = struct {
408 op: *ir.Operation,
409
410 const term = op_templates.explicitTerminator(@This(), .{
411 .mnemonic = "return",
412 .interfaces = &.{
413 ir.interfaces.YieldOpInterface.entry(&yield_vtable),
414 effects.EffectOpInterface.entryFor(.{
415 .capacity = .{ .per_operand = 1 },
416 .enumerate = returnEffects,
417 }),
418 },
419 });
420 pub const operation_spec = term.operation_spec;
421 pub const operation_name = term.operation_name;
422 pub const createTerminator = term.createTerminator;
423
424 pub fn create(
425 ctx: *ir.Context,
426 loc: ir.Location,
427 operands: []const *ir.Value,
428 ) !ReturnOp {
429 try loadSpec(ctx);
430 return try @This().createTerminator(ctx, loc, operands, &.{});
431 }
432
433 pub fn getOperands(self: ReturnOp) []const *ir.Value {
434 return self.op.getOperandValues();
435 }
436
437 pub fn getNumOperands(self: ReturnOp) usize {
438 return self.op.operands.items.len;
439 }
440 };
441
442 fn loadSpec(ctx: *ir.Context) !void {
443 ir.dialects.loadDialectSpec(ctx, spec) catch |err| switch (err) {
444 error.ContextFrozen => {},
445 else => return err,
446 };
447 }
448
449 pub fn getSymNameAttr(ctx: *ir.Context, func_name: []const u8) !ir.Attribute {
450 return ctx.getDialectAttr(attr_names.sym_name, func_name);
451 }
452
453 pub fn getSymNameValue(attr: ir.Attribute) ?[]const u8 {
454 if (!std.mem.eql(u8, attr.abstract.name, attr_names.sym_name)) return null;
455 const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;
456 return dialect_attr.payload;
457 }
458
459 pub fn getSymbolRefValue(attr: ir.Attribute) ?*const ir.Attribute.SymbolRefAttr {
460 return attr.cast(ir.Attribute.SymbolRefAttr);
461 }
462
463 pub fn getKernelAttr(ctx: *ir.Context) !ir.Attribute {
464 return ctx.getDialectAttr(attr_names.kernel, "");
465 }
466
467 fn setFunctionSignatureAttrs(
468 ctx: *ir.Context,
469 op: *ir.Operation,
470 input_types: []const ir.Type,
471 ) !void {
472 try op.setAttr(op_attr_names.input_count, try ctx.getI64Attr(@intCast(input_types.len)));
473 try op.setAttr(op_attr_names.input_types, try ctx.getTypeListAttr(input_types));
474
475 var input_type_text: std.ArrayListUnmanaged(u8) = .empty;
476 const allocator = ir.context.transientAllocator(ctx);
477 defer input_type_text.deinit(allocator);
478
479 for (input_types, 0..) |typ, index| {
480 if (index != 0) try input_type_text.append(allocator, ',');
481 const rendered = try std.fmt.allocPrint(allocator, "{f}", .{typ});
482 errdefer allocator.free(rendered);
483 try input_type_text.appendSlice(allocator, rendered);
484 allocator.free(rendered);
485 }
486
487 try op.setAttr(op_attr_names.input_types_text, try ctx.getStringAttr(input_type_text.items));
488 }
489
490 fn verifyCallOperands(call: CallOp, callee: FuncOp) !void {
491 if (callee.op.getRegion(0)) |region| {
492 const expected_args = region.getEntryBlock().?.arguments.items;
493 if (call.getNumOperands() != expected_args.len) {
494 return FuncVerifyError.CallOperandCountMismatch;
495 }
496 for (call.op.operands.items, 0..) |operand, index| {
497 if (!operand.value.type.eql(expected_args[index].type)) {
498 return FuncVerifyError.CallOperandTypeMismatch;
499 }
500 }
501 return;
502 }
503
504 const input_types = callee.getInputTypes();
505 const input_count = if (input_types) |types|
506 types.len
507 else if (callee.op.getAttrAs(ir.Attribute.IntegerAttr, op_attr_names.input_count)) |int_attr|
508 @as(usize, @intCast(int_attr.getValue()))
509 else
510 0;
511 if (call.getNumOperands() != input_count) {
512 return FuncVerifyError.CallOperandCountMismatch;
513 }
514
515 const expected_types = input_types orelse return FuncVerifyError.MissingFunctionSignature;
516 for (call.op.operands.items, 0..) |operand, index| {
517 if (!operand.value.type.eql(expected_types[index])) {
518 return FuncVerifyError.CallOperandTypeMismatch;
519 }
520 }
521 }
522
523 fn verifyCallResults(call: CallOp, callee: FuncOp) !void {
524 const expected_results = callee.getResultTypes();
525 if (call.getNumResults() != expected_results.len) {
526 return FuncVerifyError.CallResultCountMismatch;
527 }
528
529 for (call.op.results.items, 0..) |result, index| {
530 if (!result.type.eql(expected_results[index])) {
531 return FuncVerifyError.CallResultTypeMismatch;
532 }
533 }
534 }
535
536 /// The declared shape bounds the operand count, so the verifier only states the two facts
537 /// the shape cannot: a number is present and the kernel's one return value is taken.
538 fn verifySyscallOp(op_ptr: *const anyopaque) anyerror!void {
539 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
540 const operand_count = op.operands.items.len;
541 if (operand_count == 0 or operand_count > SyscallOp.max_arguments + 1) {
542 return FuncVerifyError.SyscallArgumentCount;
543 }
544 if (op.results.items.len != 1) return FuncVerifyError.SyscallMissingResult;
545 }
546
547 fn verifyCallSymbolUses(
548 op_ptr: *const anyopaque,
549 symbol_tables: *ir.SymbolTable.Collection,
550 ) anyerror!void {
551 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
552 const call = CallOp{ .op = op };
553 const callee_ref = call.getCalleeRef() orelse return FuncVerifyError.MissingCallee;
554 const callee_op = try symbol_tables.lookupNearestSymbolRefFrom(op, callee_ref) orelse return FuncVerifyError.UnresolvedCallee;
555 if (!std.mem.eql(u8, callee_op.name.name, FuncOp.operation_name)) {
556 return FuncVerifyError.CalleeNotFunction;
557 }
558
559 const callee = FuncOp{ .op = callee_op };
560 try verifyCallOperands(call, callee);
561 try verifyCallResults(call, callee);
562 }
563
564 fn getFuncSymbolName(op_ptr: *const anyopaque) ?[]const u8 {
565 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
566 const attr = op.getAttr(op_attr_names.sym_name) orelse return null;
567 return getSymNameValue(attr);
568 }
569
570 fn setFuncSymbolName(op_ptr: *const anyopaque, symbol_name: []const u8) anyerror!void {
571 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
572 try op.setAttr(op_attr_names.sym_name, try getSymNameAttr(op.getContext(), symbol_name));
573 }
574
575 fn isFuncDeclaration(op_ptr: *const anyopaque) bool {
576 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
577 return (FuncOp{ .op = op }).isDeclaration();
578 }
579
580 fn getCallCalleeSymbol(op_ptr: *const anyopaque) ?[]const u8 {
581 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
582 const symbol_ref = op.getAttrAs(ir.Attribute.SymbolRefAttr, "callee") orelse return null;
583 return symbol_ref.getLeafReference();
584 }
585
586 fn getCallCalleeValue(_: *const anyopaque) ?*ir.Value {
587 return null;
588 }
589
590 fn getCallArgumentValues(op_ptr: *const anyopaque) []const *ir.Value {
591 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
592 return op.getOperandValues();
593 }
594
595 fn getCallArgumentKeywords(_: *const anyopaque) []const []const u8 {
596 return &.{};
597 }
598
599 fn getYieldOperandCount(op_ptr: *const anyopaque) usize {
600 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
601 return op.getOperandValues().len;
602 }
603
604 fn getYieldOperand(op_ptr: *const anyopaque, index: usize) ?*ir.Value {
605 const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));
606 return op.getOperand(index);
607 }
608
609 fn hasFunctionBody(op_ptr: *const anyopaque) bool {
610 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
611 return (FuncOp{ .op = op }).hasBody();
612 }
613
614 fn getFunctionEntryBlock(op_ptr: *const anyopaque) ?*ir.Block {
615 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
616 const func = FuncOp{ .op = op };
617 if (!func.hasBody()) return null;
618 return func.getEntryBlock();
619 }
620
621 fn getFunctionArgumentCount(op_ptr: *const anyopaque) usize {
622 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
623 return (FuncOp{ .op = op }).getNumArguments();
624 }
625
626 fn getFunctionResultCount(op_ptr: *const anyopaque) usize {
627 const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));
628 return (FuncOp{ .op = op }).getNumResults();
629 }
630 };
631
632 const ResourceCounts = struct {
633 operations: usize,
634
635 fn capture(ctx: *const ir.Context) ResourceCounts {
636 return .{
637 .operations = ctx.operationCount(),
638 };
639 }
640
641 fn expectEqual(self: ResourceCounts, ctx: *const ir.Context) !void {
642 try std.testing.expectEqual(self.operations, ctx.operationCount());
643 }
644 };
645
646 fn checkFuncConstructorAllocationFailures(allocator: std.mem.Allocator) !void {
647 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
648 defer ctx.deinit(allocator);
649 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
650 const loc = ir.Location.getUnknown();
651 const baseline = ResourceCounts.capture(&ctx);
652
653 const function = FuncDialect.FuncOp.create(&ctx, loc, "function", &.{}, &.{}) catch |err| {
654 try baseline.expectEqual(&ctx);
655 return err;
656 };
657 function.op.erase();
658 try baseline.expectEqual(&ctx);
659
660 const declaration = FuncDialect.FuncOp.createDeclaration(&ctx, loc, "declaration", &.{}, &.{}) catch |err| {
661 try baseline.expectEqual(&ctx);
662 return err;
663 };
664 declaration.op.erase();
665 try baseline.expectEqual(&ctx);
666
667 const kernel = FuncDialect.FuncOp.createKernel(&ctx, loc, "kernel", &.{}) catch |err| {
668 try baseline.expectEqual(&ctx);
669 return err;
670 };
671 kernel.op.erase();
672 try baseline.expectEqual(&ctx);
673
674 const call = FuncDialect.CallOp.create(&ctx, loc, "callee", &.{}, &.{}) catch |err| {
675 try baseline.expectEqual(&ctx);
676 return err;
677 };
678 call.op.erase();
679 try baseline.expectEqual(&ctx);
680 }
681
682 test "FuncDialect constructors clean every allocation failure" {
683 try std.testing.checkAllAllocationFailures(
684 std.testing.allocator,
685 checkFuncConstructorAllocationFailures,
686 .{},
687 );
688 }
689
690 test "FuncDialect.FuncOp creates function" {
691 const testing = std.testing;
692 const arith = @import("arith/root.zig");
693
694 var arena = alloc_arena.Arena.init(std.testing.allocator);
695 defer arena.deinit();
696 const allocator = arena.allocator();
697
698 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
699 defer ctx.deinit(allocator);
700 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
701
702 const loc = ir.Location.getUnknown();
703 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
704
705 const func_op = try FuncDialect.FuncOp.create(
706 &ctx,
707 loc,
708 "add",
709 &.{ i32_type, i32_type },
710 &.{i32_type},
711 );
712
713 try testing.expectEqualStrings("func.func", func_op.op.name.name);
714 try testing.expectEqualStrings("add", func_op.getName().?);
715 try testing.expect(func_op.hasBody());
716 try testing.expect(!func_op.isDeclaration());
717 try testing.expectEqual(@as(usize, 2), func_op.getNumArguments());
718 try testing.expectEqual(@as(usize, 1), func_op.getNumResults());
719 const input_types = func_op.getInputTypes().?;
720 try testing.expectEqual(@as(usize, 2), input_types.len);
721 try testing.expect(input_types[0].eql(i32_type));
722 try testing.expect(input_types[1].eql(i32_type));
723 try testing.expectEqualStrings("!arith.i32,!arith.i32", func_op.getInputTypesText().?);
724 try testing.expect(!func_op.isKernel());
725 try testing.expect(func_op.op.hasTraitId(ir.traits.IsolatedFromAbove.id));
726
727 const iface = func_op.op.interface(ir.interfaces.FunctionOpInterface).?;
728 try testing.expect(iface.call(.hasBody, .{}));
729 try testing.expectEqual(func_op.getEntryBlock(), iface.call(.getEntryBlock, .{}).?);
730 try testing.expectEqual(@as(usize, 2), iface.call(.getArgumentCount, .{}));
731 try testing.expectEqual(@as(usize, 1), iface.call(.getResultCount, .{}));
732 }
733
734 test "FuncDialect.FuncOp creates external declaration" {
735 const testing = std.testing;
736 const arith = @import("arith/root.zig");
737 const interfaces = @import("../core/root.zig").interfaces;
738
739 var arena = alloc_arena.Arena.init(std.testing.allocator);
740 defer arena.deinit();
741 const allocator = arena.allocator();
742
743 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
744 defer ctx.deinit(allocator);
745 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
746
747 const loc = ir.Location.getUnknown();
748 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
749
750 const func_op = try FuncDialect.FuncOp.createDeclaration(
751 &ctx,
752 loc,
753 "external_add",
754 &.{ i32_type, i32_type },
755 &.{i32_type},
756 );
757
758 try testing.expectEqualStrings("func.func", func_op.op.name.name);
759 try testing.expectEqualStrings("external_add", func_op.getName().?);
760 try testing.expect(!func_op.hasBody());
761 try testing.expect(func_op.isDeclaration());
762 try testing.expectEqual(@as(usize, 2), func_op.getNumArguments());
763 try testing.expectEqual(@as(usize, 1), func_op.getNumResults());
764 const input_types = func_op.getInputTypes().?;
765 try testing.expectEqual(@as(usize, 2), input_types.len);
766 try testing.expect(input_types[0].eql(i32_type));
767 try testing.expect(input_types[1].eql(i32_type));
768 try testing.expectEqualStrings("!arith.i32,!arith.i32", func_op.getInputTypesText().?);
769 try testing.expectEqual(ir.SymbolTable.Visibility.private, ir.SymbolTable.getSymbolVisibility(func_op.op));
770 try testing.expect(ir.SymbolTable.isDeclaration(func_op.op));
771
772 const iface = func_op.op.interface(interfaces.SymbolOpInterface).?;
773 try testing.expectEqualStrings("external_add", iface.call(.getSymbolName, .{}).?);
774 try testing.expect(iface.call(.isDeclaration, .{}));
775
776 const function_iface = func_op.op.interface(interfaces.FunctionOpInterface).?;
777 try testing.expect(!function_iface.call(.hasBody, .{}));
778 try testing.expectEqual(@as(?*ir.Block, null), function_iface.call(.getEntryBlock, .{}));
779 try testing.expectEqual(@as(usize, 2), function_iface.call(.getArgumentCount, .{}));
780 try testing.expectEqual(@as(usize, 1), function_iface.call(.getResultCount, .{}));
781 }
782
783 test "FuncDialect.FuncOp creates kernel" {
784 const testing = std.testing;
785 const arith = @import("arith/root.zig");
786 const memref = @import("memref.zig");
787
788 var arena = alloc_arena.Arena.init(std.testing.allocator);
789 defer arena.deinit();
790 const allocator = arena.allocator();
791
792 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
793 defer ctx.deinit(allocator);
794 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
795
796 const loc = ir.Location.getUnknown();
797 const f32_type = try arith.ArithDialect.getScalarType(&ctx, .f32);
798 const memref_type = try memref.MemrefDialect.getMemrefType1D(&ctx, 1024, f32_type, .device);
799
800 const kernel_op = try FuncDialect.FuncOp.createKernel(
801 &ctx,
802 loc,
803 "vector_add",
804 &.{ memref_type, memref_type, memref_type },
805 );
806
807 try testing.expectEqualStrings("func.func", kernel_op.op.name.name);
808 try testing.expectEqualStrings("vector_add", kernel_op.getName().?);
809 try testing.expectEqual(@as(usize, 3), kernel_op.getNumArguments());
810 try testing.expectEqual(@as(usize, 0), kernel_op.getNumResults());
811 try testing.expect(kernel_op.isKernel());
812 }
813
814 test "FuncDialect.CallOp creates function call" {
815 const testing = std.testing;
816 const arith = @import("arith/root.zig");
817
818 var arena = alloc_arena.Arena.init(std.testing.allocator);
819 defer arena.deinit();
820 const allocator = arena.allocator();
821
822 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
823 defer ctx.deinit(allocator);
824 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
825
826 const loc = ir.Location.getUnknown();
827 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
828
829 var c1 = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 10);
830 var c2 = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 20);
831
832 var call_op = try FuncDialect.CallOp.create(
833 &ctx,
834 loc,
835 "add",
836 &.{ c1.getResult(), c2.getResult() },
837 &.{i32_type},
838 );
839
840 try testing.expectEqualStrings("func.call", call_op.op.name.name);
841 try testing.expectEqualStrings("add", call_op.getCallee().?);
842 const callee_ref = call_op.getCalleeRef().?;
843 try testing.expect(callee_ref.isFlat());
844 try testing.expectEqualStrings("add", callee_ref.getRootReference());
845 try testing.expectEqualStrings(ir.builtin_attr_names.symbol_ref, call_op.op.getAttr("callee").?.abstract.name);
846 try testing.expectEqual(@as(usize, 2), call_op.getNumOperands());
847 try testing.expectEqual(@as(usize, 1), call_op.getNumResults());
848 const call_operands = call_op.getOperands();
849 try testing.expectEqual(@as(usize, 2), call_operands.len);
850 try testing.expect(call_operands[0] == c1.getResult());
851 try testing.expect(call_operands[1] == c2.getResult());
852
853 const interfaces = @import("../core/root.zig").interfaces;
854 const iface = call_op.op.interface(interfaces.CallOpInterface).?;
855 try testing.expectEqualStrings("add", iface.call(.getCalleeSymbol, .{}).?);
856 const iface_args = iface.call(.getArgumentValues, .{});
857 try testing.expectEqual(@as(usize, 2), iface_args.len);
858 try testing.expect(iface_args[0] == c1.getResult());
859 try testing.expect(iface_args[1] == c2.getResult());
860 try testing.expectEqual(@as(usize, 0), iface.call(.getArgumentKeywords, .{}).len);
861 try testing.expect(call_op.op.hasInterface(interfaces.SymbolUserOpInterface));
862 }
863
864 test "FuncDialect.CallOp verifier accepts matching declaration signature" {
865 const testing = std.testing;
866 const arith = @import("arith/root.zig");
867 const builtin = @import("builtin.zig");
868
869 var arena = alloc_arena.Arena.init(std.testing.allocator);
870 defer arena.deinit();
871 const allocator = arena.allocator();
872
873 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
874 defer ctx.deinit(allocator);
875 try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);
876 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
877
878 const loc = ir.Location.getUnknown();
879 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
880
881 const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);
882 const module_block = module.getBodyBlock();
883
884 const callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "extern_i32", &.{i32_type}, &.{i32_type});
885 try module_block.addOperation(callee.op);
886
887 var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
888 try module_block.addOperation(caller.op);
889
890 const entry = caller.getEntryBlock();
891 const arg0 = caller.getArgument(0);
892 var call = try FuncDialect.CallOp.create(&ctx, loc, "extern_i32", &.{arg0}, &.{i32_type});
893 try entry.addOperation(call.op);
894
895 try ir.verifyOperation(module.op, ir.verify.default_options);
896 try testing.expectEqual(@as(usize, 1), call.getNumOperands());
897 }
898
899 test "FuncDialect.CallOp verifier rejects unresolved callee" {
900 const arith = @import("arith/root.zig");
901 const builtin = @import("builtin.zig");
902
903 var arena = alloc_arena.Arena.init(std.testing.allocator);
904 defer arena.deinit();
905 const allocator = arena.allocator();
906
907 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
908 defer ctx.deinit(allocator);
909 try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);
910 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
911
912 const loc = ir.Location.getUnknown();
913 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
914
915 const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);
916 const module_block = module.getBodyBlock();
917
918 var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
919 try module_block.addOperation(caller.op);
920
921 const entry = caller.getEntryBlock();
922 const call = try FuncDialect.CallOp.create(&ctx, loc, "missing", &.{caller.getArgument(0)}, &.{i32_type});
923 try entry.addOperation(call.op);
924
925 try std.testing.expectError(FuncVerifyError.UnresolvedCallee, ir.verifyOperation(module.op, ir.verify.default_options));
926 }
927
928 test "FuncDialect.CallOp verifier uses nearest symbol table" {
929 const arith = @import("arith/root.zig");
930 const builtin = @import("builtin.zig");
931
932 var arena = alloc_arena.Arena.init(std.testing.allocator);
933 defer arena.deinit();
934 const allocator = arena.allocator();
935
936 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
937 defer ctx.deinit(allocator);
938 try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);
939 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
940
941 const loc = ir.Location.getUnknown();
942 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
943
944 const outer = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);
945 const outer_block = outer.getBodyBlock();
946 const outer_callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "outer", &.{i32_type}, &.{i32_type});
947 try outer_block.addOperation(outer_callee.op);
948
949 const inner = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);
950 try outer_block.addOperation(inner.op);
951 const inner_block = inner.getBodyBlock();
952
953 var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});
954 try inner_block.addOperation(caller.op);
955
956 const entry = caller.getEntryBlock();
957 const call = try FuncDialect.CallOp.create(&ctx, loc, "outer", &.{caller.getArgument(0)}, &.{i32_type});
958 try entry.addOperation(call.op);
959
960 try std.testing.expectError(FuncVerifyError.UnresolvedCallee, ir.verifyOperation(outer.op, ir.verify.default_options));
961 }
962
963 test "FuncDialect.CallOp verifier rejects operand type mismatch" {
964 const arith = @import("arith/root.zig");
965 const builtin = @import("builtin.zig");
966
967 var arena = alloc_arena.Arena.init(std.testing.allocator);
968 defer arena.deinit();
969 const allocator = arena.allocator();
970
971 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
972 defer ctx.deinit(allocator);
973 try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);
974 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
975
976 const loc = ir.Location.getUnknown();
977 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
978 const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);
979
980 const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);
981 const module_block = module.getBodyBlock();
982
983 const callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "extern_i32", &.{i32_type}, &.{i32_type});
984 try module_block.addOperation(callee.op);
985
986 var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i64_type}, &.{i32_type});
987 try module_block.addOperation(caller.op);
988
989 const entry = caller.getEntryBlock();
990 const call = try FuncDialect.CallOp.create(&ctx, loc, "extern_i32", &.{caller.getArgument(0)}, &.{i32_type});
991 try entry.addOperation(call.op);
992
993 try std.testing.expectError(FuncVerifyError.CallOperandTypeMismatch, ir.verifyOperation(module.op, ir.verify.default_options));
994 }
995
996 test "FuncDialect.CallOp verifier rejects result type mismatch" {
997 const arith = @import("arith/root.zig");
998 const builtin = @import("builtin.zig");
999
1000 var arena = alloc_arena.Arena.init(std.testing.allocator);
1001 defer arena.deinit();
1002 const allocator = arena.allocator();
1003
1004 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1005 defer ctx.deinit(allocator);
1006 try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);
1007 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
1008
1009 const loc = ir.Location.getUnknown();
1010 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
1011 const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);
1012
1013 const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);
1014 const module_block = module.getBodyBlock();
1015
1016 const callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "extern_i32", &.{i32_type}, &.{i32_type});
1017 try module_block.addOperation(callee.op);
1018
1019 var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i64_type});
1020 try module_block.addOperation(caller.op);
1021
1022 const entry = caller.getEntryBlock();
1023 const call = try FuncDialect.CallOp.create(&ctx, loc, "extern_i32", &.{caller.getArgument(0)}, &.{i64_type});
1024 try entry.addOperation(call.op);
1025
1026 try std.testing.expectError(FuncVerifyError.CallResultTypeMismatch, ir.verifyOperation(module.op, ir.verify.default_options));
1027 }
1028
1029 test "FuncDialect.SyscallOp carries a number and the kernel's arguments" {
1030 const testing = std.testing;
1031 const arith = @import("arith/root.zig");
1032
1033 var arena = alloc_arena.Arena.init(std.testing.allocator);
1034 defer arena.deinit();
1035 const allocator = arena.allocator();
1036
1037 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1038 defer ctx.deinit(allocator);
1039 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
1040
1041 const loc = ir.Location.getUnknown();
1042 const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);
1043
1044 var number = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 39);
1045 var first = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 1);
1046 var second = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 2);
1047
1048 const entered = try FuncDialect.SyscallOp.create(
1049 &ctx,
1050 loc,
1051 number.getResult(),
1052 &.{ first.getResult(), second.getResult() },
1053 i64_type,
1054 );
1055
1056 try testing.expectEqualStrings("func.syscall", entered.op.name.name);
1057 try testing.expect(entered.getNumber() == number.getResult());
1058 try testing.expectEqual(@as(usize, 2), entered.getNumArguments());
1059 const arguments = entered.getArguments();
1060 try testing.expect(arguments[0] == first.getResult());
1061 try testing.expect(arguments[1] == second.getResult());
1062 try testing.expect(entered.getResult().type.eql(i64_type));
1063 try ir.verifyOperation(entered.op, ir.verify.default_options);
1064
1065 const bare = try FuncDialect.SyscallOp.create(&ctx, loc, number.getResult(), &.{}, i64_type);
1066 try testing.expectEqual(@as(usize, 0), bare.getNumArguments());
1067 try ir.verifyOperation(bare.op, ir.verify.default_options);
1068
1069 var seven: [FuncDialect.SyscallOp.max_arguments + 1]*ir.Value = undefined;
1070 for (&seven) |*argument| argument.* = first.getResult();
1071 try testing.expectError(
1072 FuncVerifyError.SyscallArgumentCount,
1073 FuncDialect.SyscallOp.create(&ctx, loc, number.getResult(), &seven, i64_type),
1074 );
1075 }
1076
1077 test "FuncDialect.ReturnOp creates return" {
1078 const testing = std.testing;
1079 const arith = @import("arith/root.zig");
1080
1081 var arena = alloc_arena.Arena.init(std.testing.allocator);
1082 defer arena.deinit();
1083 const allocator = arena.allocator();
1084
1085 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1086 defer ctx.deinit(allocator);
1087
1088 const loc = ir.Location.getUnknown();
1089 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
1090
1091 var val = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 42);
1092
1093 const ret_op = try FuncDialect.ReturnOp.create(&ctx, loc, &.{val.getResult()});
1094
1095 try testing.expectEqualStrings("func.return", ret_op.op.name.name);
1096 try testing.expectEqual(@as(usize, 1), ret_op.getNumOperands());
1097 const ret_operands = ret_op.getOperands();
1098 try testing.expectEqual(@as(usize, 1), ret_operands.len);
1099 try testing.expect(ret_operands[0] == val.getResult());
1100
1101 const iface = ret_op.op.interface(ir.interfaces.YieldOpInterface).?;
1102 try testing.expectEqual(@as(usize, 1), iface.call(.getYieldOperandCount, .{}));
1103 try testing.expect(iface.call(.getYieldOperand, .{0}).? == val.getResult());
1104 try testing.expectEqual(@as(?*ir.Value, null), iface.call(.getYieldOperand, .{1}));
1105 }
1106
1107 test "FuncDialect.FuncOp SymbolOpInterface" {
1108 const testing = std.testing;
1109 const arith = @import("arith/root.zig");
1110 const interfaces = @import("../core/root.zig").interfaces;
1111
1112 var arena = alloc_arena.Arena.init(std.testing.allocator);
1113 defer arena.deinit();
1114 const allocator = arena.allocator();
1115
1116 var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);
1117 defer ctx.deinit(allocator);
1118 try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);
1119
1120 const loc = ir.Location.getUnknown();
1121 const i32_type = try arith.ArithDialect.getI32Type(&ctx);
1122
1123 const func_op = try FuncDialect.FuncOp.create(
1124 &ctx,
1125 loc,
1126 "my_function",
1127 &.{i32_type},
1128 &.{i32_type},
1129 );
1130
1131 const iface = func_op.op.interface(interfaces.SymbolOpInterface).?;
1132 try testing.expectEqualStrings("my_function", iface.call(.getSymbolName, .{}).?);
1133 try testing.expect(!iface.call(.isDeclaration, .{}));
1134 try testing.expect(!ir.SymbolTable.isDeclaration(func_op.op));
1135 }
1136
1137 fn functionEffects(op: *const ir.Operation, collector: *effects.Collector) void {
1138 for (0..op.getNumRegions()) |index| collector.append(.{ .region = .{
1139 .index = index,
1140 .execution = .latent,
1141 .may_diverge = false,
1142 .captures = false,
1143 } });
1144 }
1145
1146 fn returnEffects(op: *const ir.Operation, collector: *effects.Collector) void {
1147 for (0..op.getNumOperands()) |index| collector.append(.{ .event = .{
1148 .kind = .move,
1149 .resource = .{ .subject = .{ .operand = index } },
1150 } });
1151 }
1152
1153 test "func effect declarations keep definitions latent and calls unresolved" {
1154 var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
1155 defer ctx.deinit(std.testing.allocator);
1156 const function = try FuncDialect.FuncOp.create(&ctx, .unknown, "effect_function", &.{}, &.{});
1157 const call = try FuncDialect.CallOp.create(&ctx, .unknown, "effect_function", &.{}, &.{});
1158 var definition = try effects.inspect(std.testing.allocator, function.op);
1159 defer definition.deinit(std.testing.allocator);
1160 try std.testing.expectEqual(
1161 effects.Execution.latent,
1162 definition.facts.records[0].region.execution,
1163 );
1164 try std.testing.expect(!definition.facts.records[0].region.may_diverge);
1165 var invocation = try effects.inspect(std.testing.allocator, call.op);
1166 defer invocation.deinit(std.testing.allocator);
1167 try std.testing.expectEqual(
1168 effects.RequirementKind.callee_contract,
1169 invocation.facts.records[0].requirement.kind,
1170 );
1171 try std.testing.expect(!effects.discard(invocation.facts));
1172 }