lib/choir/src/dialects/rc.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const ir = @import("../core/root.zig");
  3 const effects = ir.interfaces.effects;
  4 
  5 pub const attr_names = struct {
  6     pub const ownership = "rc.ownership";
  7 };
  8 
  9 pub const RcDialect = struct {
 10     pub const name = "rc";
 11     const op_templates = ir.dialects.operationTemplate.dialect(@This());
 12     pub const spec = ir.dialects.dialectSpec(@This(), .{});
 13 
 14     pub const RetainOp: type = op_templates.unarySameType(
 15         "retain",
 16         ownershipOptions(.retain, .owned),
 17     );
 18 
 19     pub const ReleaseOp: type = op_templates.unaryNoResult(
 20         "release",
 21         ownershipOptions(.release, .none),
 22     );
 23 
 24     pub const BorrowOp: type = op_templates.unarySameTypeStringAttr(
 25         "borrow",
 26         attr_names.ownership,
 27         "borrowed",
 28         ownershipOptions(.borrow, .borrowed),
 29     );
 30 
 31     pub const MoveOp: type = op_templates.unarySameTypeStringAttr(
 32         "move",
 33         attr_names.ownership,
 34         "owned",
 35         ownershipOptions(.move, .transferred),
 36     );
 37 };
 38 
 39 test "rc dialect ops preserve value type" {
 40     const arith = @import("arith/root.zig");
 41     const test_dialect = @import("fixture/root.zig");
 42 
 43     var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
 44     defer ctx.deinit(std.testing.allocator);
 45 
 46     try ir.dialects.loadDialectSpec(&ctx, RcDialect.spec);
 47     try test_dialect.registerTestDialect(&ctx);
 48     _ = try ctx.registerOperation("test.source", .{});
 49 
 50     const retain_info = ctx.lookupOperation(RcDialect.RetainOp.operation_name) orelse return error.TestExpectedRetainOp;
 51     const release_info = ctx.lookupOperation(RcDialect.ReleaseOp.operation_name) orelse return error.TestExpectedReleaseOp;
 52     const borrow_info = ctx.lookupOperation(RcDialect.BorrowOp.operation_name) orelse return error.TestExpectedBorrowOp;
 53 
 54     try std.testing.expect(retain_info.shape.operands.allows(1));
 55     try std.testing.expect(!retain_info.shape.operands.allows(2));
 56     try std.testing.expect(retain_info.shape.results.allows(1));
 57     try std.testing.expect(release_info.shape.operands.allows(1));
 58     try std.testing.expect(release_info.shape.results.allows(0));
 59     try std.testing.expect(!release_info.shape.results.allows(1));
 60     try std.testing.expect(borrow_info.hasInherentAttributeName(attr_names.ownership));
 61 
 62     const loc = ir.Location.getUnknown();
 63     const i64_type = try arith.ArithDialect.getScalarType(&ctx, .i64);
 64     var state = ir.Operation.State.init("test.source", loc);
 65     state.addTypes(&.{i64_type});
 66     const source = try ctx.createOperation(state);
 67     const value = source.getResult(0).?;
 68 
 69     const retained = try RcDialect.RetainOp.create(&ctx, loc, value);
 70     const borrowed = try RcDialect.BorrowOp.create(&ctx, loc, retained.getResult());
 71     const moved = try RcDialect.MoveOp.create(&ctx, loc, borrowed.getResult());
 72     const release = try RcDialect.ReleaseOp.create(&ctx, loc, moved.getResult());
 73 
 74     try std.testing.expect(retained.getResult().type.eql(i64_type));
 75     try std.testing.expect(borrowed.getResult().type.eql(i64_type));
 76     try std.testing.expect(moved.getResult().type.eql(i64_type));
 77     try std.testing.expectEqualStrings("borrowed", borrowed.getStringAttr().?);
 78     try std.testing.expectEqualStrings("owned", moved.getStringAttr().?);
 79     try std.testing.expect(release.getInput() == moved.getResult());
 80     try std.testing.expectEqual(@as(usize, 0), release.op.results.items.len);
 81 }
 82 
 83 fn ownershipOptions(
 84     comptime kind: effects.EventKind,
 85     comptime ownership: effects.Ownership,
 86 ) ir.dialects.opSpec.Options {
 87     const Declaration = struct {
 88         fn enumerate(op: *const ir.Operation, collector: *effects.Collector) void {
 89             if (op.getNumOperands() != 1) return;
 90             collector.append(.{ .requirement = .{ .kind = .live, .subject = .{ .operand = 0 } } });
 91             collector.append(.{ .event = .{ .kind = kind, .resource = .{
 92                 .subject = .{ .operand = 0 },
 93             } } });
 94             if (kind == .release) {
 95                 collector.append(.{ .requirement = .{
 96                     .kind = .callee_contract,
 97                     .subject = .{ .operand = 0 },
 98                 } });
 99                 collector.append(.{ .event = .{ .kind = .free, .resource = .{
100                     .subject = .{ .operand = 0 },
101                 } } });
102             }
103             for (0..op.getNumResults()) |index| collector.append(.{ .result = .{
104                 .index = index,
105                 .alias = .{ .operand = 0 },
106                 .ownership = ownership,
107             } });
108         }
109     };
110     return .{ .interfaces = &.{effects.EffectOpInterface.entryFor(.{
111         .capacity = .{ .entries = 4, .per_result = 1 },
112         .enumerate = Declaration.enumerate,
113     })} };
114 }
115 
116 test "rc effect declarations retain ownership events and result aliases" {
117     const arith = @import("arith/root.zig").ArithDialect;
118     var ctx = try ir.Context.init(std.testing.allocator, ir.Context.Limits.testing);
119     defer ctx.deinit(std.testing.allocator);
120     try ir.dialects.loadDialectSpec(&ctx, RcDialect.spec);
121     const typ = try arith.getScalarType(&ctx, .i64);
122     const input = try arith.ConstantOp.createInt(&ctx, .unknown, typ, 1);
123     const retained = try RcDialect.RetainOp.create(&ctx, .unknown, input.getResult());
124     const borrowed = try RcDialect.BorrowOp.create(&ctx, .unknown, retained.getResult());
125     const moved = try RcDialect.MoveOp.create(&ctx, .unknown, borrowed.getResult());
126     const released = try RcDialect.ReleaseOp.create(&ctx, .unknown, moved.getResult());
127     const operations = [_]*ir.Operation{ retained.op, borrowed.op, moved.op, released.op };
128     const kinds = [_]effects.EventKind{ .retain, .borrow, .move, .release };
129     for (operations, kinds) |op, kind| {
130         var declaration = try effects.inspect(std.testing.allocator, op);
131         defer declaration.deinit(std.testing.allocator);
132         try std.testing.expect(!effects.discard(declaration.facts));
133         try std.testing.expect(!effects.duplicate(declaration.facts, .{}));
134         try std.testing.expectEqual(kind, declaration.facts.records[1].event.kind);
135         if (op.getNumResults() != 0) {
136             const result = declaration.facts.records[2].result;
137             try std.testing.expectEqual(@as(usize, 0), result.alias.?.operand);
138             try std.testing.expect(result.ownership != .none);
139         }
140     }
141 }