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, &regions, &.{});
 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 }