lib/chant/src/parse/state/storage.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_phase = @import("alloc_phase");
3 const chant = @import("../../root.zig");
4 const ast = @import("../../ast/root.zig");
5 const capacity_mod = @import("capacity.zig");
6 const origin = @import("origin.zig");
7
8 const Token = chant.token.Token;
9
10 const NodeExhaustion = error{
11 NodeCapacityExceeded,
12 SurveyTokensMismatch,
13 };
14 const PlacementExhaustion = error{NodeCapacityExceeded};
15
16 /// The record stores the address and length of one lexed token slice, and a
17 /// token limit equal to that length. The record comes from `survey(tokens)` (a
18 /// first pass counting what a second pass will store). The caller sizes parser
19 /// node storage (a byte buffer the caller allocates) from its limit, and passes
20 /// the record to parsing, so the storage can check that it parses the tokens it
21 /// was sized for. Admission into parser node storage compares both with the
22 /// slice it receives and returns `error.SurveyTokensMismatch` on any
23 /// difference. The token slice stays at the same address and unchanged from the
24 /// survey through parsing.
25 pub const Survey = struct {
26 tokens_ptr: [*]const Token,
27 tokens_len: usize,
28 limits: Limits,
29 };
30
31 /// The storage divides one buffer the caller supplies into slots for up to two
32 /// expression nodes, one statement node and two type nodes per admitted token,
33 /// along with the source token and cached inferred type of each node. A caller
34 /// allocates one aligned buffer of `Capacity.storage_bytes`, hands it to
35 /// `init`, activates it, and passes it to `parse`, and every node of the
36 /// resulting syntax tree (the parsed translation unit) lives here. The buffer
37 /// belongs to the caller, and `deinit` hands it back. Each parse starts by
38 /// clearing the node counts, so a second parse into the same storage
39 /// invalidates every node pointer from the first, and `deinit` invalidates them
40 /// as well. A request for a node when its slots are full returns
41 /// `error.NodeCapacityExceeded` before anything is written. The order of use is
42 /// `init`, `activate`, any number of parses, then `deinit`.
43 pub const Storage = struct {
44 phase: alloc_phase.capacity.Phase,
45 capacity: capacity_mod.Capacity,
46 storage: @This().Storage,
47 expressions: []ast.Expr,
48 statements: []ast.Stmt,
49 types: []ast.Type,
50 type_origins: []origin.TypeOrigin,
51 expression_origins: []usize,
52 expression_types: []?*const ast.Type,
53 type_charges: []u8,
54 admitted_tokens_len: usize,
55 expression_count: usize,
56 statement_count: usize,
57 type_count: usize,
58
59 pub const storage_alignment: usize = capacity_mod.storage_alignment;
60 pub const Storage = []align(storage_alignment) u8;
61 pub const Limits: type = capacity_mod.Limits;
62 pub const Capacity: type = capacity_mod.Capacity;
63 pub const Exhaustion: type = NodeExhaustion;
64 pub const InitError = capacity_mod.DeriveError || error{StorageTooShort};
65 pub const work_limits: alloc_phase.capacity.WorkLimits = .{
66 .transition_steps_max = std.math.maxInt(usize),
67 .cleanup_steps_per_call_max = 0,
68 .cleanup_calls_at_capacity_max = 0,
69 };
70 pub const claim: alloc_phase.capacity.Declaration = .{
71 .source = .{
72 .id = "chant.parser_node_storage",
73 .kind = .phase_static,
74 .limit_source = .caller,
75 .storage = .{
76 .covered = &.{
77 .{
78 .id = "at_most_two_expression_nodes_per_lexed_token",
79 .lifetime = .steady,
80 .detail = "at most two expression nodes per lexed token",
81 },
82 .{
83 .id = "at_most_one_statement_node_per_lexed_token",
84 .lifetime = .steady,
85 .detail = "at most one statement node per lexed token",
86 },
87 .{
88 .id = "at_most_two_dynamic_type_nodes_per_lexed_token",
89 .lifetime = .steady,
90 .detail = "at most two dynamic type nodes per lexed token",
91 },
92 .{
93 .id = "node_origin_inferred_type_cache_and_two_charge_toke_1bf7f8a5a1ad",
94 .lifetime = .steady,
95 .detail = "node origin, inferred-type cache, and two-charge token ledger metadata",
96 },
97 },
98 .excluded = &.{
99 "list and map backing bytes plus their container metadata",
100 "object bindings, scopes, and decoded token-text bytes",
101 "caller-owned lexed tokens and their borrowed source and file bytes",
102 "allocator metadata for caller acquisition and excluded parser allocations",
103 "Choir context and lowered IR storage",
104 },
105 },
106 .capacity = .{
107 .inputs = &.{
108 alloc_phase.capacity.bindInput(capacity_mod.Limits, "tokens", "tokens"),
109 },
110 .type_selectors = &.{
111 alloc_phase.capacity.bindType(ast.Expr, "expr"),
112 alloc_phase.capacity.bindType(ast.Stmt, "stmt"),
113 alloc_phase.capacity.bindType(ast.Type, "type"),
114 alloc_phase.capacity.bindType(origin.TypeOrigin, "typeorigin"),
115 alloc_phase.capacity.bindType(usize, "usize"),
116 alloc_phase.capacity.bindType(?*const ast.Type, "type_type"),
117 alloc_phase.capacity.bindType(u8, "u8"),
118 },
119 .nodes = &.{
120 .{ .input = 0 },
121 .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 2 } } },
122 .{ .scale = .{ .node = 1, .coefficient = .{ .size_of_concrete_type = 0 } } },
123 .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 1 } } },
124 .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 2 } } },
125 .{ .scale = .{ .node = 4, .coefficient = .{ .size_of_concrete_type = 2 } } },
126 .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 2 } } },
127 .{ .scale = .{ .node = 6, .coefficient = .{ .size_of_concrete_type = 3 } } },
128 .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 2 } } },
129 .{ .scale = .{ .node = 8, .coefficient = .{ .size_of_concrete_type = 4 } } },
130 .{ .scale = .{ .node = 0, .coefficient = .{ .literal = 2 } } },
131 .{ .scale = .{ .node = 10, .coefficient = .{ .size_of_concrete_type = 5 } } },
132 .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 6 } } },
133 .{ .constant = 0 },
134 .{ .alignment = .{ .node = 13, .alignment = .{ .concrete_type = 0 } } },
135 .{ .add = .{ .left = 14, .right = 2 } },
136 .{ .alignment = .{ .node = 15, .alignment = .{ .concrete_type = 1 } } },
137 .{ .add = .{ .left = 16, .right = 3 } },
138 .{ .alignment = .{ .node = 17, .alignment = .{ .concrete_type = 2 } } },
139 .{ .add = .{ .left = 18, .right = 5 } },
140 .{ .alignment = .{ .node = 19, .alignment = .{ .concrete_type = 3 } } },
141 .{ .add = .{ .left = 20, .right = 7 } },
142 .{ .alignment = .{ .node = 21, .alignment = .{ .concrete_type = 4 } } },
143 .{ .add = .{ .left = 22, .right = 9 } },
144 .{ .alignment = .{ .node = 23, .alignment = .{ .concrete_type = 5 } } },
145 .{ .add = .{ .left = 24, .right = 11 } },
146 .{ .alignment = .{ .node = 25, .alignment = .{ .concrete_type = 6 } } },
147 .{ .add = .{ .left = 26, .right = 12 } },
148 .{ .alignment = .{ .node = 27, .alignment = .{ .literal = 1 } } },
149 },
150 .assertions = &.{.{
151 .scope = .closure_total,
152 .measure = .retained,
153 .relation = .exact,
154 .expression = 28,
155 }},
156 },
157 .overload = .{
158 .kind = .reject_before_mutation,
159 .detail = "short regions and mismatched or over-limit token surveys reject before node placement",
160 },
161 .risks = .{
162 .transitive = .{
163 .status = .witnessed,
164 .detail = "all parser node creation dispatches exhaustively into the three typed slices",
165 },
166 .foreign = .{
167 .status = .excluded,
168 .detail = "node placement reads borrowed memory and crosses no foreign boundary",
169 },
170 },
171 .work = .{ .equation = "each node placement is one checked slice write bounded by lexed tokens" },
172 .dependencies = &.{"chant.lexed_token_storage"},
173 .obligations = &.{
174 .{ .key = "chant_parser_nodes_capacity", .role = .capacity_model },
175 .{ .key = "chant_parser_nodes_boundary", .role = .overload },
176 .{ .key = "chant_parser_nodes_oom", .role = .custom },
177 .{ .key = "chant_parser_nodes_identity", .role = .overload },
178 .{ .key = "chant_parser_nodes_prepublication", .role = .overload },
179 .{ .key = "chant_parser_nodes_sealed_transitive_risk", .role = .transitive_risk },
180 .{ .key = "chant_parser_nodes_sealed_foreign_risk", .role = .foreign_risk },
181 .{ .key = "chant_parser_nodes_sealed_work_bound", .role = .work_bound },
182 .{ .key = "chant_parser_nodes_differential", .role = .transitive_risk },
183 .{ .key = "chant_parser_nodes_adversarial", .role = .transitive_risk },
184 .{ .key = "chant_parser_type_charge", .role = .transitive_risk },
185 .{ .key = "chant_parser_type_cache", .role = .transitive_risk },
186 },
187 },
188 .bindings = .{
189 .owner = @This(),
190 .seal = .{
191 .family = alloc_phase.capacity.selector(@This().activate),
192 .premise = .{
193 .class = .checked_semantic_fact,
194 .authority = .checker,
195 },
196 },
197 .teardown = .{
198 .family = alloc_phase.capacity.selector(@This().deinit),
199 .premise = .{
200 .class = .checked_semantic_fact,
201 .authority = .checker,
202 },
203 },
204 },
205 };
206
207 pub const Status = struct {
208 expressions: usize,
209 statements: usize,
210 types: usize,
211 };
212
213 pub fn init(
214 storage: @This().Storage,
215 limits: capacity_mod.Limits,
216 ) InitError!@This() {
217 const capacity = try capacity_mod.Capacity.derive(limits);
218 if (storage.len < capacity.storage_bytes) return error.StorageTooShort;
219 const owned = storage[0..capacity.storage_bytes];
220 return .{
221 .phase = .initialization,
222 .capacity = capacity,
223 .storage = owned,
224 .expressions = typedSlice(
225 ast.Expr,
226 owned,
227 capacity.expressions_offset,
228 capacity.expressions,
229 ),
230 .statements = typedSlice(
231 ast.Stmt,
232 owned,
233 capacity.statements_offset,
234 capacity.statements,
235 ),
236 .types = typedSlice(
237 ast.Type,
238 owned,
239 capacity.types_offset,
240 capacity.types,
241 ),
242 .type_origins = typedSlice(
243 origin.TypeOrigin,
244 owned,
245 capacity.type_origins_offset,
246 capacity.types,
247 ),
248 .expression_origins = typedSlice(
249 usize,
250 owned,
251 capacity.expression_origins_offset,
252 capacity.expressions,
253 ),
254 .expression_types = typedSlice(
255 ?*const ast.Type,
256 owned,
257 capacity.expression_types_offset,
258 capacity.expressions,
259 ),
260 .type_charges = typedSlice(
261 u8,
262 owned,
263 capacity.type_charges_offset,
264 capacity.limits.tokens,
265 ),
266 .admitted_tokens_len = 0,
267 .expression_count = 0,
268 .statement_count = 0,
269 .type_count = 0,
270 };
271 }
272
273 pub fn activate(self: *@This()) void {
274 std.debug.assert(self.phase == .initialization);
275 self.assertStorage();
276 self.phase = .steady;
277 }
278
279 pub fn admit(
280 self: *@This(),
281 token_survey: Survey,
282 tokens: []const Token,
283 ) NodeExhaustion!void {
284 std.debug.assert(self.phase == .steady);
285 try requireIdentity(token_survey, tokens);
286 if (token_survey.limits.tokens > self.capacity.limits.tokens) {
287 return error.NodeCapacityExceeded;
288 }
289 @memset(self.type_charges, 0);
290 self.admitted_tokens_len = tokens.len;
291 self.expression_count = 0;
292 self.statement_count = 0;
293 self.type_count = 0;
294 }
295
296 pub fn createExpr(self: *@This(), value: ast.Expr) PlacementExhaustion!*ast.Expr {
297 return self.createExprAt(value, null);
298 }
299
300 pub fn createExprAt(
301 self: *@This(),
302 value: ast.Expr,
303 token_index: ?usize,
304 ) PlacementExhaustion!*ast.Expr {
305 std.debug.assert(self.phase == .steady);
306 if (self.expression_count >= self.expressions.len) {
307 return error.NodeCapacityExceeded;
308 }
309 if (token_index) |index| {
310 if (index >= self.admitted_tokens_len) {
311 return error.NodeCapacityExceeded;
312 }
313 }
314 const result = &self.expressions[self.expression_count];
315 self.expression_origins[self.expression_count] = token_index orelse
316 origin.no_token;
317 self.expression_types[self.expression_count] = null;
318 self.expression_count += 1;
319 result.* = value;
320 return result;
321 }
322
323 pub fn createStmt(self: *@This(), value: ast.Stmt) PlacementExhaustion!*ast.Stmt {
324 std.debug.assert(self.phase == .steady);
325 if (self.statement_count >= self.statements.len) {
326 return error.NodeCapacityExceeded;
327 }
328 const result = &self.statements[self.statement_count];
329 self.statement_count += 1;
330 result.* = value;
331 return result;
332 }
333
334 pub fn createType(
335 self: *@This(),
336 value: ast.Type,
337 placement: origin.TypePlacement,
338 ) PlacementExhaustion!*ast.Type {
339 std.debug.assert(self.phase == .steady);
340 if (self.type_count >= self.types.len) return error.NodeCapacityExceeded;
341 if (!self.validTypePlacement(placement)) {
342 return error.NodeCapacityExceeded;
343 }
344 if (self.type_charges[placement.charged] >= 2) {
345 return error.NodeCapacityExceeded;
346 }
347 const result = &self.types[self.type_count];
348 self.type_charges[placement.charged] += 1;
349 self.type_origins[self.type_count] = placement.origin;
350 self.type_count += 1;
351 result.* = value;
352 return result;
353 }
354
355 pub fn typeOrigin(
356 self: *const @This(),
357 value: *const ast.Type,
358 ) ?origin.TypeOrigin {
359 const index = pointerIndex(ast.Type, self.types, value) orelse return null;
360 std.debug.assert(index < self.type_count);
361 return self.type_origins[index];
362 }
363
364 pub fn expressionOrigin(
365 self: *const @This(),
366 value: *const ast.Expr,
367 ) usize {
368 const index = pointerIndex(ast.Expr, self.expressions, value) orelse
369 unreachable;
370 std.debug.assert(index < self.expression_count);
371 const token_index = self.expression_origins[index];
372 std.debug.assert(token_index != origin.no_token);
373 return token_index;
374 }
375
376 pub fn inferredType(
377 self: *const @This(),
378 value: *const ast.Expr,
379 ) ?*const ast.Type {
380 const index = pointerIndex(ast.Expr, self.expressions, value) orelse
381 unreachable;
382 std.debug.assert(index < self.expression_count);
383 return self.expression_types[index];
384 }
385
386 pub fn setInferredType(
387 self: *@This(),
388 value: *const ast.Expr,
389 inferred: *const ast.Type,
390 ) void {
391 const index = pointerIndex(ast.Expr, self.expressions, value) orelse
392 unreachable;
393 std.debug.assert(index < self.expression_count);
394 std.debug.assert(self.expression_types[index] == null);
395 self.expression_types[index] = inferred;
396 }
397
398 pub fn status(self: *const @This()) Status {
399 std.debug.assert(self.phase == .steady);
400 return .{
401 .expressions = self.expression_count,
402 .statements = self.statement_count,
403 .types = self.type_count,
404 };
405 }
406
407 pub fn deinit(self: *@This()) @This().Storage {
408 std.debug.assert(self.phase == .steady);
409 self.phase = .teardown;
410 const storage = self.storage;
411 self.* = undefined;
412 return storage;
413 }
414
415 fn assertStorage(self: *const @This()) void {
416 const expected = capacity_mod.Capacity.derive(
417 self.capacity.limits,
418 ) catch unreachable;
419 std.debug.assert(std.meta.eql(expected, self.capacity));
420 std.debug.assert(self.storage.len == self.capacity.storage_bytes);
421 std.debug.assert(self.expressions.len == self.capacity.expressions);
422 std.debug.assert(self.statements.len == self.capacity.statements);
423 std.debug.assert(self.types.len == self.capacity.types);
424 std.debug.assert(self.type_origins.len == self.capacity.types);
425 std.debug.assert(self.expression_origins.len == self.capacity.expressions);
426 std.debug.assert(self.expression_types.len == self.capacity.expressions);
427 std.debug.assert(self.type_charges.len == self.capacity.limits.tokens);
428 }
429
430 fn validTypePlacement(
431 self: *const @This(),
432 placement: origin.TypePlacement,
433 ) bool {
434 if (placement.charged >= self.admitted_tokens_len) return false;
435 if (placement.origin.primary >= self.admitted_tokens_len) return false;
436 if (placement.origin.secondary != origin.no_token and
437 placement.origin.secondary >= self.admitted_tokens_len)
438 {
439 return false;
440 }
441 return placement.charged == placement.origin.primary or
442 placement.charged == placement.origin.secondary;
443 }
444 };
445
446 pub const Limits = capacity_mod.Limits;
447 pub const Capacity = capacity_mod.Capacity;
448 pub const CapacityError = capacity_mod.DeriveError;
449 pub const Exhaustion = NodeExhaustion;
450 pub const TypeOrigin = origin.TypeOrigin;
451 pub const TypePlacement = origin.TypePlacement;
452
453 pub fn survey(tokens: []const Token) Survey {
454 return .{
455 .tokens_ptr = tokens.ptr,
456 .tokens_len = tokens.len,
457 .limits = .{ .tokens = tokens.len },
458 };
459 }
460
461 fn requireIdentity(token_survey: Survey, tokens: []const Token) Exhaustion!void {
462 if (token_survey.tokens_ptr != tokens.ptr) return error.SurveyTokensMismatch;
463 if (token_survey.tokens_len != tokens.len) return error.SurveyTokensMismatch;
464 if (token_survey.limits.tokens != tokens.len) return error.SurveyTokensMismatch;
465 }
466
467 fn typedSlice(
468 comptime T: type,
469 storage: Storage.Storage,
470 offset: usize,
471 count: usize,
472 ) []T {
473 const bytes = storage[offset..][0 .. count * @sizeOf(T)];
474 const aligned: []align(@alignOf(T)) u8 = @alignCast(bytes);
475 return std.mem.bytesAsSlice(T, aligned);
476 }
477
478 fn pointerIndex(
479 comptime T: type,
480 values: []const T,
481 pointer: *const T,
482 ) ?usize {
483 const start = @intFromPtr(values.ptr);
484 const address = @intFromPtr(pointer);
485 const bytes = values.len * @sizeOf(T);
486 if (address < start or address >= start + bytes) return null;
487 const offset = address - start;
488 if (offset % @sizeOf(T) != 0) return null;
489 return offset / @sizeOf(T);
490 }
491
492 fn initFailures(allocator: std.mem.Allocator) !void {
493 var tokens: [4]Token = undefined;
494 const token_survey = survey(&tokens);
495 const capacity = try Capacity.derive(token_survey.limits);
496 const bytes = try allocator.alignedAlloc(
497 u8,
498 .fromByteUnits(Storage.storage_alignment),
499 capacity.storage_bytes,
500 );
501 defer allocator.free(bytes);
502 var storage = try Storage.init(bytes, token_survey.limits);
503 storage.activate();
504 try storage.admit(token_survey, &tokens);
505 _ = try storage.createExpr(.{
506 .integer_literal = .{ .value = 1, .type = &ast.types.int_type },
507 });
508 _ = try storage.createStmt(.empty);
509 _ = try storage.createType(
510 ast.types.int_type,
511 .{ .charged = 0, .origin = .{ .primary = 0 } },
512 );
513 _ = storage.deinit();
514 }
515
516 test "parser node storage acquisition retries after allocation failure" {
517 comptime {
518 @stardustClaim(
519 @import("alloc_phase").capacity.witness(Storage, "chant_parser_nodes_oom"),
520 null,
521 null,
522 null,
523 null,
524 null,
525 null,
526 );
527 }
528
529 try std.testing.checkAllAllocationFailures(
530 std.testing.allocator,
531 initFailures,
532 .{},
533 );
534 }
535
536 test "parser node storage admits exact capacity and rejects short storage" {
537 comptime {
538 @stardustClaim(
539 @import("alloc_phase").capacity.witness(Storage, "chant_parser_nodes_boundary"),
540 null,
541 null,
542 null,
543 null,
544 null,
545 null,
546 );
547 }
548
549 var tokens: [2]Token = undefined;
550 const token_survey = survey(&tokens);
551 const capacity = try Capacity.derive(token_survey.limits);
552 var bytes: [4096]u8 align(Storage.storage_alignment) = undefined;
553 try std.testing.expect(capacity.storage_bytes < bytes.len);
554
555 var exact = try Storage.init(
556 bytes[0..capacity.storage_bytes],
557 token_survey.limits,
558 );
559 exact.activate();
560 try exact.admit(token_survey, &tokens);
561 for (0..capacity.expressions) |value| {
562 _ = try exact.createExpr(.{
563 .integer_literal = .{
564 .value = @intCast(value),
565 .type = &ast.types.int_type,
566 },
567 });
568 }
569 for (0..capacity.statements) |_| _ = try exact.createStmt(.empty);
570 for (0..capacity.types) |index| {
571 const token_index = index / 2;
572 _ = try exact.createType(
573 ast.types.int_type,
574 .{
575 .charged = token_index,
576 .origin = .{ .primary = token_index },
577 },
578 );
579 }
580 const full = exact.status();
581 try std.testing.expectEqual(capacity.expressions, full.expressions);
582 try std.testing.expectEqual(capacity.statements, full.statements);
583 try std.testing.expectEqual(capacity.types, full.types);
584 try std.testing.expectError(
585 error.NodeCapacityExceeded,
586 exact.createExpr(.{
587 .integer_literal = .{ .value = 0, .type = &ast.types.int_type },
588 }),
589 );
590 try std.testing.expectError(
591 error.NodeCapacityExceeded,
592 exact.createStmt(.empty),
593 );
594 try std.testing.expectError(
595 error.NodeCapacityExceeded,
596 exact.createType(
597 ast.types.int_type,
598 .{ .charged = 0, .origin = .{ .primary = 0 } },
599 ),
600 );
601 _ = exact.deinit();
602
603 try std.testing.expectError(
604 error.StorageTooShort,
605 Storage.init(
606 bytes[0 .. capacity.storage_bytes - 1],
607 token_survey.limits,
608 ),
609 );
610
611 const smaller_limits = Limits{ .tokens = token_survey.limits.tokens - 1 };
612 const smaller_capacity = try Capacity.derive(smaller_limits);
613 var smaller = try Storage.init(
614 bytes[0..smaller_capacity.storage_bytes],
615 smaller_limits,
616 );
617 smaller.activate();
618 _ = try smaller.createExpr(.{
619 .integer_literal = .{ .value = 0, .type = &ast.types.int_type },
620 });
621 const before = smaller.status();
622 try std.testing.expectError(
623 error.NodeCapacityExceeded,
624 smaller.admit(token_survey, &tokens),
625 );
626 try std.testing.expectEqual(before, smaller.status());
627 _ = smaller.deinit();
628 }
629
630 test "parser node storage requires the surveyed token identity" {
631 comptime {
632 @stardustClaim(
633 @import("alloc_phase").capacity.witness(Storage, "chant_parser_nodes_identity"),
634 null,
635 null,
636 null,
637 null,
638 null,
639 null,
640 );
641 }
642
643 var tokens: [2]Token = undefined;
644 var other_tokens: [2]Token = undefined;
645 std.mem.doNotOptimizeAway(&tokens);
646 std.mem.doNotOptimizeAway(&other_tokens);
647 try std.testing.expect(@intFromPtr(&tokens) != @intFromPtr(&other_tokens));
648
649 const token_survey = survey(&tokens);
650 const capacity = try Capacity.derive(token_survey.limits);
651 var bytes: [4096]u8 align(Storage.storage_alignment) = undefined;
652 try std.testing.expect(capacity.storage_bytes <= bytes.len);
653 var storage = try Storage.init(
654 bytes[0..capacity.storage_bytes],
655 token_survey.limits,
656 );
657 storage.activate();
658 _ = try storage.createStmt(.empty);
659 const before = storage.status();
660 try std.testing.expectError(
661 error.SurveyTokensMismatch,
662 storage.admit(token_survey, &other_tokens),
663 );
664 try std.testing.expectEqual(before, storage.status());
665 var forged_survey = token_survey;
666 forged_survey.limits.tokens -= 1;
667 try std.testing.expectError(
668 error.SurveyTokensMismatch,
669 storage.admit(forged_survey, &tokens),
670 );
671 try std.testing.expectEqual(before, storage.status());
672 _ = storage.deinit();
673 }
674
675 test "parser type origins enforce two charges per token" {
676 comptime {
677 @stardustClaim(
678 @import("alloc_phase").capacity.witness(Storage, "chant_parser_type_charge"),
679 null,
680 null,
681 null,
682 null,
683 null,
684 null,
685 );
686 }
687
688 var tokens: [2]Token = undefined;
689 const token_survey = survey(&tokens);
690 const capacity = try Capacity.derive(token_survey.limits);
691 var bytes: [8192]u8 align(Storage.storage_alignment) = undefined;
692 try std.testing.expect(capacity.storage_bytes <= bytes.len);
693 var storage = try Storage.init(
694 bytes[0..capacity.storage_bytes],
695 token_survey.limits,
696 );
697 storage.activate();
698 try storage.admit(token_survey, &tokens);
699 const placement = TypePlacement{
700 .charged = 0,
701 .origin = .{ .primary = 0 },
702 };
703 _ = try storage.createType(ast.types.int_type, placement);
704 _ = try storage.createType(ast.types.int_type, placement);
705 const before = storage.status();
706 try std.testing.expect(before.types < capacity.types);
707 try std.testing.expectError(
708 error.NodeCapacityExceeded,
709 storage.createType(ast.types.int_type, placement),
710 );
711 try std.testing.expectEqual(before, storage.status());
712 _ = storage.deinit();
713 }
714
715 comptime {
716 alloc_phase.capacity.requireProvisionedRejectingOwnerShape(Storage);
717 }