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 }