Skip to documentation
SLOP

tiny.choir.ir.equivalence

Reference tiny.choir ir equivalence

Defined in ir.

API (1)

Types and contracts

Public types and contracts.

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

Source

Source: lib/choir/src/core/equivalence.zig

zig
const std = @import("std");const alloc_arena = @import("alloc_arena");const Operation = @import("operation/root.zig").Operation;const Value = @import("value.zig").Value;const Attribute = @import("attribute.zig").Attribute;const NamedAttribute = @import("attribute.zig").NamedAttribute;const context_mod = @import("context/root.zig");const Context = context_mod.Context;const Location = @import("location.zig").Location;const interfaces = @import("interfaces/root.zig");pub const OperationEquivalence = struct {    pub const ValueMapper = *const fn (?*const anyopaque, *Value) *Value;    pub const AttributeFilter = *const fn (?*const anyopaque, *Operation, NamedAttribute) bool;    pub const Options = struct {        value_mapper: ?ValueMapper = null,        value_mapper_context: ?*const anyopaque = null,        attribute_filter: ?AttributeFilter = null,        attribute_filter_context: ?*const anyopaque = null,        commute_operands: bool = false,        include_locations: bool = false,    };    pub fn computeHash(        op: *Operation,        options: Options,    ) u64 {        var hasher = std.hash.Wyhash.init(0);        updateBytes(&hasher, op.name.name);        updateU64(&hasher, op.regions.items.len);        updateU64(&hasher, op.successors.items.len);        updateU64(&hasher, op.result_types.len);        for (op.result_types) |typ| {            updateU64(&hasher, typ.uniqueId());        }        if (options.include_locations) {            updateLocation(&hasher, op.location);        }        const operands = op.operand_values;        updateU64(&hasher, operands.len);        if (options.commute_operands and operands.len > 1) {            var operand_hash: u64 = 0;            for (operands) |operand| {                operand_hash +%= valueHash(options, operand);            }            updateU64(&hasher, operand_hash);        } else {            for (operands) |operand| {                updateU64(&hasher, valueHash(options, operand));            }        }        var hash_iter = op.getAttrs();        while (hash_iter.next()) |attr| {            if (!attributeIncluded(options, op, attr)) continue;            updateBytes(&hasher, attr.name);            updateAttribute(&hasher, attr.value);        }        return hasher.final();    }    pub fn isEquivalentTo(        lhs: *Operation,        rhs: *Operation,        options: Options,    ) bool {        if (lhs == rhs) return true;        if (!sameName(lhs.name.name, rhs.name.name)) return false;        if (lhs.regions.items.len != rhs.regions.items.len) return false;        if (lhs.successors.items.len != rhs.successors.items.len) return false;        if (lhs.operands.items.len != rhs.operands.items.len) return false;        if (lhs.result_types.len != rhs.result_types.len) return false;        if (options.include_locations and !lhs.location.eql(rhs.location)) return false;        for (lhs.result_types, rhs.result_types) |lhs_type, rhs_type| {            if (!lhs_type.eql(rhs_type)) return false;        }        if (!operandsEquivalent(lhs.operand_values, rhs.operand_values, options)) {            return false;        }        return attrsEquivalent(lhs, rhs, options);    }    pub fn exactValueMapper(_: ?*const anyopaque, value: *Value) *Value {        return value;    }    fn valueHash(options: Options, value: *Value) u64 {        return @intFromPtr(mapValue(options, value));    }    fn mapValue(options: Options, value: *Value) *Value {        if (options.value_mapper) |mapper| {            return mapper(options.value_mapper_context, value);        }        return value;    }    fn attributeIncluded(options: Options, op: *Operation, attr: NamedAttribute) bool {        if (options.attribute_filter) |filter| {            return filter(options.attribute_filter_context, op, attr);        }        return true;    }};fn operandsEquivalent(    lhs: []const *Value,    rhs: []const *Value,    options: OperationEquivalence.Options,) bool {    if (lhs.len != rhs.len) return false;    if (!options.commute_operands or lhs.len <= 1) {        for (lhs, rhs) |lhs_value, rhs_value| {            if (OperationEquivalence.mapValue(options, lhs_value) !=                OperationEquivalence.mapValue(options, rhs_value))            {                return false;            }        }        return true;    }    if (lhs.len == 2) {        const lhs_0 = OperationEquivalence.mapValue(options, lhs[0]);        const lhs_1 = OperationEquivalence.mapValue(options, lhs[1]);        const rhs_0 = OperationEquivalence.mapValue(options, rhs[0]);        const rhs_1 = OperationEquivalence.mapValue(options, rhs[1]);        return (lhs_0 == rhs_0 and lhs_1 == rhs_1) or            (lhs_0 == rhs_1 and lhs_1 == rhs_0);    }    for (lhs, 0..) |lhs_value, lhs_index| {        const mapped_lhs = OperationEquivalence.mapValue(options, lhs_value);        var seen = false;        for (lhs[0..lhs_index]) |earlier_value| {            if (OperationEquivalence.mapValue(options, earlier_value) == mapped_lhs) {                seen = true;                break;            }        }        if (seen) continue;        var lhs_count: usize = 0;        for (lhs) |value| {            lhs_count += @intFromBool(OperationEquivalence.mapValue(options, value) == mapped_lhs);        }        var rhs_count: usize = 0;        for (rhs) |value| {            rhs_count += @intFromBool(OperationEquivalence.mapValue(options, value) == mapped_lhs);        }        if (lhs_count != rhs_count) return false;    }    return true;}fn attrsEquivalent(lhs: *Operation, rhs: *Operation, options: OperationEquivalence.Options) bool {    var lhs_iter = lhs.getAttrs();    var rhs_iter = rhs.getAttrs();    while (true) {        const lhs_attr = nextIncluded(&lhs_iter, lhs, options);        const rhs_attr = nextIncluded(&rhs_iter, rhs, options);        if (lhs_attr == null or rhs_attr == null) return lhs_attr == null and rhs_attr == null;        if (!sameName(lhs_attr.?.name, rhs_attr.?.name)) return false;        if (!lhs_attr.?.value.eql(rhs_attr.?.value)) return false;    }}fn nextIncluded(    attrs: *Operation.AttributeIterator,    op: *Operation,    options: OperationEquivalence.Options,) ?NamedAttribute {    while (attrs.next()) |attr| {        if (OperationEquivalence.attributeIncluded(options, op, attr)) return attr;    }    return null;}fn sameName(lhs: []const u8, rhs: []const u8) bool {    return (lhs.ptr == rhs.ptr and lhs.len == rhs.len) or std.mem.eql(u8, lhs, rhs);}fn updateLocation(hasher: *std.hash.Wyhash, loc: Location) void {    const Tag = std.meta.Tag(Location);    updateU64(hasher, @backingInt(@as(Tag, loc)));    switch (loc) {        .unknown => {},        .file => |file| {            updateBytes(hasher, file.filename);            updateU64(hasher, file.line);            updateU64(hasher, file.column);        },        .file_range => |range| {            updateBytes(hasher, range.filename);            updateU64(hasher, range.start.byte);            updateU64(hasher, range.start.line);            updateU64(hasher, range.start.column);            updateU64(hasher, range.end.byte);            updateU64(hasher, range.end.line);            updateU64(hasher, range.end.column);        },        .name => |name| {            updateBytes(hasher, name.name);            updateU64(hasher, if (name.child == null) 0 else 1);            if (name.child) |child| updateLocation(hasher, child.*);        },        .fused => |fused| {            updateU64(hasher, fused.locations.len);            for (fused.locations) |child| {                updateLocation(hasher, child);            }            updateU64(hasher, if (fused.metadata) |metadata| @intFromPtr(metadata) else 0);        },        .call_site => |call_site| {            updateLocation(hasher, call_site.callee.*);            updateLocation(hasher, call_site.caller.*);        },    }}fn updateAttribute(hasher: *std.hash.Wyhash, attr: Attribute) void {    updateU64(hasher, @backingInt(attr.attr_id));    updateBytes(hasher, attr.abstract.name);    if (attr.cast(Attribute.IntegerAttr)) |int_attr| {        updateU64(hasher, @bitCast(int_attr.value));        updateU64(hasher, int_attr.width);        updateU64(hasher, if (int_attr.is_signed) 1 else 0);        return;    }    if (attr.cast(Attribute.FloatAttr)) |float_attr| {        updateU64(hasher, @bitCast(float_attr.value));        updateU64(hasher, float_attr.width);        return;    }    if (attr.cast(Attribute.BoolAttr)) |bool_attr| {        updateU64(hasher, if (bool_attr.value) 1 else 0);        return;    }    if (attr.cast(Attribute.StringAttr)) |string_attr| {        updateBytes(hasher, string_attr.value);        return;    }    if (attr.cast(Attribute.SymbolRefAttr)) |symbol_ref| {        updateBytes(hasher, symbol_ref.root_reference);        updateU64(hasher, symbol_ref.nested_references.len);        for (symbol_ref.nested_references) |nested| {            updateBytes(hasher, nested);        }        return;    }    if (attr.cast(Attribute.StringListAttr)) |list_attr| {        updateU64(hasher, list_attr.values.len);        for (list_attr.values) |value| {            updateBytes(hasher, value);        }        return;    }    if (attr.cast(Attribute.TypeListAttr)) |list_attr| {        updateU64(hasher, list_attr.values.len);        for (list_attr.values) |typ| {            updateU64(hasher, typ.uniqueId());        }        return;    }    if (attr.cast(Attribute.ArrayAttr)) |array_attr| {        updateU64(hasher, array_attr.values.len);        for (array_attr.values) |value| {            updateAttribute(hasher, value);        }        return;    }    if (attr.cast(Attribute.DialectAttr)) |dialect_attr| {        updateBytes(hasher, dialect_attr.payload);        return;    }    updateU64(hasher, @intFromPtr(attr.impl));}fn updateBytes(hasher: *std.hash.Wyhash, bytes: []const u8) void {    updateU64(hasher, bytes.len);    hasher.update(bytes);}fn updateU64(hasher: *std.hash.Wyhash, value: u64) void {    var buf: [8]u8 = undefined;    std.mem.writeInt(u64, &buf, value, .little);    hasher.update(&buf);}fn operationWithOperands(    ctx: *Context,    name: []const u8,    operands: []const *Value,    result_type: @import("type.zig").Type,) !*Operation {    var state = Operation.State.init(name, Location.getUnknown());    state.addOperands(operands);    state.addTypes(&.{result_type});    return try ctx.createOperation(state);}fn ignoreDebugAttr(_: ?*const anyopaque, _: *Operation, attr: NamedAttribute) bool {    return !std.mem.eql(u8, attr.name, "debug.span");}const RemapPair = struct {    from: *Value,    to: *Value,};fn pairValueMapper(mapper_context: ?*const anyopaque, value: *Value) *Value {    const pair: *const RemapPair = @ptrCast(@alignCast(mapper_context.?));    if (value == pair.from) return pair.to;    return value;}test "OperationEquivalence matches same operation shape" {    const testing = std.testing;    const allocator = testing.allocator;    var arena = alloc_arena.Arena.init(allocator);    defer arena.deinit();    const arena_alloc = arena.allocator();    var ctx = try Context.init(arena_alloc, Context.Limits.testing);    defer ctx.deinit(arena_alloc);    try ctx.allowUnregistered();    var region = context_mod.initRegion(&ctx);    const block = try region.addBlock();    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const lhs = try block.addArgument(i32_type, Location.getUnknown());    const rhs = try block.addArgument(i32_type, Location.getUnknown());    const first = try operationWithOperands(&ctx, "test.add", &.{ lhs, rhs }, i32_type);    const second = try operationWithOperands(&ctx, "test.add", &.{ lhs, rhs }, i32_type);    try first.setAttr("value", try ctx.getI64Attr(1));    try second.setAttr("value", try ctx.getI64Attr(1));    const first_hash = OperationEquivalence.computeHash(first, .{});    const second_hash = OperationEquivalence.computeHash(second, .{});    try testing.expectEqual(first_hash, second_hash);    try testing.expect(OperationEquivalence.isEquivalentTo(first, second, .{}));}test "OperationEquivalence keeps distinct attrs apart" {    const testing = std.testing;    const allocator = testing.allocator;    var arena = alloc_arena.Arena.init(allocator);    defer arena.deinit();    const arena_alloc = arena.allocator();    var ctx = try Context.init(arena_alloc, Context.Limits.testing);    defer ctx.deinit(arena_alloc);    try ctx.allowUnregistered();    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const first = try operationWithOperands(&ctx, "test.constant", &.{}, i32_type);    const second = try operationWithOperands(&ctx, "test.constant", &.{}, i32_type);    try first.setAttr("value", try ctx.getI64Attr(1));    try second.setAttr("value", try ctx.getI64Attr(2));    const first_hash = OperationEquivalence.computeHash(first, .{});    const second_hash = OperationEquivalence.computeHash(second, .{});    try testing.expect(first_hash != second_hash);    try testing.expect(!OperationEquivalence.isEquivalentTo(first, second, .{}));}test "OperationEquivalence filters nonsemantic attrs" {    const testing = std.testing;    const allocator = testing.allocator;    var arena = alloc_arena.Arena.init(allocator);    defer arena.deinit();    const arena_alloc = arena.allocator();    var ctx = try Context.init(arena_alloc, Context.Limits.testing);    defer ctx.deinit(arena_alloc);    try ctx.allowUnregistered();    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const first = try operationWithOperands(&ctx, "test.constant", &.{}, i32_type);    const second = try operationWithOperands(&ctx, "test.constant", &.{}, i32_type);    try first.setAttr("debug.span", try ctx.getI64Attr(1));    try second.setAttr("debug.span", try ctx.getI64Attr(2));    const options = OperationEquivalence.Options{ .attribute_filter = ignoreDebugAttr };    const first_hash = OperationEquivalence.computeHash(first, options);    const second_hash = OperationEquivalence.computeHash(second, options);    try testing.expectEqual(first_hash, second_hash);    try testing.expect(OperationEquivalence.isEquivalentTo(first, second, options));}test "OperationEquivalence hashes inherent attributes without temporary allocation" {    const testing = std.testing;    var ctx = try Context.init(testing.allocator, Context.Limits.testing);    defer ctx.deinit(testing.allocator);    try ctx.allowUnregistered();    _ = try ctx.registerOperation("test.constant", .{});    try ctx.registerOperationInherentAttributeName("test.constant", "value");    try ctx.registerOperationPropertiesModel(        "test.constant",        interfaces.singleAttributePropertiesModel("test.constant.properties", "value"),    );    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const first = try operationWithOperands(&ctx, "test.constant", &.{}, i32_type);    const second = try operationWithOperands(&ctx, "test.constant", &.{}, i32_type);    try first.setAttr("value", try ctx.getI64Attr(7));    try second.setAttr("value", try ctx.getI64Attr(8));    const first_hash = OperationEquivalence.computeHash(first, .{});    const second_hash = OperationEquivalence.computeHash(second, .{});    try testing.expect(first_hash != second_hash);    try testing.expect(!OperationEquivalence.isEquivalentTo(first, second, .{}));}test "OperationEquivalence commutes operands" {    const testing = std.testing;    const allocator = testing.allocator;    var arena = alloc_arena.Arena.init(allocator);    defer arena.deinit();    const arena_alloc = arena.allocator();    var ctx = try Context.init(arena_alloc, Context.Limits.testing);    defer ctx.deinit(arena_alloc);    try ctx.allowUnregistered();    var region = context_mod.initRegion(&ctx);    const block = try region.addBlock();    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const lhs = try block.addArgument(i32_type, Location.getUnknown());    const rhs = try block.addArgument(i32_type, Location.getUnknown());    const first = try operationWithOperands(&ctx, "test.add", &.{ lhs, rhs }, i32_type);    const second = try operationWithOperands(&ctx, "test.add", &.{ rhs, lhs }, i32_type);    const options = OperationEquivalence.Options{ .commute_operands = true };    const first_hash = OperationEquivalence.computeHash(first, options);    const second_hash = OperationEquivalence.computeHash(second, options);    try testing.expectEqual(first_hash, second_hash);    try testing.expect(OperationEquivalence.isEquivalentTo(first, second, options));    try testing.expect(!OperationEquivalence.isEquivalentTo(first, second, .{}));}test "OperationEquivalence preserves multiplicity for variadic commutative operands" {    const testing = std.testing;    const allocator = testing.allocator;    var arena = alloc_arena.Arena.init(allocator);    defer arena.deinit();    const arena_alloc = arena.allocator();    var ctx = try Context.init(arena_alloc, Context.Limits.testing);    defer ctx.deinit(arena_alloc);    try ctx.allowUnregistered();    var region = context_mod.initRegion(&ctx);    const block = try region.addBlock();    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const lhs = try block.addArgument(i32_type, Location.getUnknown());    const rhs = try block.addArgument(i32_type, Location.getUnknown());    const extra = try block.addArgument(i32_type, Location.getUnknown());    const first = try operationWithOperands(        &ctx,        "test.combine",        &.{ lhs, rhs, lhs, extra },        i32_type,    );    const reordered = try operationWithOperands(        &ctx,        "test.combine",        &.{ extra, lhs, rhs, lhs },        i32_type,    );    const different = try operationWithOperands(        &ctx,        "test.combine",        &.{ extra, lhs, rhs, rhs },        i32_type,    );    const options = OperationEquivalence.Options{ .commute_operands = true };    try testing.expectEqual(        OperationEquivalence.computeHash(first, options),        OperationEquivalence.computeHash(reordered, options),    );    try testing.expect(OperationEquivalence.isEquivalentTo(first, reordered, options));    try testing.expect(!OperationEquivalence.isEquivalentTo(first, different, options));}test "OperationEquivalence maps values before comparing operands" {    const testing = std.testing;    const allocator = testing.allocator;    var arena = alloc_arena.Arena.init(allocator);    defer arena.deinit();    const arena_alloc = arena.allocator();    var ctx = try Context.init(arena_alloc, Context.Limits.testing);    defer ctx.deinit(arena_alloc);    try ctx.allowUnregistered();    var region = context_mod.initRegion(&ctx);    const block = try region.addBlock();    const i32_type = try ctx.getDialectTypeFromName("test.i32");    const canonical = try block.addArgument(i32_type, Location.getUnknown());    const stale = try block.addArgument(i32_type, Location.getUnknown());    const first = try operationWithOperands(&ctx, "test.use", &.{canonical}, i32_type);    const second = try operationWithOperands(&ctx, "test.use", &.{stale}, i32_type);    const pair = RemapPair{ .from = stale, .to = canonical };    const options = OperationEquivalence.Options{        .value_mapper = pairValueMapper,        .value_mapper_context = &pair,    };    const first_hash = OperationEquivalence.computeHash(first, options);    const second_hash = OperationEquivalence.computeHash(second, options);    try testing.expectEqual(first_hash, second_hash);    try testing.expect(OperationEquivalence.isEquivalentTo(first, second, options));    try testing.expect(!OperationEquivalence.isEquivalentTo(first, second, .{}));}

Source: lib/choir/src/core/root.zig:16

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

Also reachable as

backends.wasm.emission.module_encoding.common.ir.equivalence.

Audit

Definitions1
Public names2
Members0
Version26.7.0
Revisiondaab053ee433