lib/choir/src/core/context/lifecycle.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const std = @import("std");
   2 const alloc_phase = @import("alloc_phase");
   3 const core = @import("../root.zig");
   4 const diagnostics = @import("../../diagnostics/root.zig");
   5 const interfaces = @import("../root.zig").interfaces;
   6 const dialects = @import("../root.zig").dialects;
   7 const types = @import("../root.zig").types;
   8 const attrs = @import("../root.zig").attrs;
   9 const attr_ctx = @import("attrs.zig");
  10 const dialect_ctx = @import("dialects.zig");
  11 const freeze_ctx = @import("freeze.zig");
  12 const interface_ctx = @import("interface.zig");
  13 const memory_ctx = @import("memory.zig");
  14 const ops_ctx = @import("ops.zig");
  15 const policy_ctx = @import("policy.zig");
  16 const resources = @import("resources.zig");
  17 const threading = @import("../root.zig").threading;
  18 const transactions = @import("transactions.zig");
  19 const type_ctx = @import("types.zig");
  20 
  21 const PassRunAtomicInt = if (@bitSizeOf(usize) >= @bitSizeOf(u64)) u64 else u32;
  22 
  23 pub const Context = struct {
  24     phase: alloc_phase.capacity.Phase,
  25     capacity: Capacity,
  26     storage: [*]u8,
  27     memory: *memory_ctx.Memory,
  28     operation_storage_allocator: [@sizeOf(core.Operation.StorageAllocator)]u8 align(@alignOf(core.Operation.StorageAllocator)),
  29     dialect_registry: dialects.DialectRegistry,
  30     trait_registry: interfaces.TraitRegistry,
  31     type_interner: types.TypeInterner,
  32     attr_interner: attrs.AttributeInterner,
  33     resources: resources.Tracker,
  34     type_param_cache: types.TypeParamCache,
  35     dialect_load_transactions: std.ArrayListUnmanaged(transactions.DialectLoadTransaction),
  36     freeze_policy: freeze_ctx.Policy,
  37     threading_execution: threading.ExecutionState,
  38     diagnostic_engine: diagnostics.Engine,
  39     pass_runs: std.atomic.Value(PassRunAtomicInt),
  40     arithmetic_policy: interfaces.effects.ArithmeticPolicy = .{},
  41 
  42     pub const Limits = memory_ctx.Limits;
  43     pub const minimum_alignment = memory_ctx.minimum_alignment;
  44     pub const maximum_storage_bytes = memory_ctx.maximum_storage_bytes;
  45     pub const Refusal = memory_ctx.Refusal;
  46     pub const refusal = memory_ctx.refusal;
  47     pub const Capacity = memory_ctx.Capacity;
  48     pub const Segment = memory_ctx.Segment;
  49     pub const Usage = memory_ctx.Usage;
  50     pub const Exhausted = memory_ctx.Exhausted;
  51     pub const InFlightDiagnostic = diagnostics.BoundInFlightDiagnostic(
  52         Context,
  53         diagnosticPayloadAllocator,
  54     );
  55     pub const Exhaustion = error{OutOfMemory};
  56     pub const InitError = error{ CapacityOverflow, OutOfMemory };
  57 
  58     pub const claim: alloc_phase.capacity.Declaration = .{
  59         .source = .{
  60             .id = "choir.context",
  61             .kind = .phase_static,
  62             .limit_source = .caller,
  63             .storage = .{
  64                 .covered = &.{
  65                     .{
  66                         .id = "dialect_registry_trait_and_configuration_table_storage",
  67                         .lifetime = .steady,
  68                         .detail = "dialect, registry, trait, and configuration table storage",
  69                     },
  70                     .{
  71                         .id = "interned_dialect_names",
  72                         .lifetime = .steady,
  73                         .detail = "interned dialect names",
  74                     },
  75                     .{
  76                         .id = "operation_type_attribute_and_dialect_interface_definitions",
  77                         .lifetime = .steady,
  78                         .detail = "operation, type, attribute, and dialect interface definitions",
  79                     },
  80                     .{
  81                         .id = "dialect_registration_transaction_journals",
  82                         .lifetime = .initialization,
  83                         .detail = "dialect registration transaction journals",
  84                     },
  85                     .{
  86                         .id = "interned_type_tables",
  87                         .lifetime = .steady,
  88                         .detail = "interned type tables",
  89                     },
  90                     .{
  91                         .id = "type_names_and_parameter_keys",
  92                         .lifetime = .steady,
  93                         .detail = "type names and parameter keys",
  94                     },
  95                     .{
  96                         .id = "type_objects_and_parsed_parameter_payloads",
  97                         .lifetime = .steady,
  98                         .detail = "type objects and parsed parameter payloads",
  99                     },
 100                     .{
 101                         .id = "interned_attribute_tables",
 102                         .lifetime = .steady,
 103                         .detail = "interned attribute tables",
 104                     },
 105                     .{
 106                         .id = "attribute_objects_and_retained_payloads",
 107                         .lifetime = .steady,
 108                         .detail = "attribute objects and retained payloads",
 109                     },
 110                     .{
 111                         .id = "fixed_shape_operation_storage_pools",
 112                         .lifetime = .steady,
 113                         .detail = "fixed-shape operation storage pools",
 114                     },
 115                     .{
 116                         .id = "regions_blocks_properties_operands_successors_and_o_5b44c1ea68c9",
 117                         .lifetime = .steady,
 118                         .detail = "regions, blocks, properties, operands, successors, and operation spills",
 119                     },
 120                     .{
 121                         .id = "diagnostic_handler_registry",
 122                         .lifetime = .steady,
 123                         .detail = "diagnostic handler registry",
 124                     },
 125                     .{
 126                         .id = "in_flight_diagnostic_messages_notes_and_metadata",
 127                         .lifetime = .steady,
 128                         .detail = "in-flight diagnostic messages, notes, and metadata",
 129                     },
 130                     .{
 131                         .id = "bounded_transient_formatting_parsing_verification_a_52aa670b3999",
 132                         .lifetime = .initialization,
 133                         .detail = "bounded transient formatting, parsing, verification, and lowering work",
 134                     },
 135                 },
 136                 .excluded = &.{
 137                     "caller allocator implementation state and an optional stable Context handle",
 138                     "caller-owned diagnostic capture buffers and foreign interface vtable state",
 139                     "pass managers, backends, extension registries, and other owners constructed beside Context",
 140                 },
 141             },
 142             .capacity = .{
 143                 .inputs = &.{
 144                     alloc_phase.capacity.bindInput(Limits, "attributes_payload_bytes", "attributes.payload_bytes"),
 145                     alloc_phase.capacity.bindInput(Limits, "attributes_table_bytes", "attributes.table_bytes"),
 146                     alloc_phase.capacity.bindInput(Limits, "configuration_interface_bytes", "configuration.interface_bytes"),
 147                     alloc_phase.capacity.bindInput(Limits, "configuration_name_bytes", "configuration.name_bytes"),
 148                     alloc_phase.capacity.bindInput(Limits, "configuration_table_bytes", "configuration.table_bytes"),
 149                     alloc_phase.capacity.bindInput(Limits, "configuration_transaction_bytes", "configuration.transaction_bytes"),
 150                     alloc_phase.capacity.bindInput(Limits, "diagnostics_handler_bytes", "diagnostics.handler_bytes"),
 151                     alloc_phase.capacity.bindInput(Limits, "diagnostics_payload_bytes", "diagnostics.payload_bytes"),
 152                     alloc_phase.capacity.bindInput(Limits, "operations_nested_bytes", "operations.nested_bytes"),
 153                     alloc_phase.capacity.bindInput(Limits, "operations_storage_bytes", "operations.storage_bytes"),
 154                     alloc_phase.capacity.bindInput(Limits, "transient_bytes", "transient_bytes"),
 155                     alloc_phase.capacity.bindInput(Limits, "types_key_bytes", "types.key_bytes"),
 156                     alloc_phase.capacity.bindInput(Limits, "types_payload_bytes", "types.payload_bytes"),
 157                     alloc_phase.capacity.bindInput(Limits, "types_table_bytes", "types.table_bytes"),
 158                 },
 159                 .type_selectors = &.{},
 160                 .nodes = &.{
 161                     .{ .input = 0 },
 162                     .{ .input = 1 },
 163                     .{ .input = 2 },
 164                     .{ .input = 3 },
 165                     .{ .input = 4 },
 166                     .{ .input = 5 },
 167                     .{ .input = 6 },
 168                     .{ .input = 7 },
 169                     .{ .input = 8 },
 170                     .{ .input = 9 },
 171                     .{ .input = 10 },
 172                     .{ .input = 11 },
 173                     .{ .input = 12 },
 174                     .{ .input = 13 },
 175                     .{ .add = .{ .left = 0, .right = 1 } },
 176                     .{ .add = .{ .left = 14, .right = 2 } },
 177                     .{ .add = .{ .left = 15, .right = 3 } },
 178                     .{ .add = .{ .left = 16, .right = 4 } },
 179                     .{ .add = .{ .left = 17, .right = 5 } },
 180                     .{ .add = .{ .left = 18, .right = 6 } },
 181                     .{ .add = .{ .left = 19, .right = 7 } },
 182                     .{ .add = .{ .left = 20, .right = 8 } },
 183                     .{ .add = .{ .left = 21, .right = 9 } },
 184                     .{ .add = .{ .left = 22, .right = 10 } },
 185                     .{ .add = .{ .left = 23, .right = 11 } },
 186                     .{ .add = .{ .left = 24, .right = 12 } },
 187                     .{ .add = .{ .left = 25, .right = 13 } },
 188                 },
 189                 .assertions = &.{.{
 190                     .scope = .closure_total,
 191                     .measure = .retained,
 192                     .relation = .upper_bound,
 193                     .expression = 26,
 194                 }},
 195             },
 196             .overload = .{
 197                 .kind = .reject_before_mutation,
 198                 .detail = "steady OutOfMemory denotes exhaustion of one named fixed segment; capacity arithmetic and allocation admissions fail before publishing partial state",
 199             },
 200             .risks = .{
 201                 .transitive = .{
 202                     .status = .witnessed,
 203                     .detail = "all Context-retained nested owners draw from the named fixed segments acquired at construction",
 204                 },
 205                 .foreign = .{
 206                     .status = .excluded,
 207                     .detail = "opaque interface state and caller-owned diagnostic capture storage remain owned by their supplying APIs",
 208                 },
 209             },
 210             .dependencies = &.{"choir.operation_fixed_storage"},
 211             .obligations = &.{
 212                 .{ .key = "choir_context_capacity", .role = .capacity_model },
 213                 .{ .key = "choir_context_capacity_overflow", .role = .overload },
 214                 .{ .key = "choir_context_single_acquisition", .role = .transitive_risk },
 215                 .{ .key = "choir_context_initialization_retry", .role = .overload },
 216                 .{ .key = "choir_context_operation_exhaustion_overload", .role = .overload },
 217                 .{ .key = "choir_context_operation_exhaustion_transitive_risk", .role = .transitive_risk },
 218                 .{ .key = "choir_context_spill_exhaustion", .role = .overload },
 219                 .{ .key = "choir_context_foreign_ownership", .role = .foreign_risk },
 220                 .{ .key = "choir_context_diagnostic_exhaustion", .role = .overload },
 221                 .{ .key = "choir_context_lazy_param_exhaustion", .role = .overload },
 222                 .{ .key = "choir_context_attribute_exhaustion", .role = .overload },
 223             },
 224         },
 225         .bindings = .{
 226             .owner = @This(),
 227             .seal = .{
 228                 .family = alloc_phase.capacity.selector(@This().activate),
 229                 .premise = .{
 230                     .class = .checked_semantic_fact,
 231                     .authority = .checker,
 232                 },
 233             },
 234             .teardown = .{
 235                 .family = alloc_phase.capacity.selector(@This().deinit),
 236                 .premise = .{
 237                     .class = .checked_semantic_fact,
 238                     .authority = .checker,
 239                 },
 240             },
 241         },
 242     };
 243 
 244     pub const Error = error{
 245         ContextFrozen,
 246         ContextMutationDuringMultithreadedExecution,
 247         UnknownDialect,
 248         DialectLoadCycle,
 249         UnknownOperation,
 250         UnknownType,
 251         DuplicateDialectLoader,
 252         DuplicateInterface,
 253         BackendDialectRequired,
 254         InvalidTranslationDialect,
 255     } || std.mem.Allocator.Error;
 256 
 257     pub const RegisterTraitDefinitionError = Error || interfaces.TraitRegistry.RegisterError;
 258 
 259     pub const RegisterError = Error || interfaces.OperationRegistry.GetOrCreateError || interfaces.OperationRegistry.RegisterInterfaceError;
 260     pub const RegisterTraitError = Error || interfaces.OperationRegistry.RegisterTraitError;
 261     pub const RegisterInherentAttributeNameError = Error || interfaces.OperationRegistry.RegisterInherentAttributeNameError;
 262     pub const RegisterTypeConstraintError = Error || interfaces.OperationRegistry.RegisterTypeConstraintError;
 263     pub const RegisterSegmentSpecError = Error || interfaces.OperationRegistry.RegisterSegmentSpecError;
 264     pub const RegisterTypeError = Error || interfaces.TypeRegistry.GetOrCreateError || interfaces.TypeRegistry.RegisterInterfaceError;
 265     pub const RegisterAttributeError = Error || interfaces.AttributeRegistry.RegisterError;
 266 
 267     pub fn init(allocator: std.mem.Allocator, limits: Limits) InitError!Context {
 268         const capacity = try Capacity.derive(limits);
 269         const storage = allocator.rawAlloc(
 270             capacity.storage_bytes,
 271             capacity.storage_alignment,
 272             @returnAddress(),
 273         ) orelse return error.OutOfMemory;
 274         const memory = memory_ctx.Memory.init(storage, capacity);
 275         var context = Context{
 276             .phase = .initialization,
 277             .capacity = capacity,
 278             .storage = storage,
 279             .memory = memory,
 280             .operation_storage_allocator = undefined,
 281             .dialect_registry = dialects.DialectRegistry.init(
 282                 memory.configuration_tables.allocator(),
 283                 memory.configuration_interfaces.allocator(),
 284             ),
 285             .trait_registry = interfaces.TraitRegistry.init(),
 286             .type_interner = types.TypeInterner.init(
 287                 memory.type_tables.allocator(),
 288                 memory.type_keys.allocator(),
 289             ),
 290             .attr_interner = attrs.AttributeInterner.init(
 291                 memory.attribute_tables.allocator(),
 292                 memory.configuration_interfaces.allocator(),
 293             ),
 294             .resources = resources.Tracker.init(),
 295             .type_param_cache = .{},
 296             .dialect_load_transactions = .empty,
 297             .freeze_policy = freeze_ctx.Policy.init(),
 298             .threading_execution = threading.ExecutionState.init(),
 299             .diagnostic_engine = diagnostics.Engine.init(),
 300             .pass_runs = std.atomic.Value(PassRunAtomicInt).init(0),
 301         };
 302         operationStorageAllocator(&context).* = core.Operation.StorageAllocator.init(
 303             memory_ctx.operationStorageAllocator(context.memory),
 304         );
 305         return context;
 306     }
 307 
 308     pub fn create(allocator: std.mem.Allocator, limits: Limits) InitError!*Context {
 309         const self = try allocator.create(Context);
 310         errdefer allocator.destroy(self);
 311         self.* = try init(allocator, limits);
 312         return self;
 313     }
 314 
 315     pub fn activate(self: *Context) void {
 316         std.debug.assert(self.phase == .initialization);
 317         self.freeze();
 318         self.phase = .steady;
 319     }
 320 
 321     pub fn capacityUsage(self: *const Context) Usage {
 322         return memory_ctx.cast(self.memory).usage();
 323     }
 324 
 325     pub fn exhaustedSegment(self: *const Context) ?Segment {
 326         return memory_ctx.cast(self.memory).exhausted_segment;
 327     }
 328 
 329     /// The segment a request found full and the size of that request, or
 330     /// null when no request has failed since the last `clearExhaustion`.
 331     pub fn exhaustion(self: *const Context) ?Exhausted {
 332         return memory_ctx.cast(self.memory).exhaustion();
 333     }
 334 
 335     pub fn clearExhaustion(self: *Context) void {
 336         memory_ctx.cast(self.memory).exhausted_segment = null;
 337         memory_ctx.cast(self.memory).exhausted_request = 0;
 338     }
 339 
 340     pub fn requireCapacity(
 341         self: *Context,
 342         segment: Segment,
 343         bytes: usize,
 344         alignment: std.mem.Alignment,
 345     ) Exhaustion!void {
 346         const memory = memory_ctx.cast(self.memory);
 347         if (!memory.hasCapacity(segment, bytes, alignment)) {
 348             memory.exhausted_segment = segment;
 349             memory.exhausted_request = bytes;
 350             return error.OutOfMemory;
 351         }
 352     }
 353 
 354     pub fn deinit(self: *Context, allocator: std.mem.Allocator) void {
 355         std.debug.assert(self.phase != .teardown);
 356         self.resources.deinit();
 357         operationStorageAllocator(self).deinit();
 358         self.deinitTypeParamCache();
 359         transactions.deinitAll(self);
 360         self.trait_registry.deinit(memory_ctx.configurationTableAllocator(self.memory));
 361         self.dialect_registry.deinit(
 362             memory_ctx.configurationTableAllocator(self.memory),
 363             memory_ctx.configurationNameAllocator(self.memory),
 364             memory_ctx.configurationInterfaceAllocator(self.memory),
 365         );
 366         self.type_interner.deinit(
 367             memory_ctx.typeTableAllocator(self.memory),
 368             memory_ctx.typeKeyAllocator(self.memory),
 369             memory_ctx.typePayloadAllocator(self.memory),
 370         );
 371         self.attr_interner.deinit(memory_ctx.attributePayloadAllocator(self.memory));
 372         self.diagnostic_engine.deinit(memory_ctx.diagnosticHandlerAllocator(self.memory));
 373         const storage = self.storage;
 374         const capacity = self.capacity;
 375         self.phase = .teardown;
 376         self.* = undefined;
 377         allocator.rawFree(
 378             storage[0..capacity.storage_bytes],
 379             capacity.storage_alignment,
 380             @returnAddress(),
 381         );
 382     }
 383 
 384     pub fn registerTraitDefinition(self: *Context, entry: interfaces.TraitEntry) RegisterTraitDefinitionError!void {
 385         try freeze_ctx.requireMutable(self);
 386         try self.trait_registry.register(
 387             memory_ctx.configurationTableAllocator(self.memory),
 388             entry,
 389         );
 390     }
 391 
 392     pub fn lookupTrait(self: *const Context, trait_id: interfaces.TraitId) ?*const interfaces.TraitVTable {
 393         return self.trait_registry.lookup(trait_id);
 394     }
 395 
 396     pub fn getDiagnosticEngine(self: *Context) *diagnostics.Engine {
 397         return &self.diagnostic_engine;
 398     }
 399 
 400     pub fn registerDiagnosticHandler(
 401         self: *Context,
 402         handler: diagnostics.Handler,
 403     ) !diagnostics.Engine.HandlerId {
 404         return self.diagnostic_engine.registerHandler(
 405             memory_ctx.diagnosticHandlerAllocator(self.memory),
 406             handler,
 407         );
 408     }
 409 
 410     pub fn eraseDiagnosticHandler(self: *Context, id: diagnostics.Engine.HandlerId) void {
 411         self.diagnostic_engine.eraseHandler(id);
 412     }
 413 
 414     pub fn emitDiagnostic(
 415         self: *Context,
 416         diagnostic: diagnostics.Diagnostic,
 417     ) InFlightDiagnostic {
 418         return .{
 419             .context = self,
 420             .diagnostic = self.diagnostic_engine.emit(diagnostic),
 421         };
 422     }
 423 
 424     pub fn captureDiagnostics(
 425         self: *Context,
 426         buffer: *diagnostics.CaptureBuffer,
 427     ) diagnostics.CaptureScope {
 428         return self.diagnostic_engine.capture(buffer);
 429     }
 430 
 431     pub fn replayDiagnostics(
 432         self: *Context,
 433         buffer: *const diagnostics.CaptureBuffer,
 434     ) !diagnostics.HandlerResult {
 435         return self.diagnostic_engine.replay(buffer);
 436     }
 437 
 438     pub fn enterMultithreadedExecution(self: *Context) threading.ExecutionGuard {
 439         return self.threading_execution.enter();
 440     }
 441 
 442     pub fn isMultithreadedExecution(self: *const Context) bool {
 443         return self.threading_execution.isActive();
 444     }
 445 
 446     pub fn recordPassRuns(self: *Context, additional: u64) void {
 447         var current = self.pass_runs.load(.monotonic);
 448         while (true) {
 449             const next = saturatingPassRunAdd(PassRunAtomicInt, current, additional);
 450             if (self.pass_runs.cmpxchgWeak(
 451                 current,
 452                 next,
 453                 .monotonic,
 454                 .monotonic,
 455             )) |observed| {
 456                 current = observed;
 457             } else {
 458                 return;
 459             }
 460         }
 461     }
 462 
 463     pub fn passRunCount(self: *const Context) u64 {
 464         return @intCast(self.pass_runs.load(.monotonic));
 465     }
 466 
 467     fn deinitTypeParamCache(self: *Context) void {
 468         var iter = self.type_param_cache.valueIterator();
 469         while (iter.next()) |payload| {
 470             payload.deinit(memory_ctx.typePayloadAllocator(self.memory), payload.ptr);
 471         }
 472         self.type_param_cache.deinit(memory_ctx.typeTableAllocator(self.memory));
 473     }
 474 
 475     pub const appendDialectRegistry = dialect_ctx.appendDialectRegistry;
 476     pub const registerDialectLoader = dialect_ctx.registerDialectLoader;
 477     pub const registerBackendDialect = dialect_ctx.registerBackendDialect;
 478     pub const isBackendDialect = dialect_ctx.isBackendDialect;
 479     pub const addDialectExtension = dialect_ctx.addDialectExtension;
 480     pub const getOrLoadDialect = dialect_ctx.getOrLoadDialect;
 481     pub const isDialectLoaded = dialect_ctx.isDialectLoaded;
 482     pub const getDialect = dialect_ctx.getDialect;
 483     pub const ensureDialectForOperation = dialect_ctx.ensureDialectForOperation;
 484     pub const prepareDialectLoadRecordStorage = transactions.prepareRecordStorage;
 485     pub const registerDialectInterface = interface_ctx.registerDialectInterface;
 486     pub const getDialectInterface = interface_ctx.getDialectInterface;
 487     pub const registerDialectOpInterfaceFallback = interface_ctx.registerDialectOpInterfaceFallback;
 488     pub const getDialectOpInterfaceFallback = interface_ctx.getDialectOpInterfaceFallback;
 489     pub const registerDialectTypeInterfaceFallback = interface_ctx.registerDialectTypeInterfaceFallback;
 490     pub const getDialectTypeInterfaceFallback = interface_ctx.getDialectTypeInterfaceFallback;
 491     pub const requireRegistered = policy_ctx.requireRegistered;
 492     pub const allowUnregistered = policy_ctx.allowUnregistered;
 493     pub const getOperationRegistry = ops_ctx.getOperationRegistry;
 494     pub const lookupOperation = ops_ctx.lookupOperation;
 495     pub const registerOperation = ops_ctx.registerOperation;
 496     pub const registerOperationWithAttributeNameCapacity = ops_ctx.registerOperationWithAttributeNameCapacity;
 497     pub const registerOperationBatch = ops_ctx.registerOperationBatch;
 498     pub const registerOperationInterface = ops_ctx.registerOperationInterface;
 499     pub const registerOperationInterfaceExternal = ops_ctx.registerOperationInterfaceExternal;
 500     pub const registerOperationInherentAttributeName = ops_ctx.registerOperationInherentAttributeName;
 501     pub const registerOperationInherentAttributeNames = ops_ctx.registerOperationInherentAttributeNames;
 502     pub const registerOperationRequiredAttributeName = ops_ctx.registerOperationRequiredAttributeName;
 503     pub const registerOperationRequiredAttributeNames = ops_ctx.registerOperationRequiredAttributeNames;
 504     pub const registerOperationPropertiesModel = ops_ctx.registerOperationPropertiesModel;
 505     pub const registerOperationShape = ops_ctx.registerOperationShape;
 506     pub const registerOperationOperandSegments = ops_ctx.registerOperationOperandSegments;
 507     pub const registerOperationResultSegments = ops_ctx.registerOperationResultSegments;
 508     pub const registerOperationOperandTypeConstraint = ops_ctx.registerOperationOperandTypeConstraint;
 509     pub const registerOperationResultTypeConstraint = ops_ctx.registerOperationResultTypeConstraint;
 510     pub const registerOperationTraitId = ops_ctx.registerOperationTraitId;
 511     pub const registerOperationTrait = ops_ctx.registerOperationTrait;
 512     pub const allocateValueId = resources.allocateValueId;
 513     pub const operationCount = resources.operationCount;
 514     pub const operationCreationBoundary = resources.operationCreationBoundary;
 515     pub const eraseOperationsCreatedSince = resources.eraseOperationsCreatedSince;
 516     pub const containsOperation = resources.containsOperation;
 517     pub const createOperation = ops_ctx.createOperation;
 518     pub const untrackOperation = resources.untrackOperation;
 519     pub const getTypeRegistry = type_ctx.getTypeRegistry;
 520     pub const lookupType = type_ctx.lookupType;
 521     pub const registerType = type_ctx.registerType;
 522     pub const registerTypeBatch = type_ctx.registerTypeBatch;
 523     pub const registerTypeInterface = type_ctx.registerTypeInterface;
 524     pub const registerTypeInterfaceExternal = type_ctx.registerTypeInterfaceExternal;
 525     pub fn TypeInterfaceHandle(comptime IFace: type) type {
 526         return type_ctx.TypeInterfaceHandle(Context, IFace);
 527     }
 528     pub const typeInterface = type_ctx.typeInterface;
 529     pub const getTypeInterface = type_ctx.getTypeInterface;
 530     pub const getTypeParamPayloadRaw = type_ctx.getTypeParamPayloadRaw;
 531     pub const getTypeParamPayload = type_ctx.getTypeParamPayload;
 532     pub const getDialectType = type_ctx.getDialectType;
 533     pub const getDialectTypeWithKey = type_ctx.getDialectTypeWithKey;
 534     pub const getDialectTypeFromName = type_ctx.getDialectTypeFromName;
 535     pub const getDialectTypeFromNameWithKey = type_ctx.getDialectTypeFromNameWithKey;
 536     pub const lookupAttributeType = attr_ctx.lookupAttributeType;
 537     pub const registerAttributeType = attr_ctx.registerAttributeType;
 538     pub const registerAttributeInterfaceExternal = attr_ctx.registerAttributeInterfaceExternal;
 539     pub const getDialectAttr = attr_ctx.getDialectAttr;
 540     pub const getIntegerAttr = attr_ctx.getIntegerAttr;
 541     pub const getI64Attr = attr_ctx.getI64Attr;
 542     pub const getI32Attr = attr_ctx.getI32Attr;
 543     pub const getFloatAttr = attr_ctx.getFloatAttr;
 544     pub const getF64Attr = attr_ctx.getF64Attr;
 545     pub const getF32Attr = attr_ctx.getF32Attr;
 546     pub const getBoolAttr = attr_ctx.getBoolAttr;
 547     pub const getStringAttr = attr_ctx.getStringAttr;
 548     pub const getSymbolRefAttr = attr_ctx.getSymbolRefAttr;
 549     pub const getFlatSymbolRefAttr = attr_ctx.getFlatSymbolRefAttr;
 550     pub const getStringListAttr = attr_ctx.getStringListAttr;
 551     pub const getTypeListAttr = attr_ctx.getTypeListAttr;
 552     pub const getArrayAttr = attr_ctx.getArrayAttr;
 553     pub const freeze = freeze_ctx.freeze;
 554     pub const isFrozen = freeze_ctx.isFrozen;
 555 };
 556 
 557 pub fn operationStorageAllocator(context: *Context) *core.Operation.StorageAllocator {
 558     return @ptrCast(@alignCast(&context.operation_storage_allocator));
 559 }
 560 
 561 fn diagnosticPayloadAllocator(context: *Context) std.mem.Allocator {
 562     return memory_ctx.diagnosticPayloadAllocator(context.memory);
 563 }
 564 
 565 comptime {
 566     alloc_phase.capacity.requireAllocatorRejectingOwnerShape(Context);
 567 }
 568 
 569 fn saturatingPassRunAdd(comptime Int: type, current: Int, additional: u64) Int {
 570     const maximum: u64 = std.math.maxInt(Int);
 571     const bounded: Int = @intCast(@min(additional, maximum));
 572     return current +| bounded;
 573 }
 574 
 575 test "Context acquires one exact outer region before activation" {
 576     comptime {
 577         @stardustClaim(
 578             @import("alloc_phase").capacity.witness(Context, "choir_context_single_acquisition"),
 579             null,
 580             null,
 581             null,
 582             null,
 583             null,
 584             null,
 585         );
 586     }
 587 
 588     const limits = Context.Limits.testing;
 589     const capacity = try Context.Capacity.derive(limits);
 590     var failing = std.testing.FailingAllocator.init(std.testing.allocator, .{});
 591     const allocated_before = failing.allocated_bytes;
 592     const freed_before = failing.freed_bytes;
 593 
 594     var context = try Context.init(failing.allocator(), limits);
 595     try std.testing.expectEqual(@as(usize, 1), failing.alloc_index);
 596     try std.testing.expectEqual(allocated_before + capacity.storage_bytes, failing.allocated_bytes);
 597     try std.testing.expectEqual(alloc_phase.capacity.Phase.initialization, context.phase);
 598     const storage = context.storage;
 599     const storage_bytes = context.capacity.storage_bytes;
 600     context.activate();
 601     try std.testing.expectEqual(alloc_phase.capacity.Phase.steady, context.phase);
 602     try std.testing.expect(context.isFrozen());
 603     const cold_allocator = memory_ctx.transientAllocator(context.memory);
 604     const cold = try cold_allocator.alloc(u8, 1);
 605     try std.testing.expectEqual(@as(usize, 1), failing.alloc_index);
 606     try std.testing.expectEqual(storage, context.storage);
 607     try std.testing.expectEqual(storage_bytes, context.capacity.storage_bytes);
 608     cold_allocator.free(cold);
 609     context.deinit(failing.allocator());
 610 
 611     try std.testing.expectEqual(freed_before + capacity.storage_bytes, failing.freed_bytes);
 612 }
 613 
 614 test "Context initialization rejects outer allocation failure and retries" {
 615     comptime {
 616         @stardustClaim(
 617             @import("alloc_phase").capacity.witness(Context, "choir_context_initialization_retry"),
 618             null,
 619             null,
 620             null,
 621             null,
 622             null,
 623             null,
 624         );
 625     }
 626 
 627     var failing = std.testing.FailingAllocator.init(
 628         std.testing.allocator,
 629         .{ .fail_index = 0 },
 630     );
 631 
 632     try std.testing.expectError(
 633         error.OutOfMemory,
 634         Context.init(failing.allocator(), Context.Limits.standard),
 635     );
 636     try std.testing.expectEqual(@as(usize, 0), failing.allocated_bytes);
 637     try std.testing.expectEqual(@as(usize, 0), failing.freed_bytes);
 638 
 639     failing.fail_index = std.math.maxInt(usize);
 640     var context = try Context.init(failing.allocator(), Context.Limits.standard);
 641     context.deinit(failing.allocator());
 642     try std.testing.expectEqual(failing.allocated_bytes, failing.freed_bytes);
 643 }
 644 
 645 test "Context reports and checks named segment capacity" {
 646     var limits = Context.Limits.testing;
 647     limits.transient_bytes = 19;
 648     var context = try Context.init(std.testing.allocator, limits);
 649     defer context.deinit(std.testing.allocator);
 650 
 651     const initial = context.capacityUsage().transient;
 652     try std.testing.expectEqual(@as(usize, 0), initial.frontier_bytes);
 653     try std.testing.expectEqual(@as(usize, 19), initial.reserved_bytes);
 654     try context.requireCapacity(.transient, 19, .@"1");
 655     try std.testing.expectError(
 656         error.OutOfMemory,
 657         context.requireCapacity(.transient, 20, .@"1"),
 658     );
 659     try std.testing.expectEqual(Context.Segment.transient, context.exhaustedSegment().?);
 660     try std.testing.expectEqual(@as(usize, 20), context.exhaustion().?.requested_bytes);
 661     context.clearExhaustion();
 662     try std.testing.expectEqual(@as(?Context.Segment, null), context.exhaustedSegment());
 663     try std.testing.expect(context.exhaustion() == null);
 664 
 665     const allocator = memory_ctx.transientAllocator(context.memory);
 666     const initial_bytes = try allocator.alloc(u8, 8);
 667     try std.testing.expect(!allocator.resize(initial_bytes, 20));
 668     try std.testing.expectEqual(@as(?Context.Segment, null), context.exhaustedSegment());
 669     allocator.free(initial_bytes);
 670     context.clearExhaustion();
 671 
 672     try std.testing.expectError(error.OutOfMemory, allocator.alloc(u8, 20));
 673     try std.testing.expectEqual(Context.Segment.transient, context.exhaustedSegment().?);
 674     const exhausted = context.exhaustion().?;
 675     try std.testing.expectEqual(Context.Segment.transient, exhausted.segment);
 676     try std.testing.expectEqual(@as(usize, 20), exhausted.requested_bytes);
 677 }
 678 
 679 test "context pass run census saturates at atomic storage boundary" {
 680     var context = try Context.init(std.testing.allocator, Context.Limits.testing);
 681     defer context.deinit(std.testing.allocator);
 682 
 683     context.recordPassRuns(std.math.maxInt(u64));
 684     context.recordPassRuns(1);
 685     const expected: u64 = if (@bitSizeOf(usize) >= @bitSizeOf(u64))
 686         std.math.maxInt(u64)
 687     else
 688         std.math.maxInt(u32);
 689     try std.testing.expectEqual(expected, context.passRunCount());
 690 }
 691 
 692 test "pass run census saturates at u32 boundary" {
 693     const maximum = std.math.maxInt(u32);
 694 
 695     try std.testing.expectEqual(maximum, saturatingPassRunAdd(u32, maximum - 1, 1));
 696     try std.testing.expectEqual(maximum, saturatingPassRunAdd(u32, maximum - 1, 2));
 697     try std.testing.expectEqual(
 698         maximum,
 699         saturatingPassRunAdd(u32, 0, @as(u64, maximum) + 1),
 700     );
 701 }
 702 
 703 const AfterLoadTestState = struct {
 704     var extension_called: bool = false;
 705     var vtable: u8 = 99;
 706 };
 707 
 708 const ImmediateExtensionTestState = struct {
 709     var extension_called: bool = false;
 710     var vtable: u8 = 77;
 711 };
 712 
 713 const MultipleExtensionsTestState = struct {
 714     var call_count: u32 = 0;
 715 };
 716 
 717 const DuplicateInterfaceTestState = struct {
 718     var original_vtable: u8 = 1;
 719     var replacement_vtable: u8 = 2;
 720 };
 721 
 722 fn test_type_verify_noop(_: *const anyopaque) anyerror!void {}
 723 
 724 fn test_extension_noop(_: *Context) anyerror!void {}
 725 
 726 fn test_loader_noop(_: *Context) anyerror!void {}
 727 
 728 fn after_load_test_loader(c: *Context) anyerror!void {
 729     _ = try c.registerOperation("ext_test.op", .{ .is_idempotent = true });
 730 }
 731 
 732 fn after_load_test_extension(c: *Context) anyerror!void {
 733     AfterLoadTestState.extension_called = true;
 734     const effect_id = interfaces.interfaceId("ext_test.interface");
 735     try c.registerOperationInterface("ext_test.op", .{
 736         .id = effect_id,
 737         .vtable = &AfterLoadTestState.vtable,
 738     });
 739 }
 740 
 741 fn immediate_extension_test_loader(c: *Context) anyerror!void {
 742     _ = try c.registerOperation("imm_test.op", .{ .is_idempotent = true });
 743 }
 744 
 745 fn immediate_extension_test_extension(c: *Context) anyerror!void {
 746     ImmediateExtensionTestState.extension_called = true;
 747     const effect_id = interfaces.interfaceId("imm_test.interface");
 748     try c.registerOperationInterface("imm_test.op", .{
 749         .id = effect_id,
 750         .vtable = &ImmediateExtensionTestState.vtable,
 751     });
 752 }
 753 
 754 fn multiple_extensions_test_loader(c: *Context) anyerror!void {
 755     _ = try c.registerOperation("multi_ext.op", .{});
 756 }
 757 
 758 fn multiple_extensions_test_add_one(_: *Context) anyerror!void {
 759     MultipleExtensionsTestState.call_count += 1;
 760 }
 761 
 762 fn multiple_extensions_test_add_ten(_: *Context) anyerror!void {
 763     MultipleExtensionsTestState.call_count += 10;
 764 }
 765 
 766 fn duplicate_interface_test_loader(c: *Context) anyerror!void {
 767     _ = try c.registerOperation("dup_iface.op", .{ .is_idempotent = true });
 768     const effect_id = interfaces.interfaceId("dup_iface.interface1");
 769     try c.registerOperationInterface("dup_iface.op", .{
 770         .id = effect_id,
 771         .vtable = &DuplicateInterfaceTestState.original_vtable,
 772     });
 773 }
 774 
 775 fn duplicate_interface_test_extension(c: *Context) anyerror!void {
 776     const effect_id = interfaces.interfaceId("dup_iface.interface1");
 777     const result = c.registerOperationInterface("dup_iface.op", .{
 778         .id = effect_id,
 779         .vtable = &DuplicateInterfaceTestState.replacement_vtable,
 780     });
 781     try std.testing.expectError(error.DuplicateInterface, result);
 782 }
 783 
 784 test "Context freeze blocks registerOperation" {
 785     const testing = @import("std").testing;
 786     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 787     defer ctx.deinit(testing.allocator);
 788 
 789     try testing.expect(!ctx.isFrozen());
 790     _ = try ctx.registerOperation("test.before", .{ .is_idempotent = true });
 791 
 792     ctx.freeze();
 793     try testing.expect(ctx.isFrozen());
 794 
 795     const result = ctx.registerOperation("test.after", .{ .is_idempotent = true });
 796     try testing.expectError(error.ContextFrozen, result);
 797 }
 798 
 799 test "Context freeze blocks registerOperationInterface" {
 800     const testing = @import("std").testing;
 801     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 802     defer ctx.deinit(testing.allocator);
 803 
 804     _ = try ctx.registerOperation("test.op", .{ .is_idempotent = true });
 805 
 806     const effect_id = interfaces.interfaceId("test.interface1");
 807     const vtable1: u8 = 1;
 808     try ctx.registerOperationInterface("test.op", .{ .id = effect_id, .vtable = &vtable1 });
 809 
 810     ctx.freeze();
 811 
 812     const effect_id2 = interfaces.interfaceId("test.interface2");
 813     const vtable2: u8 = 2;
 814     const result = ctx.registerOperationInterface("test.op", .{ .id = effect_id2, .vtable = &vtable2 });
 815     try testing.expectError(error.ContextFrozen, result);
 816 }
 817 
 818 test "Context freeze blocks registerOperationShape" {
 819     const testing = @import("std").testing;
 820     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 821     defer ctx.deinit(testing.allocator);
 822 
 823     _ = try ctx.registerOperation("test.op", .{ .is_idempotent = true });
 824     try ctx.registerOperationShape("test.op", .{ .operands = interfaces.CountRange.exactly(0) });
 825 
 826     ctx.freeze();
 827 
 828     const result = ctx.registerOperationShape("test.other", .{ .operands = interfaces.CountRange.exactly(1) });
 829     try testing.expectError(error.ContextFrozen, result);
 830 }
 831 
 832 test "Context freeze blocks registerOperationTypeConstraints" {
 833     const testing = @import("std").testing;
 834     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 835     defer ctx.deinit(testing.allocator);
 836 
 837     _ = try ctx.registerOperation("test.op", .{ .is_idempotent = true });
 838     try ctx.registerOperationOperandTypeConstraint("test.op", .{
 839         .index = 0,
 840         .type_name = "test.i32",
 841     });
 842 
 843     ctx.freeze();
 844 
 845     const operand_result = ctx.registerOperationOperandTypeConstraint("test.other", .{
 846         .index = 0,
 847         .type_name = "test.i32",
 848     });
 849     try testing.expectError(error.ContextFrozen, operand_result);
 850 
 851     const result_result = ctx.registerOperationResultTypeConstraint("test.other", .{
 852         .index = 0,
 853         .type_name = "test.i32",
 854     });
 855     try testing.expectError(error.ContextFrozen, result_result);
 856 }
 857 
 858 test "Context freeze blocks registerOperationSegments" {
 859     const testing = @import("std").testing;
 860     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 861     defer ctx.deinit(testing.allocator);
 862 
 863     _ = try ctx.registerOperation("test.op", .{ .is_idempotent = true });
 864     try ctx.registerOperationOperandSegments("test.op", .{
 865         .attribute_name = "operand_segment_sizes",
 866         .segments = &.{interfaces.CountRange.exactly(1)},
 867     });
 868 
 869     ctx.freeze();
 870 
 871     const operand_result = ctx.registerOperationOperandSegments("test.other", .{
 872         .attribute_name = "operand_segment_sizes",
 873         .segments = &.{interfaces.CountRange.exactly(1)},
 874     });
 875     try testing.expectError(error.ContextFrozen, operand_result);
 876 
 877     const result_result = ctx.registerOperationResultSegments("test.other", .{
 878         .attribute_name = "result_segment_sizes",
 879         .segments = &.{interfaces.CountRange.exactly(1)},
 880     });
 881     try testing.expectError(error.ContextFrozen, result_result);
 882 }
 883 
 884 test "Context freeze blocks registerType" {
 885     const testing = @import("std").testing;
 886     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 887     defer ctx.deinit(testing.allocator);
 888 
 889     _ = try ctx.registerType("test.ty");
 890     ctx.freeze();
 891 
 892     const result = ctx.registerType("test.after");
 893     try testing.expectError(error.ContextFrozen, result);
 894 }
 895 
 896 test "Context freeze blocks registerTypeInterface" {
 897     const testing = @import("std").testing;
 898     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 899     defer ctx.deinit(testing.allocator);
 900 
 901     _ = try ctx.registerType("test.ty");
 902     const vtable = interfaces.TypeVerifyInterface.VTable{ .verify = test_type_verify_noop };
 903     try ctx.registerTypeInterface("test.ty", interfaces.TypeVerifyInterface.entry(&vtable));
 904 
 905     ctx.freeze();
 906 
 907     const vtable2 = interfaces.TypeVerifyInterface.VTable{ .verify = test_type_verify_noop };
 908     const result = ctx.registerTypeInterface("test.ty", interfaces.TypeVerifyInterface.entry(&vtable2));
 909     try testing.expectError(error.ContextFrozen, result);
 910 }
 911 
 912 test "Context freeze blocks addDialectExtension" {
 913     const testing = @import("std").testing;
 914     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 915     defer ctx.deinit(testing.allocator);
 916 
 917     try ctx.addDialectExtension("test_dialect", test_extension_noop);
 918 
 919     ctx.freeze();
 920 
 921     const result = ctx.addDialectExtension("test_dialect2", test_extension_noop);
 922     try testing.expectError(error.ContextFrozen, result);
 923 }
 924 
 925 test "Context freeze blocks registerDialectLoader" {
 926     const testing = @import("std").testing;
 927     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 928     defer ctx.deinit(testing.allocator);
 929 
 930     try ctx.registerDialectLoader("test_dialect", test_loader_noop);
 931 
 932     ctx.freeze();
 933 
 934     const result = ctx.registerDialectLoader("test_dialect2", test_loader_noop);
 935     try testing.expectError(error.ContextFrozen, result);
 936 }
 937 
 938 test "Context freeze blocks getOrLoadDialect for unloaded dialects" {
 939     const testing = @import("std").testing;
 940     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 941     defer ctx.deinit(testing.allocator);
 942 
 943     try ctx.registerDialectLoader("test_dialect", test_loader_noop);
 944 
 945     _ = try ctx.getOrLoadDialect("test_dialect");
 946 
 947     ctx.freeze();
 948 
 949     const dialect = try ctx.getOrLoadDialect("test_dialect");
 950     try testing.expectEqualStrings("test_dialect", dialect.name);
 951 
 952     const result = ctx.getOrLoadDialect("unregistered_after_freeze");
 953     try testing.expectError(error.ContextFrozen, result);
 954 }
 955 
 956 test "Context dialects clean up loaded dialects" {
 957     const testing = @import("std").testing;
 958     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 959     defer ctx.deinit(testing.allocator);
 960     try ctx.allowUnregistered();
 961 
 962     _ = try ctx.getOrLoadDialect("arith");
 963     _ = try ctx.getOrLoadDialect("func");
 964     _ = try ctx.getOrLoadDialect("scf");
 965 }
 966 
 967 test "Context dialect extension applied after load" {
 968     const testing = @import("std").testing;
 969     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 970     defer ctx.deinit(testing.allocator);
 971 
 972     AfterLoadTestState.extension_called = false;
 973 
 974     try ctx.registerDialectLoader("ext_test", after_load_test_loader);
 975     try ctx.addDialectExtension("ext_test", after_load_test_extension);
 976 
 977     _ = try ctx.getOrLoadDialect("ext_test");
 978 
 979     try testing.expect(AfterLoadTestState.extension_called);
 980 
 981     const info = ctx.lookupOperation("ext_test.op");
 982     try testing.expect(info != null);
 983     try testing.expect(info.?.traits.is_idempotent);
 984 
 985     const effect_id = interfaces.interfaceId("ext_test.interface");
 986     try testing.expect(info.?.hasInterface(effect_id));
 987 }
 988 
 989 test "Context dialect extension applied immediately if already loaded" {
 990     const testing = @import("std").testing;
 991     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
 992     defer ctx.deinit(testing.allocator);
 993 
 994     ImmediateExtensionTestState.extension_called = false;
 995 
 996     try ctx.registerDialectLoader("imm_test", immediate_extension_test_loader);
 997     _ = try ctx.getOrLoadDialect("imm_test");
 998 
 999     try testing.expect(!ImmediateExtensionTestState.extension_called);
1000 
1001     try ctx.addDialectExtension("imm_test", immediate_extension_test_extension);
1002 
1003     try testing.expect(ImmediateExtensionTestState.extension_called);
1004 
1005     const info = ctx.lookupOperation("imm_test.op");
1006     const effect_id = interfaces.interfaceId("imm_test.interface");
1007     try testing.expect(info.?.hasInterface(effect_id));
1008 }
1009 
1010 test "Context duplicate dialect loader rejected" {
1011     const testing = @import("std").testing;
1012     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1013     defer ctx.deinit(testing.allocator);
1014 
1015     try ctx.registerDialectLoader("dup_test", test_loader_noop);
1016 
1017     const result = ctx.registerDialectLoader("dup_test", test_loader_noop);
1018     try testing.expectError(error.DuplicateDialectLoader, result);
1019 }
1020 
1021 test "Context multiple extensions for same dialect" {
1022     const testing = @import("std").testing;
1023     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1024     defer ctx.deinit(testing.allocator);
1025 
1026     MultipleExtensionsTestState.call_count = 0;
1027 
1028     try ctx.registerDialectLoader("multi_ext", multiple_extensions_test_loader);
1029     try ctx.addDialectExtension("multi_ext", multiple_extensions_test_add_one);
1030     try ctx.addDialectExtension("multi_ext", multiple_extensions_test_add_ten);
1031 
1032     _ = try ctx.getOrLoadDialect("multi_ext");
1033 
1034     try testing.expectEqual(@as(u32, 11), MultipleExtensionsTestState.call_count);
1035 }
1036 
1037 test "Context dialect extension duplicate interface handling" {
1038     const testing = @import("std").testing;
1039     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1040     defer ctx.deinit(testing.allocator);
1041 
1042     try ctx.registerDialectLoader("dup_iface", duplicate_interface_test_loader);
1043     try ctx.addDialectExtension("dup_iface", duplicate_interface_test_extension);
1044 
1045     _ = try ctx.getOrLoadDialect("dup_iface");
1046 
1047     const info = ctx.lookupOperation("dup_iface.op");
1048     try testing.expect(info != null);
1049     const effect_id = interfaces.interfaceId("dup_iface.interface1");
1050     try testing.expect(info.?.hasInterface(effect_id));
1051 }
1052 
1053 test "Context freeze allows read operations" {
1054     const testing = @import("std").testing;
1055     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1056     defer ctx.deinit(testing.allocator);
1057 
1058     _ = try ctx.registerOperation("test.op", .{ .is_idempotent = true, .is_commutative = true });
1059     const effect_id = interfaces.interfaceId("test.interface");
1060     const vtable: u8 = 42;
1061     try ctx.registerOperationInterface("test.op", .{ .id = effect_id, .vtable = &vtable });
1062 
1063     ctx.freeze();
1064 
1065     const info = ctx.lookupOperation("test.op");
1066     try testing.expect(info != null);
1067     try testing.expect(info.?.traits.is_idempotent);
1068     try testing.expect(info.?.traits.is_commutative);
1069     try testing.expect(info.?.hasInterface(effect_id));
1070 }
1071 
1072 test "Context freeze allows Operation.getInterface lookups" {
1073     const testing = @import("std").testing;
1074     const Operation = @import("../root.zig").Operation;
1075     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1076     defer ctx.deinit(testing.allocator);
1077     try ctx.allowUnregistered();
1078 
1079     _ = try ctx.registerOperation("freeze_iface.op", .{ .is_idempotent = true });
1080     const effect_id = interfaces.interfaceId("freeze_iface.test_interface");
1081     const vtable: u8 = 42;
1082     try ctx.registerOperationInterface("freeze_iface.op", .{ .id = effect_id, .vtable = &vtable });
1083 
1084     const state = Operation.State.init("freeze_iface.op", .unknown);
1085     const op = try ctx.createOperation(state);
1086 
1087     const info_before = op.name.getRegisteredInfo();
1088     try testing.expect(info_before != null);
1089     try testing.expect(info_before.?.hasInterface(effect_id));
1090 
1091     ctx.freeze();
1092 
1093     for (0..5) |_| {
1094         const info_after = ctx.lookupOperation("freeze_iface.op");
1095         try testing.expect(info_after != null);
1096         try testing.expect(info_after.?.hasInterface(effect_id));
1097 
1098         try testing.expect(op.name.getRegisteredInfo() != null);
1099         try testing.expect(op.name.getRegisteredInfo().?.hasInterface(effect_id));
1100     }
1101 }
1102 
1103 test "Context multithreaded execution blocks registry mutation" {
1104     const testing = @import("std").testing;
1105 
1106     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1107     defer ctx.deinit(testing.allocator);
1108 
1109     var guard = ctx.enterMultithreadedExecution();
1110     defer guard.deinit();
1111 
1112     const result = ctx.registerOperation("threaded.after", .{ .is_idempotent = true });
1113     try testing.expectError(error.ContextMutationDuringMultithreadedExecution, result);
1114 }
1115 
1116 test "Context multithreaded execution requires preexisting uniqued storage" {
1117     const testing = @import("std").testing;
1118 
1119     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1120     defer ctx.deinit(testing.allocator);
1121     try ctx.allowUnregistered();
1122 
1123     const typ = try ctx.getDialectTypeFromName("threaded.ty");
1124     const attr = try ctx.getI64Attr(42);
1125     _ = try ctx.registerType("threaded.registered");
1126 
1127     var guard = ctx.enterMultithreadedExecution();
1128     defer guard.deinit();
1129 
1130     const typ_again = try ctx.getDialectTypeFromName("threaded.ty");
1131     try testing.expectEqual(typ.impl, typ_again.impl);
1132 
1133     const attr_again = try ctx.getI64Attr(42);
1134     try testing.expectEqual(attr.impl, attr_again.impl);
1135 
1136     try testing.expectError(
1137         error.ContextMutationDuringMultithreadedExecution,
1138         ctx.getDialectTypeFromName("threaded.registered"),
1139     );
1140     try testing.expectError(
1141         error.ContextMutationDuringMultithreadedExecution,
1142         ctx.getI64Attr(100),
1143     );
1144 }
1145 
1146 test "Context multithreaded execution blocks resource creation" {
1147     const testing = @import("std").testing;
1148     const Operation = @import("../root.zig").Operation;
1149 
1150     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1151     defer ctx.deinit(testing.allocator);
1152     try ctx.allowUnregistered();
1153 
1154     var guard = ctx.enterMultithreadedExecution();
1155     defer guard.deinit();
1156 
1157     const state = Operation.State.init("threaded.op", .unknown);
1158     try testing.expectError(
1159         error.ContextMutationDuringMultithreadedExecution,
1160         ctx.createOperation(state),
1161     );
1162 }
1163 
1164 test "Context resources clean up operations with use-def chains" {
1165     const testing = @import("std").testing;
1166     const Operation = @import("../root.zig").Operation;
1167 
1168     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1169     defer ctx.deinit(testing.allocator);
1170     try ctx.allowUnregistered();
1171 
1172     const unknown_type = try ctx.getDialectTypeFromName("test.ty");
1173     var producer_state = Operation.State.init("test.producer", .unknown);
1174     producer_state.addTypes(&.{unknown_type});
1175     const producer = try ctx.createOperation(producer_state);
1176 
1177     const result = producer.getResult(0).?;
1178     var consumer_state = Operation.State.init("test.consumer", .unknown);
1179     consumer_state.addOperands(&.{result});
1180     _ = try ctx.createOperation(consumer_state);
1181 }
1182 
1183 test "Context resources erase operation prevents double free" {
1184     const testing = @import("std").testing;
1185     const Operation = @import("../root.zig").Operation;
1186 
1187     var ctx = try Context.init(testing.allocator, Context.Limits.testing);
1188     defer ctx.deinit(testing.allocator);
1189     try ctx.allowUnregistered();
1190 
1191     const state = Operation.State.init("test.op", .unknown);
1192     const op = try ctx.createOperation(state);
1193 
1194     op.erase();
1195 }
1196 
1197 test "Context resize hints preserve successful allocation fallback" {
1198     var limits = Context.Limits.testing;
1199     limits.transient_bytes = 64;
1200     var context = try Context.init(std.testing.allocator, limits);
1201     defer context.deinit(std.testing.allocator);
1202     const allocator = memory_ctx.transientAllocator(context.memory);
1203     const first = try allocator.alloc(u8, 8);
1204     @memset(first, 37);
1205     const sibling = try allocator.alloc(u8, 8);
1206     defer allocator.free(sibling);
1207     @memset(sibling, 53);
1208     try std.testing.expect(!allocator.resize(first, 16));
1209     try std.testing.expectEqual(null, allocator.remap(first, 16));
1210     try std.testing.expectEqual(@as(?Context.Segment, null), context.exhaustedSegment());
1211     const moved = try allocator.realloc(first, 16);
1212     defer allocator.free(moved);
1213     const expected: [8]u8 = @splat(37);
1214     const unchanged: [8]u8 = @splat(53);
1215     try std.testing.expectEqualSlices(u8, &expected, moved[0..8]);
1216     try std.testing.expectEqualSlices(u8, &unchanged, sibling);
1217     try std.testing.expectEqual(@as(?Context.Segment, null), context.exhaustedSegment());
1218     try std.testing.expectError(error.OutOfMemory, allocator.alloc(u8, 65));
1219     try std.testing.expectEqual(Context.Segment.transient, context.exhaustedSegment().?);
1220 }
1221 
1222 test "the largest limits the ceiling admits reach an allocator and are refused, not aborted" {
1223     const testing = std.testing;
1224 
1225     var limits = Context.Limits.testing;
1226     limits.transient_bytes = Context.maximum_storage_bytes;
1227     const named = Context.refusal(limits) orelse return error.TestExpectedRefusal;
1228     try testing.expectEqual(memory_ctx.Segment.transient, named.segment);
1229 
1230     limits.transient_bytes = named.largest;
1231     try testing.expect(Context.refusal(limits) == null);
1232 
1233     var arena = std.heap.ArenaAllocator.init(testing.allocator);
1234     defer arena.deinit();
1235     const warm = try arena.allocator().alloc(u8, 64);
1236     try testing.expectEqual(@as(usize, 64), warm.len);
1237 
1238     try testing.expectError(error.OutOfMemory, Context.init(arena.allocator(), limits));
1239 }
1240 
1241 test "one byte past the ceiling is refused before any allocator is asked" {
1242     const testing = std.testing;
1243 
1244     var limits = Context.Limits.testing;
1245     limits.transient_bytes = Context.maximum_storage_bytes;
1246     const named = Context.refusal(limits) orelse return error.TestExpectedRefusal;
1247 
1248     limits.transient_bytes = named.largest + 1;
1249     try testing.expect(Context.refusal(limits) != null);
1250 
1251     var refuses_everything = std.testing.FailingAllocator.init(testing.allocator, .{ .fail_index = 0 });
1252     try testing.expectError(
1253         error.CapacityOverflow,
1254         Context.init(refuses_everything.allocator(), limits),
1255     );
1256     try testing.expectEqual(@as(usize, 0), refuses_everything.alloc_index);
1257 }