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 }