Skip to documentation
SLOP

tiny.choir.dialects.func

Reference tiny.choir dialects func

Defined in dialects.

API (4)

Types and contracts

Public types and contracts.

No direct callersNo direct callsdialectsfunc
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/choir/src/dialects/func.zig

zig
const std = @import("std");const alloc_arena = @import("alloc_arena");const ir = @import("../core/root.zig");const effects = ir.interfaces.effects;pub const attr_names = struct {    pub const sym_name = "func.sym_name";    pub const func_type = "func.type";    pub const kernel = "func.kernel";};pub const op_attr_names = struct {    pub const sym_name = "sym_name";    pub const sym_visibility = ir.SymbolTable.symbol_attr_names.sym_visibility;    pub const kernel = "kernel";    pub const input_count = "input_count";    pub const input_types = "input_types";    pub const input_types_text = "input_types_text";};pub const FuncVerifyError = error{    MissingCallee,    UnresolvedCallee,    CalleeNotFunction,    CallOperandCountMismatch,    CallResultCountMismatch,    CallOperandTypeMismatch,    CallResultTypeMismatch,    MissingFunctionSignature,    SyscallArgumentCount,    SyscallMissingResult,};const FuncEval = struct {    fn canEval(op_ptr: *const anyopaque) bool {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        return std.mem.eql(u8, op.name.name, FuncDialect.ReturnOp.operation_name) or            std.mem.eql(u8, op.name.name, FuncDialect.CallOp.operation_name);    }    fn evaluate(        op_ptr: *const anyopaque,        operands: []const ir.Attribute,        eval_ctx: *const ir.interfaces.EvalContext,    ) ir.interfaces.EvalError!ir.Attribute {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        if (std.mem.eql(u8, op.name.name, FuncDialect.CallOp.operation_name)) {            const callee_ref = op.getAttrAs(ir.Attribute.SymbolRefAttr, "callee") orelse return error.InvalidOperand;            return eval_ctx.evaluateSymbol(eval_ctx.state, callee_ref.getLeafReference(), operands);        }        if (operands.len == 0) return error.YieldMissingOperand;        if (operands.len == 1) return operands[0];        return op.getContext().getArrayAttr(operands) catch error.OutOfMemory;    }    const vtable = ir.interfaces.Evaluatable.VTable{        .canEval = canEval,        .evaluate = evaluate,    };    fn fallback(_: *const ir.Operation) ?*const anyopaque {        return &vtable;    }};pub const FuncDialect = struct {    pub const name = "func";    const op_templates = ir.dialects.operationTemplate.dialect(@This());    pub const spec = ir.dialects.dialectSpec(@This(), .{        .op_interface_fallbacks = &.{            .{ .id = ir.interfaces.Evaluatable.id, .fallback = FuncEval.fallback },        },    });    const func_symbol_vtable = ir.interfaces.SymbolOpInterface.VTable{        .getSymbolName = getFuncSymbolName,        .setSymbolName = setFuncSymbolName,        .isDeclaration = isFuncDeclaration,    };    const function_vtable = ir.interfaces.FunctionOpInterface.VTable{        .hasBody = hasFunctionBody,        .getEntryBlock = getFunctionEntryBlock,        .getArgumentCount = getFunctionArgumentCount,        .getResultCount = getFunctionResultCount,    };    const call_vtable = ir.interfaces.CallOpInterface.VTable{        .getCalleeSymbol = getCallCalleeSymbol,        .getCalleeValue = getCallCalleeValue,        .getArgumentValues = getCallArgumentValues,        .getArgumentKeywords = getCallArgumentKeywords,    };    const yield_vtable = ir.interfaces.YieldOpInterface.VTable{        .getYieldOperandCount = getYieldOperandCount,        .getYieldOperand = getYieldOperand,    };    pub const FuncOp = struct {        op: *ir.Operation,        const def = op_templates.explicit(@This(), .{            .mnemonic = "func",            .operands = 0,            .regions = ir.dialects.shape.atMost(1),            .region_names = .{"body"},            .successors = 0,            .attrs = &.{                op_attr_names.kernel,                op_attr_names.sym_visibility,            },            .required_attrs = &.{                op_attr_names.input_count,                op_attr_names.input_types,                op_attr_names.input_types_text,                op_attr_names.sym_name,            },            .interfaces = &.{                ir.interfaces.SymbolOpInterface.entry(&func_symbol_vtable),                ir.interfaces.FunctionOpInterface.entry(&function_vtable),                effects.EffectOpInterface.entryFor(.{                    .capacity = .{ .per_region = 1 },                    .enumerate = functionEffects,                }),            },            .dynamic_traits = .{ ir.traits.AtMostNRegions(1), ir.traits.IsolatedFromAbove },        });        pub const operation_spec = def.operation_spec;        pub const operation_name = def.operation_name;        pub const createOperation = def.createOperation;        pub const getRegion = def.getRegion;        pub fn create(            ctx: *ir.Context,            loc: ir.Location,            func_name: []const u8,            input_types: []const ir.Type,            result_types: []const ir.Type,        ) !FuncOp {            try loadSpec(ctx);            var body = ir.context.initRegion(ctx);            defer body.deinit();            var body_builder = ir.OperationBuilder.init(ctx);            _ = try body_builder.createBlockWithLoc(&body, input_types, loc);            var regions = [_]*ir.Region{&body};            const func = try @This().createOperation(ctx, loc, &.{}, result_types, &regions, &.{});            const op = func.op;            errdefer op.erase();            const name_attr = try getSymNameAttr(ctx, func_name);            try op.setAttr(op_attr_names.sym_name, name_attr);            try setFunctionSignatureAttrs(ctx, op, input_types);            return func;        }        pub fn createDeclaration(            ctx: *ir.Context,            loc: ir.Location,            func_name: []const u8,            input_types: []const ir.Type,            result_types: []const ir.Type,        ) !FuncOp {            try loadSpec(ctx);            const func = try @This().createOperation(ctx, loc, &.{}, result_types, &.{}, &.{});            const op = func.op;            errdefer op.erase();            const name_attr = try getSymNameAttr(ctx, func_name);            try op.setAttr(op_attr_names.sym_name, name_attr);            try setFunctionSignatureAttrs(ctx, op, input_types);            try ir.SymbolTable.setSymbolVisibility(op, .private);            return func;        }        pub fn createKernel(            ctx: *ir.Context,            loc: ir.Location,            kernel_name: []const u8,            input_types: []const ir.Type,        ) !FuncOp {            const func_op = try create(ctx, loc, kernel_name, input_types, &.{});            errdefer func_op.op.erase();            const kernel_attr = try getKernelAttr(ctx);            try func_op.op.setAttr(op_attr_names.kernel, kernel_attr);            return func_op;        }        pub fn getName(self: FuncOp) ?[]const u8 {            return ir.SymbolTable.getSymbolName(self.op);        }        pub fn isKernel(self: FuncOp) bool {            return self.op.getAttr(op_attr_names.kernel) != null;        }        pub fn hasBody(self: FuncOp) bool {            return self.op.getRegion(0) != null;        }        pub fn isDeclaration(self: FuncOp) bool {            return !self.hasBody();        }        pub fn getBody(self: FuncOp) *ir.Region {            return self.getRegion("body");        }        pub fn getEntryBlock(self: FuncOp) *ir.Block {            return self.getBody().getEntryBlock().?;        }        pub fn getArguments(self: FuncOp) []*ir.Value {            return self.getEntryBlock().arguments.items;        }        pub fn getNumArguments(self: FuncOp) usize {            if (self.op.getRegion(0)) |region| {                return region.getEntryBlock().?.arguments.items.len;            }            if (self.getInputTypes()) |types| {                return types.len;            }            if (self.op.getAttrAs(ir.Attribute.IntegerAttr, op_attr_names.input_count)) |int_attr| {                return @intCast(int_attr.getValue());            }            return 0;        }        pub fn getInputTypes(self: FuncOp) ?[]const ir.Type {            const type_list_attr = self.op.getAttrAs(ir.Attribute.TypeListAttr, op_attr_names.input_types) orelse return null;            return type_list_attr.getValues();        }        pub fn getInputType(self: FuncOp, index: usize) ?ir.Type {            const types = self.getInputTypes() orelse return null;            if (index >= types.len) return null;            return types[index];        }        pub fn getInputTypesText(self: FuncOp) ?[]const u8 {            if (self.op.getAttr(op_attr_names.input_types_text)) |attr| {                const string_attr = attr.cast(ir.Attribute.StringAttr) orelse return null;                return string_attr.getValue();            }            const string_attr = self.op.getAttrAs(ir.Attribute.StringAttr, op_attr_names.input_types) orelse return null;            return string_attr.getValue();        }        pub fn getArgument(self: FuncOp, index: usize) *ir.Value {            return self.getEntryBlock().arguments.items[index];        }        pub fn getResultTypes(self: FuncOp) []const ir.Type {            return self.op.getResultTypes();        }        pub fn getNumResults(self: FuncOp) usize {            return self.op.results.items.len;        }    };    pub const CallOp = struct {        op: *ir.Operation,        const leaf = op_templates.explicitLeaf(@This(), .{            .mnemonic = "call",            .required_attrs = &.{"callee"},            .interfaces = &.{                ir.interfaces.CallOpInterface.entry(&call_vtable),                effects.EffectOpInterface.entryFor(.{                    .facts = &.{.{ .requirement = .{                        .kind = .callee_contract,                        .subject = .operation,                    } }},                }),            },        });        pub const operation_spec = leaf.operation_spec;        pub const operation_name = leaf.operation_name;        pub const createLeaf = leaf.createLeaf;        pub const verifySymbolUses = verifyCallSymbolUses;        pub fn create(            ctx: *ir.Context,            loc: ir.Location,            callee_name: []const u8,            operands: []const *ir.Value,            result_types: []const ir.Type,        ) !CallOp {            try loadSpec(ctx);            const call = try @This().createLeaf(ctx, loc, operands, result_types);            const op = call.op;            errdefer op.erase();            const callee_attr = try ctx.getFlatSymbolRefAttr(callee_name);            try op.setAttr("callee", callee_attr);            return call;        }        pub fn getCalleeRef(self: CallOp) ?*const ir.Attribute.SymbolRefAttr {            return self.op.getAttrAs(ir.Attribute.SymbolRefAttr, "callee");        }        pub fn getCallee(self: CallOp) ?[]const u8 {            const symbol_ref = self.getCalleeRef() orelse return null;            return symbol_ref.getLeafReference();        }        pub fn getOperands(self: CallOp) []const *ir.Value {            return self.op.getOperandValues();        }        pub fn getNumOperands(self: CallOp) usize {            return self.op.operands.items.len;        }        pub fn getResult(self: *const CallOp, index: usize) ?*ir.Value {            return self.op.getResult(index);        }        pub fn getNumResults(self: CallOp) usize {            return self.op.results.items.len;        }    };    /// Crosses into the kernel and answers the one value the kernel returns.    ///    /// The op belongs to `func` because `func` owns the call boundary. `func.call` names a    /// callee the module can see and `func.syscall` names one it cannot, but both carry the    /// same shape: arguments in operand order, a register convention the target supplies, and    /// a result the callee decides. Nothing here is x86-64. Every Linux port spells this same    /// boundary over a number and at most six arguments, so a target dialect would have been    /// the wrong home for the operand order and the right home only for the instruction.    ///    /// Operand 0 is the number and operands 1 through 6 are the kernel's arguments in the    /// kernel's order. The effect record is deliberately incomplete: what the kernel reads and    /// writes follows from the number, which is an SSA value, so no pass may read the    /// enumerated facts as the whole truth.    pub const SyscallOp = struct {        op: *ir.Operation,        /// The kernel reads at most six arguments, so seven operands is the whole shape.        pub const max_arguments: usize = 6;        const leaf = op_templates.explicitLeaf(@This(), .{            .mnemonic = "syscall",            .operands = ir.dialects.shape.between(1, max_arguments + 1),            .operand_names = .{                "number",                "argument0",                "argument1",                "argument2",                "argument3",                "argument4",                "argument5",            },            .results = .{"result"},            .interfaces = &.{                effects.EffectOpInterface.entryFor(.{                    .facts = &.{.{ .event = .{ .kind = .foreign, .resource = .{ .subject = .operation } } }},                }),            },        });        pub const operation_spec = leaf.operation_spec;        pub const operation_name = leaf.operation_name;        pub const createLeaf = leaf.createLeaf;        pub const verify = verifySyscallOp;        pub fn create(            ctx: *ir.Context,            loc: ir.Location,            number: *ir.Value,            arguments: []const *ir.Value,            result_type: ir.Type,        ) !SyscallOp {            try loadSpec(ctx);            if (arguments.len > max_arguments) return FuncVerifyError.SyscallArgumentCount;            var operands: [max_arguments + 1]*ir.Value = undefined;            operands[0] = number;            @memcpy(operands[1..][0..arguments.len], arguments);            return try @This().createLeaf(ctx, loc, operands[0 .. arguments.len + 1], &.{result_type});        }        pub fn getNumber(self: SyscallOp) *ir.Value {            return self.op.getOperand(0).?;        }        pub fn getArguments(self: SyscallOp) []const *ir.Value {            return self.op.getOperandValues()[1..];        }        pub fn getNumArguments(self: SyscallOp) usize {            return self.op.operands.items.len - 1;        }        pub fn getResult(self: SyscallOp) *ir.Value {            return self.op.getResult(0).?;        }    };    pub const ReturnOp = struct {        op: *ir.Operation,        const term = op_templates.explicitTerminator(@This(), .{            .mnemonic = "return",            .interfaces = &.{                ir.interfaces.YieldOpInterface.entry(&yield_vtable),                effects.EffectOpInterface.entryFor(.{                    .capacity = .{ .per_operand = 1 },                    .enumerate = returnEffects,                }),            },        });        pub const operation_spec = term.operation_spec;        pub const operation_name = term.operation_name;        pub const createTerminator = term.createTerminator;        pub fn create(            ctx: *ir.Context,            loc: ir.Location,            operands: []const *ir.Value,        ) !ReturnOp {            try loadSpec(ctx);            return try @This().createTerminator(ctx, loc, operands, &.{});        }        pub fn getOperands(self: ReturnOp) []const *ir.Value {            return self.op.getOperandValues();        }        pub fn getNumOperands(self: ReturnOp) usize {            return self.op.operands.items.len;        }    };    fn loadSpec(ctx: *ir.Context) !void {        ir.dialects.loadDialectSpec(ctx, spec) catch |err| switch (err) {            error.ContextFrozen => {},            else => return err,        };    }    pub fn getSymNameAttr(ctx: *ir.Context, func_name: []const u8) !ir.Attribute {        return ctx.getDialectAttr(attr_names.sym_name, func_name);    }    pub fn getSymNameValue(attr: ir.Attribute) ?[]const u8 {        if (!std.mem.eql(u8, attr.abstract.name, attr_names.sym_name)) return null;        const dialect_attr = attr.cast(ir.Attribute.DialectAttr) orelse return null;        return dialect_attr.payload;    }    pub fn getSymbolRefValue(attr: ir.Attribute) ?*const ir.Attribute.SymbolRefAttr {        return attr.cast(ir.Attribute.SymbolRefAttr);    }    pub fn getKernelAttr(ctx: *ir.Context) !ir.Attribute {        return ctx.getDialectAttr(attr_names.kernel, "");    }    fn setFunctionSignatureAttrs(        ctx: *ir.Context,        op: *ir.Operation,        input_types: []const ir.Type,    ) !void {        try op.setAttr(op_attr_names.input_count, try ctx.getI64Attr(@intCast(input_types.len)));        try op.setAttr(op_attr_names.input_types, try ctx.getTypeListAttr(input_types));        var input_type_text: std.ArrayListUnmanaged(u8) = .empty;        const allocator = ir.context.transientAllocator(ctx);        defer input_type_text.deinit(allocator);        for (input_types, 0..) |typ, index| {            if (index != 0) try input_type_text.append(allocator, ',');            const rendered = try std.fmt.allocPrint(allocator, "{f}", .{typ});            errdefer allocator.free(rendered);            try input_type_text.appendSlice(allocator, rendered);            allocator.free(rendered);        }        try op.setAttr(op_attr_names.input_types_text, try ctx.getStringAttr(input_type_text.items));    }    fn verifyCallOperands(call: CallOp, callee: FuncOp) !void {        if (callee.op.getRegion(0)) |region| {            const expected_args = region.getEntryBlock().?.arguments.items;            if (call.getNumOperands() != expected_args.len) {                return FuncVerifyError.CallOperandCountMismatch;            }            for (call.op.operands.items, 0..) |operand, index| {                if (!operand.value.type.eql(expected_args[index].type)) {                    return FuncVerifyError.CallOperandTypeMismatch;                }            }            return;        }        const input_types = callee.getInputTypes();        const input_count = if (input_types) |types|            types.len        else if (callee.op.getAttrAs(ir.Attribute.IntegerAttr, op_attr_names.input_count)) |int_attr|            @as(usize, @intCast(int_attr.getValue()))        else            0;        if (call.getNumOperands() != input_count) {            return FuncVerifyError.CallOperandCountMismatch;        }        const expected_types = input_types orelse return FuncVerifyError.MissingFunctionSignature;        for (call.op.operands.items, 0..) |operand, index| {            if (!operand.value.type.eql(expected_types[index])) {                return FuncVerifyError.CallOperandTypeMismatch;            }        }    }    fn verifyCallResults(call: CallOp, callee: FuncOp) !void {        const expected_results = callee.getResultTypes();        if (call.getNumResults() != expected_results.len) {            return FuncVerifyError.CallResultCountMismatch;        }        for (call.op.results.items, 0..) |result, index| {            if (!result.type.eql(expected_results[index])) {                return FuncVerifyError.CallResultTypeMismatch;            }        }    }    /// The declared shape bounds the operand count, so the verifier only states the two facts    /// the shape cannot: a number is present and the kernel's one return value is taken.    fn verifySyscallOp(op_ptr: *const anyopaque) anyerror!void {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        const operand_count = op.operands.items.len;        if (operand_count == 0 or operand_count > SyscallOp.max_arguments + 1) {            return FuncVerifyError.SyscallArgumentCount;        }        if (op.results.items.len != 1) return FuncVerifyError.SyscallMissingResult;    }    fn verifyCallSymbolUses(        op_ptr: *const anyopaque,        symbol_tables: *ir.SymbolTable.Collection,    ) anyerror!void {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        const call = CallOp{ .op = op };        const callee_ref = call.getCalleeRef() orelse return FuncVerifyError.MissingCallee;        const callee_op = try symbol_tables.lookupNearestSymbolRefFrom(op, callee_ref) orelse return FuncVerifyError.UnresolvedCallee;        if (!std.mem.eql(u8, callee_op.name.name, FuncOp.operation_name)) {            return FuncVerifyError.CalleeNotFunction;        }        const callee = FuncOp{ .op = callee_op };        try verifyCallOperands(call, callee);        try verifyCallResults(call, callee);    }    fn getFuncSymbolName(op_ptr: *const anyopaque) ?[]const u8 {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        const attr = op.getAttr(op_attr_names.sym_name) orelse return null;        return getSymNameValue(attr);    }    fn setFuncSymbolName(op_ptr: *const anyopaque, symbol_name: []const u8) anyerror!void {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        try op.setAttr(op_attr_names.sym_name, try getSymNameAttr(op.getContext(), symbol_name));    }    fn isFuncDeclaration(op_ptr: *const anyopaque) bool {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        return (FuncOp{ .op = op }).isDeclaration();    }    fn getCallCalleeSymbol(op_ptr: *const anyopaque) ?[]const u8 {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        const symbol_ref = op.getAttrAs(ir.Attribute.SymbolRefAttr, "callee") orelse return null;        return symbol_ref.getLeafReference();    }    fn getCallCalleeValue(_: *const anyopaque) ?*ir.Value {        return null;    }    fn getCallArgumentValues(op_ptr: *const anyopaque) []const *ir.Value {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        return op.getOperandValues();    }    fn getCallArgumentKeywords(_: *const anyopaque) []const []const u8 {        return &.{};    }    fn getYieldOperandCount(op_ptr: *const anyopaque) usize {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        return op.getOperandValues().len;    }    fn getYieldOperand(op_ptr: *const anyopaque, index: usize) ?*ir.Value {        const op: *const ir.Operation = @ptrCast(@alignCast(op_ptr));        return op.getOperand(index);    }    fn hasFunctionBody(op_ptr: *const anyopaque) bool {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        return (FuncOp{ .op = op }).hasBody();    }    fn getFunctionEntryBlock(op_ptr: *const anyopaque) ?*ir.Block {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        const func = FuncOp{ .op = op };        if (!func.hasBody()) return null;        return func.getEntryBlock();    }    fn getFunctionArgumentCount(op_ptr: *const anyopaque) usize {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        return (FuncOp{ .op = op }).getNumArguments();    }    fn getFunctionResultCount(op_ptr: *const anyopaque) usize {        const op: *ir.Operation = @ptrCast(@alignCast(@constCast(op_ptr)));        return (FuncOp{ .op = op }).getNumResults();    }};const ResourceCounts = struct {    operations: usize,    fn capture(ctx: *const ir.Context) ResourceCounts {        return .{            .operations = ctx.operationCount(),        };    }    fn expectEqual(self: ResourceCounts, ctx: *const ir.Context) !void {        try std.testing.expectEqual(self.operations, ctx.operationCount());    }};fn checkFuncConstructorAllocationFailures(allocator: std.mem.Allocator) !void {    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const baseline = ResourceCounts.capture(&ctx);    const function = FuncDialect.FuncOp.create(&ctx, loc, "function", &.{}, &.{}) catch |err| {        try baseline.expectEqual(&ctx);        return err;    };    function.op.erase();    try baseline.expectEqual(&ctx);    const declaration = FuncDialect.FuncOp.createDeclaration(&ctx, loc, "declaration", &.{}, &.{}) catch |err| {        try baseline.expectEqual(&ctx);        return err;    };    declaration.op.erase();    try baseline.expectEqual(&ctx);    const kernel = FuncDialect.FuncOp.createKernel(&ctx, loc, "kernel", &.{}) catch |err| {        try baseline.expectEqual(&ctx);        return err;    };    kernel.op.erase();    try baseline.expectEqual(&ctx);    const call = FuncDialect.CallOp.create(&ctx, loc, "callee", &.{}, &.{}) catch |err| {        try baseline.expectEqual(&ctx);        return err;    };    call.op.erase();    try baseline.expectEqual(&ctx);}test "FuncDialect constructors clean every allocation failure" {    try std.testing.checkAllAllocationFailures(        std.testing.allocator,        checkFuncConstructorAllocationFailures,        .{},    );}test "FuncDialect.FuncOp creates function" {    const testing = std.testing;    const arith = @import("arith/root.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const func_op = try FuncDialect.FuncOp.create(        &ctx,        loc,        "add",        &.{ i32_type, i32_type },        &.{i32_type},    );    try testing.expectEqualStrings("func.func", func_op.op.name.name);    try testing.expectEqualStrings("add", func_op.getName().?);    try testing.expect(func_op.hasBody());    try testing.expect(!func_op.isDeclaration());    try testing.expectEqual(@as(usize, 2), func_op.getNumArguments());    try testing.expectEqual(@as(usize, 1), func_op.getNumResults());    const input_types = func_op.getInputTypes().?;    try testing.expectEqual(@as(usize, 2), input_types.len);    try testing.expect(input_types[0].eql(i32_type));    try testing.expect(input_types[1].eql(i32_type));    try testing.expectEqualStrings("!arith.i32,!arith.i32", func_op.getInputTypesText().?);    try testing.expect(!func_op.isKernel());    try testing.expect(func_op.op.hasTraitId(ir.traits.IsolatedFromAbove.id));    const iface = func_op.op.interface(ir.interfaces.FunctionOpInterface).?;    try testing.expect(iface.call(.hasBody, .{}));    try testing.expectEqual(func_op.getEntryBlock(), iface.call(.getEntryBlock, .{}).?);    try testing.expectEqual(@as(usize, 2), iface.call(.getArgumentCount, .{}));    try testing.expectEqual(@as(usize, 1), iface.call(.getResultCount, .{}));}test "FuncDialect.FuncOp creates external declaration" {    const testing = std.testing;    const arith = @import("arith/root.zig");    const interfaces = @import("../core/root.zig").interfaces;    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const func_op = try FuncDialect.FuncOp.createDeclaration(        &ctx,        loc,        "external_add",        &.{ i32_type, i32_type },        &.{i32_type},    );    try testing.expectEqualStrings("func.func", func_op.op.name.name);    try testing.expectEqualStrings("external_add", func_op.getName().?);    try testing.expect(!func_op.hasBody());    try testing.expect(func_op.isDeclaration());    try testing.expectEqual(@as(usize, 2), func_op.getNumArguments());    try testing.expectEqual(@as(usize, 1), func_op.getNumResults());    const input_types = func_op.getInputTypes().?;    try testing.expectEqual(@as(usize, 2), input_types.len);    try testing.expect(input_types[0].eql(i32_type));    try testing.expect(input_types[1].eql(i32_type));    try testing.expectEqualStrings("!arith.i32,!arith.i32", func_op.getInputTypesText().?);    try testing.expectEqual(ir.SymbolTable.Visibility.private, ir.SymbolTable.getSymbolVisibility(func_op.op));    try testing.expect(ir.SymbolTable.isDeclaration(func_op.op));    const iface = func_op.op.interface(interfaces.SymbolOpInterface).?;    try testing.expectEqualStrings("external_add", iface.call(.getSymbolName, .{}).?);    try testing.expect(iface.call(.isDeclaration, .{}));    const function_iface = func_op.op.interface(interfaces.FunctionOpInterface).?;    try testing.expect(!function_iface.call(.hasBody, .{}));    try testing.expectEqual(@as(?*ir.Block, null), function_iface.call(.getEntryBlock, .{}));    try testing.expectEqual(@as(usize, 2), function_iface.call(.getArgumentCount, .{}));    try testing.expectEqual(@as(usize, 1), function_iface.call(.getResultCount, .{}));}test "FuncDialect.FuncOp creates kernel" {    const testing = std.testing;    const arith = @import("arith/root.zig");    const memref = @import("memref.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const f32_type = try arith.ArithDialect.getScalarType(&ctx, .f32);    const memref_type = try memref.MemrefDialect.getMemrefType1D(&ctx, 1024, f32_type, .device);    const kernel_op = try FuncDialect.FuncOp.createKernel(        &ctx,        loc,        "vector_add",        &.{ memref_type, memref_type, memref_type },    );    try testing.expectEqualStrings("func.func", kernel_op.op.name.name);    try testing.expectEqualStrings("vector_add", kernel_op.getName().?);    try testing.expectEqual(@as(usize, 3), kernel_op.getNumArguments());    try testing.expectEqual(@as(usize, 0), kernel_op.getNumResults());    try testing.expect(kernel_op.isKernel());}test "FuncDialect.CallOp creates function call" {    const testing = std.testing;    const arith = @import("arith/root.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    var c1 = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 10);    var c2 = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 20);    var call_op = try FuncDialect.CallOp.create(        &ctx,        loc,        "add",        &.{ c1.getResult(), c2.getResult() },        &.{i32_type},    );    try testing.expectEqualStrings("func.call", call_op.op.name.name);    try testing.expectEqualStrings("add", call_op.getCallee().?);    const callee_ref = call_op.getCalleeRef().?;    try testing.expect(callee_ref.isFlat());    try testing.expectEqualStrings("add", callee_ref.getRootReference());    try testing.expectEqualStrings(ir.builtin_attr_names.symbol_ref, call_op.op.getAttr("callee").?.abstract.name);    try testing.expectEqual(@as(usize, 2), call_op.getNumOperands());    try testing.expectEqual(@as(usize, 1), call_op.getNumResults());    const call_operands = call_op.getOperands();    try testing.expectEqual(@as(usize, 2), call_operands.len);    try testing.expect(call_operands[0] == c1.getResult());    try testing.expect(call_operands[1] == c2.getResult());    const interfaces = @import("../core/root.zig").interfaces;    const iface = call_op.op.interface(interfaces.CallOpInterface).?;    try testing.expectEqualStrings("add", iface.call(.getCalleeSymbol, .{}).?);    const iface_args = iface.call(.getArgumentValues, .{});    try testing.expectEqual(@as(usize, 2), iface_args.len);    try testing.expect(iface_args[0] == c1.getResult());    try testing.expect(iface_args[1] == c2.getResult());    try testing.expectEqual(@as(usize, 0), iface.call(.getArgumentKeywords, .{}).len);    try testing.expect(call_op.op.hasInterface(interfaces.SymbolUserOpInterface));}test "FuncDialect.CallOp verifier accepts matching declaration signature" {    const testing = std.testing;    const arith = @import("arith/root.zig");    const builtin = @import("builtin.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);    const module_block = module.getBodyBlock();    const callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "extern_i32", &.{i32_type}, &.{i32_type});    try module_block.addOperation(callee.op);    var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});    try module_block.addOperation(caller.op);    const entry = caller.getEntryBlock();    const arg0 = caller.getArgument(0);    var call = try FuncDialect.CallOp.create(&ctx, loc, "extern_i32", &.{arg0}, &.{i32_type});    try entry.addOperation(call.op);    try ir.verifyOperation(module.op, ir.verify.default_options);    try testing.expectEqual(@as(usize, 1), call.getNumOperands());}test "FuncDialect.CallOp verifier rejects unresolved callee" {    const arith = @import("arith/root.zig");    const builtin = @import("builtin.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);    const module_block = module.getBodyBlock();    var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});    try module_block.addOperation(caller.op);    const entry = caller.getEntryBlock();    const call = try FuncDialect.CallOp.create(&ctx, loc, "missing", &.{caller.getArgument(0)}, &.{i32_type});    try entry.addOperation(call.op);    try std.testing.expectError(FuncVerifyError.UnresolvedCallee, ir.verifyOperation(module.op, ir.verify.default_options));}test "FuncDialect.CallOp verifier uses nearest symbol table" {    const arith = @import("arith/root.zig");    const builtin = @import("builtin.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const outer = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);    const outer_block = outer.getBodyBlock();    const outer_callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "outer", &.{i32_type}, &.{i32_type});    try outer_block.addOperation(outer_callee.op);    const inner = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);    try outer_block.addOperation(inner.op);    const inner_block = inner.getBodyBlock();    var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i32_type});    try inner_block.addOperation(caller.op);    const entry = caller.getEntryBlock();    const call = try FuncDialect.CallOp.create(&ctx, loc, "outer", &.{caller.getArgument(0)}, &.{i32_type});    try entry.addOperation(call.op);    try std.testing.expectError(FuncVerifyError.UnresolvedCallee, ir.verifyOperation(outer.op, ir.verify.default_options));}test "FuncDialect.CallOp verifier rejects operand type mismatch" {    const arith = @import("arith/root.zig");    const builtin = @import("builtin.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);    const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);    const module_block = module.getBodyBlock();    const callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "extern_i32", &.{i32_type}, &.{i32_type});    try module_block.addOperation(callee.op);    var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i64_type}, &.{i32_type});    try module_block.addOperation(caller.op);    const entry = caller.getEntryBlock();    const call = try FuncDialect.CallOp.create(&ctx, loc, "extern_i32", &.{caller.getArgument(0)}, &.{i32_type});    try entry.addOperation(call.op);    try std.testing.expectError(FuncVerifyError.CallOperandTypeMismatch, ir.verifyOperation(module.op, ir.verify.default_options));}test "FuncDialect.CallOp verifier rejects result type mismatch" {    const arith = @import("arith/root.zig");    const builtin = @import("builtin.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, builtin.BuiltinDialect.spec);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);    const module = try builtin.BuiltinDialect.ModuleOp.create(&ctx, loc);    const module_block = module.getBodyBlock();    const callee = try FuncDialect.FuncOp.createDeclaration(&ctx, loc, "extern_i32", &.{i32_type}, &.{i32_type});    try module_block.addOperation(callee.op);    var caller = try FuncDialect.FuncOp.create(&ctx, loc, "caller", &.{i32_type}, &.{i64_type});    try module_block.addOperation(caller.op);    const entry = caller.getEntryBlock();    const call = try FuncDialect.CallOp.create(&ctx, loc, "extern_i32", &.{caller.getArgument(0)}, &.{i64_type});    try entry.addOperation(call.op);    try std.testing.expectError(FuncVerifyError.CallResultTypeMismatch, ir.verifyOperation(module.op, ir.verify.default_options));}test "FuncDialect.SyscallOp carries a number and the kernel's arguments" {    const testing = std.testing;    const arith = @import("arith/root.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);    var number = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 39);    var first = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 1);    var second = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i64_type, 2);    const entered = try FuncDialect.SyscallOp.create(        &ctx,        loc,        number.getResult(),        &.{ first.getResult(), second.getResult() },        i64_type,    );    try testing.expectEqualStrings("func.syscall", entered.op.name.name);    try testing.expect(entered.getNumber() == number.getResult());    try testing.expectEqual(@as(usize, 2), entered.getNumArguments());    const arguments = entered.getArguments();    try testing.expect(arguments[0] == first.getResult());    try testing.expect(arguments[1] == second.getResult());    try testing.expect(entered.getResult().type.eql(i64_type));    try ir.verifyOperation(entered.op, ir.verify.default_options);    const bare = try FuncDialect.SyscallOp.create(&ctx, loc, number.getResult(), &.{}, i64_type);    try testing.expectEqual(@as(usize, 0), bare.getNumArguments());    try ir.verifyOperation(bare.op, ir.verify.default_options);    var seven: [FuncDialect.SyscallOp.max_arguments + 1]*ir.Value = undefined;    for (&seven) |*argument| argument.* = first.getResult();    try testing.expectError(        FuncVerifyError.SyscallArgumentCount,        FuncDialect.SyscallOp.create(&ctx, loc, number.getResult(), &seven, i64_type),    );}test "FuncDialect.ReturnOp creates return" {    const testing = std.testing;    const arith = @import("arith/root.zig");    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    var val = try arith.ArithDialect.ConstantOp.createInt(&ctx, loc, i32_type, 42);    const ret_op = try FuncDialect.ReturnOp.create(&ctx, loc, &.{val.getResult()});    try testing.expectEqualStrings("func.return", ret_op.op.name.name);    try testing.expectEqual(@as(usize, 1), ret_op.getNumOperands());    const ret_operands = ret_op.getOperands();    try testing.expectEqual(@as(usize, 1), ret_operands.len);    try testing.expect(ret_operands[0] == val.getResult());    const iface = ret_op.op.interface(ir.interfaces.YieldOpInterface).?;    try testing.expectEqual(@as(usize, 1), iface.call(.getYieldOperandCount, .{}));    try testing.expect(iface.call(.getYieldOperand, .{0}).? == val.getResult());    try testing.expectEqual(@as(?*ir.Value, null), iface.call(.getYieldOperand, .{1}));}test "FuncDialect.FuncOp SymbolOpInterface" {    const testing = std.testing;    const arith = @import("arith/root.zig");    const interfaces = @import("../core/root.zig").interfaces;    var arena = alloc_arena.Arena.init(std.testing.allocator);    defer arena.deinit();    const allocator = arena.allocator();    var ctx = try ir.Context.init(allocator, ir.Context.Limits.testing);    defer ctx.deinit(allocator);    try ir.dialects.loadDialectSpec(&ctx, FuncDialect.spec);    const loc = ir.Location.getUnknown();    const i32_type = try arith.ArithDialect.getI32Type(&ctx);    const func_op = try FuncDialect.FuncOp.create(        &ctx,        loc,        "my_function",        &.{i32_type},        &.{i32_type},    );    const iface = func_op.op.interface(interfaces.SymbolOpInterface).?;    try testing.expectEqualStrings("my_function", iface.call(.getSymbolName, .{}).?);    try testing.expect(!iface.call(.isDeclaration, .{}));    try testing.expect(!ir.SymbolTable.isDeclaration(func_op.op));}fn functionEffects(op: *const ir.Operation, collector: *effects.Collector) void {    for (0..op.getNumRegions()) |index| collector.append(.{ .region = .{        .index = index,        .execution = .latent,        .may_diverge = false,        .captures = false,    } });}fn returnEffects(op: *const ir.Operation, collector: *effects.Collector) void {    for (0..op.getNumOperands()) |index| collector.append(.{ .event = .{        .kind = .move,        .resource = .{ .subject = .{ .operand = index } },    } });}test "func effect declarations keep definitions latent and calls unresolved" {    var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);    defer ctx.deinit(std.testing.allocator);    const function = try FuncDialect.FuncOp.create(&ctx, .unknown, "effect_function", &.{}, &.{});    const call = try FuncDialect.CallOp.create(&ctx, .unknown, "effect_function", &.{}, &.{});    var definition = try effects.inspect(std.testing.allocator, function.op);    defer definition.deinit(std.testing.allocator);    try std.testing.expectEqual(        effects.Execution.latent,        definition.facts.records[0].region.execution,    );    try std.testing.expect(!definition.facts.records[0].region.may_diverge);    var invocation = try effects.inspect(std.testing.allocator, call.op);    defer invocation.deinit(std.testing.allocator);    try std.testing.expectEqual(        effects.RequirementKind.callee_contract,        invocation.facts.records[0].requirement.kind,    );    try std.testing.expect(!effects.discard(invocation.facts));}

Source: lib/choir/src/dialects/root.zig:8

zig
pub const func = @import("func.zig");

Audit

Definitions2
Public names2
Members10
Version26.7.0
Revisiondaab053ee433