OR-1 dataflow CPU sketch
1"""Tests for frame-based allocation in the assembler.
2
3Tests AC5 acceptance criteria for frame layouts, IRAM deduplication,
4activation ID assignment, and mode computation.
5"""
6
7from dataclasses import replace
8from asm.allocate import (
9 _assign_iram_offsets,
10 _assign_act_ids,
11 _deduplicate_iram,
12 _compute_modes,
13 _compute_frame_layouts,
14 allocate,
15)
16from asm.ir import (
17 IRNode,
18 IREdge,
19 SourceLoc,
20 CallSite,
21 IRGraph,
22 SystemConfig,
23 FrameLayout,
24 FrameSlotMap,
25)
26from asm.errors import ErrorCategory, ErrorSeverity
27from cm_inst import (
28 ArithOp,
29 MemOp,
30 Port,
31 RoutingOp,
32 OutputStyle,
33 TokenKind,
34)
35
36
37class TestIRAMDeduplication:
38 """AC5.1: IRAM offsets deduplicated for identical instruction templates."""
39
40 def test_identical_instructions_share_offset(self):
41 """Two nodes with same opcode, mode, and fref share IRAM offset."""
42 # Create two nodes with identical template
43 node1 = IRNode(
44 name="&add1",
45 opcode=ArithOp.ADD,
46 pe=0,
47 iram_offset=0,
48 mode=(OutputStyle.INHERIT, False, 1),
49 fref=0,
50 wide=False,
51 )
52 node2 = IRNode(
53 name="&add2",
54 opcode=ArithOp.ADD,
55 pe=0,
56 iram_offset=1,
57 mode=(OutputStyle.INHERIT, False, 1),
58 fref=0,
59 wide=False,
60 )
61
62 nodes_on_pe = {"&add1": node1, "&add2": node2}
63 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0)
64
65 # Both should have same offset
66 assert deduped["&add1"].iram_offset == 0
67 assert deduped["&add2"].iram_offset == 0
68
69 def test_different_opcodes_different_offsets(self):
70 """Nodes with different opcodes keep different offsets."""
71 node1 = IRNode(
72 name="&add",
73 opcode=ArithOp.ADD,
74 pe=0,
75 iram_offset=0,
76 mode=(OutputStyle.INHERIT, False, 1),
77 fref=0,
78 wide=False,
79 )
80 node2 = IRNode(
81 name="&sub",
82 opcode=ArithOp.SUB,
83 pe=0,
84 iram_offset=1,
85 mode=(OutputStyle.INHERIT, False, 1),
86 fref=0,
87 wide=False,
88 )
89
90 nodes_on_pe = {"&add": node1, "&sub": node2}
91 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0)
92
93 assert deduped["&add"].iram_offset == 0
94 assert deduped["&sub"].iram_offset == 1
95
96 def test_same_opcode_different_modes_different_offsets(self):
97 """Same opcode but different modes get different offsets."""
98 node1 = IRNode(
99 name="&add1",
100 opcode=ArithOp.ADD,
101 pe=0,
102 iram_offset=0,
103 mode=(OutputStyle.INHERIT, False, 1),
104 fref=0,
105 wide=False,
106 )
107 node2 = IRNode(
108 name="&add2",
109 opcode=ArithOp.ADD,
110 pe=0,
111 iram_offset=1,
112 mode=(OutputStyle.INHERIT, True, 1), # has_const differs
113 fref=0,
114 wide=False,
115 )
116
117 nodes_on_pe = {"&add1": node1, "&add2": node2}
118 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0)
119
120 assert deduped["&add1"].iram_offset == 0
121 assert deduped["&add2"].iram_offset == 1
122
123 def test_seed_nodes_excluded(self):
124 """Seed nodes are excluded from deduplication."""
125 seed_node = IRNode(
126 name="&seed",
127 opcode=ArithOp.ADD,
128 pe=0,
129 iram_offset=None,
130 seed=True,
131 )
132 normal_node = IRNode(
133 name="&add",
134 opcode=ArithOp.ADD,
135 pe=0,
136 iram_offset=0,
137 mode=(OutputStyle.INHERIT, False, 1),
138 fref=0,
139 wide=False,
140 )
141
142 nodes_on_pe = {"&seed": seed_node, "&add": normal_node}
143 deduped = _deduplicate_iram(nodes_on_pe, pe_id=0)
144
145 # Seed node should be unchanged
146 assert deduped["&seed"].seed
147 assert deduped["&seed"].iram_offset is None
148
149
150class TestActivationIDAssignment:
151 """AC5.3, AC5.7: Activation IDs assigned sequentially, exhaustion checked."""
152
153 def test_single_function_gets_act_id_0(self):
154 """Single function scope gets act_id=0."""
155 node = IRNode(
156 name="&add",
157 opcode=ArithOp.ADD,
158 pe=0,
159 iram_offset=0,
160 )
161
162 updated, errors = _assign_act_ids([node], {"&add": node}, frame_count=8, pe_id=0)
163
164 # Root scope node should get act_id=0
165 assert updated["&add"].act_id == 0
166 assert not errors
167
168 def test_multiple_functions_different_act_ids(self):
169 """Different functions on same PE get different act_ids."""
170 node1 = IRNode(
171 name="$func1.&add",
172 opcode=ArithOp.ADD,
173 pe=0,
174 iram_offset=0,
175 )
176 node2 = IRNode(
177 name="$func2.&sub",
178 opcode=ArithOp.SUB,
179 pe=0,
180 iram_offset=1,
181 )
182
183 all_nodes = {"$func1.&add": node1, "$func2.&sub": node2}
184 updated, errors = _assign_act_ids(
185 [node1, node2], all_nodes, frame_count=8, pe_id=0
186 )
187
188 # Different functions should get different act_ids
189 assert updated["$func1.&add"].act_id == 0
190 assert updated["$func2.&sub"].act_id == 1
191 assert not errors
192
193 def test_activation_exhaustion_error(self):
194 """When >frame_count act_ids needed, error with FRAME category."""
195 nodes = []
196 all_nodes = {}
197 for i in range(10):
198 node = IRNode(
199 name=f"$func{i}.&op",
200 opcode=ArithOp.ADD,
201 pe=0,
202 iram_offset=i,
203 )
204 nodes.append(node)
205 all_nodes[f"$func{i}.&op"] = node
206
207 updated, errors = _assign_act_ids(nodes, all_nodes, frame_count=8, pe_id=0)
208
209 # Should report activation exhaustion error
210 assert len(errors) > 0
211 assert errors[0].category == ErrorCategory.FRAME
212 assert "exhaustion" in errors[0].message.lower()
213
214 def test_same_function_across_pes_reuses_act_id(self):
215 """Same function on different PEs can reuse act_id."""
216 node1 = IRNode(
217 name="$func.&add",
218 opcode=ArithOp.ADD,
219 pe=0,
220 iram_offset=0,
221 )
222 node2 = IRNode(
223 name="$func.&sub",
224 opcode=ArithOp.SUB,
225 pe=1,
226 iram_offset=0,
227 )
228
229 # Process PE 0
230 all_nodes = {"$func.&add": node1, "$func.&sub": node2}
231 updated0, errors0 = _assign_act_ids([node1], all_nodes, frame_count=8, pe_id=0)
232
233 # Process PE 1
234 updated1, errors1 = _assign_act_ids([node2], all_nodes, frame_count=8, pe_id=1)
235
236 # Both can have same act_id within their respective PEs
237 assert updated0["$func.&add"].act_id == 0
238 assert updated1["$func.&sub"].act_id == 0
239
240
241class TestModeComputation:
242 """AC5.5: Mode (OutputStyle + has_const + dest_count) computed from topology."""
243
244 def test_sink_op_has_zero_dest_count(self):
245 """SM WRITE produces SINK mode with dest_count=0."""
246 node = IRNode(
247 name="&write",
248 opcode=MemOp.WRITE,
249 const=0x100,
250 pe=0,
251 )
252
253 edges_by_source = {"&write": []} # No outgoing edges
254 modes = _compute_modes({"&write": node}, edges_by_source)
255
256 mode = modes["&write"].mode
257 assert mode[0] == OutputStyle.SINK # OutputStyle
258 assert mode[2] == 0 # dest_count should be 0
259
260 def test_read_op_has_inherit_style(self):
261 """SM READ produces INHERIT style."""
262 node = IRNode(
263 name="&read",
264 opcode=MemOp.READ,
265 pe=0,
266 )
267
268 edges_by_source = {"&read": []}
269 modes = _compute_modes({"&read": node}, edges_by_source)
270
271 mode = modes["&read"].mode
272 assert mode[0] == OutputStyle.INHERIT
273
274 def test_has_const_flag_set(self):
275 """Node with const field has has_const=True."""
276 node = IRNode(
277 name="&add_const",
278 opcode=ArithOp.ADD,
279 const=42,
280 pe=0,
281 )
282
283 edges_by_source = {"&add_const": []}
284 modes = _compute_modes({"&add_const": node}, edges_by_source)
285
286 mode = modes["&add_const"].mode
287 assert mode[1] is True # has_const
288
289 def test_dest_count_from_edges(self):
290 """dest_count matches number of outgoing edges."""
291 node = IRNode(
292 name="&add",
293 opcode=ArithOp.ADD,
294 pe=0,
295 )
296 edge1 = IREdge(source="&add", dest="&sub", port=Port.L)
297 edge2 = IREdge(source="&add", dest="&mul", port=Port.R)
298
299 edges_by_source = {"&add": [edge1, edge2]}
300 modes = _compute_modes({"&add": node}, edges_by_source)
301
302 mode = modes["&add"].mode
303 assert mode[2] == 2 # dest_count
304
305 def test_free_frame_produces_sink(self):
306 """FREE_FRAME produces SINK style."""
307 node = IRNode(
308 name="&free",
309 opcode=RoutingOp.FREE_FRAME,
310 pe=0,
311 )
312
313 edges_by_source = {"&free": []}
314 modes = _compute_modes({"&free": node}, edges_by_source)
315
316 mode = modes["&free"].mode
317 assert mode[0] == OutputStyle.SINK
318
319 def test_ctx_override_produces_change_tag_with_dest_count_1(self):
320 """AC5.5: ctx_override=True on outgoing edge produces CHANGE_TAG mode with dest_count=1.
321
322 When a node has an outgoing edge with ctx_override=True (crossing function boundary),
323 the output mode should be CHANGE_TAG with dest_count=1.
324 """
325 source_node = IRNode(
326 name="&source",
327 opcode=ArithOp.ADD,
328 pe=0,
329 )
330 dest_node = IRNode(
331 name="&dest",
332 opcode=ArithOp.SUB,
333 pe=0,
334 )
335
336 # Create edge with ctx_override=True
337 edge = IREdge(
338 source="&source",
339 dest="&dest",
340 port=Port.L,
341 ctx_override=True,
342 )
343
344 edges_by_source = {"&source": [edge], "&dest": []}
345 nodes = {"&source": source_node, "&dest": dest_node}
346 modes = _compute_modes(nodes, edges_by_source)
347
348 mode = modes["&source"].mode
349 # ctx_override=True should produce CHANGE_TAG style
350 assert mode[0] == OutputStyle.CHANGE_TAG, \
351 f"ctx_override=True should produce CHANGE_TAG, got {mode[0]}"
352 # dest_count should be 1 (one outgoing edge)
353 assert mode[2] == 1, \
354 f"dest_count should be 1 for single ctx_override edge, got {mode[2]}"
355
356
357class TestFrameLayoutComputation:
358 """AC5.2, AC5.4, AC5.6: Frame slot assignment and deduplication."""
359
360 def test_frame_layout_canonical_per_function(self):
361 """Two activations of same function produce identical FrameLayout."""
362 # Create nodes with same structure but different names
363 node1 = IRNode(
364 name="&add1",
365 opcode=ArithOp.ADD,
366 pe=0,
367 act_id=0,
368 iram_offset=0,
369 mode=(OutputStyle.INHERIT, False, 1),
370 )
371 node2 = IRNode(
372 name="&add2",
373 opcode=ArithOp.ADD,
374 pe=0,
375 act_id=1,
376 iram_offset=1,
377 mode=(OutputStyle.INHERIT, False, 1),
378 )
379
380 nodes_on_pe = {"&add1": node1, "&add2": node2}
381 all_nodes = nodes_on_pe.copy()
382 edges_by_source = {"&add1": [], "&add2": []}
383 edges_by_dest = {}
384
385 updated, errors = _compute_frame_layouts(
386 nodes_on_pe,
387 edges_by_source,
388 edges_by_dest,
389 all_nodes,
390 frame_slots=64,
391 matchable_offsets=8,
392 pe_id=0,
393 )
394
395 # Both activations should have layouts with same structure
396 layout1 = updated["&add1"].frame_layout
397 layout2 = updated["&add2"].frame_layout
398 assert layout1 is not None
399 assert layout2 is not None
400 # Layouts should have same slot structure (though may be different objects)
401 assert layout1.total_slots == layout2.total_slots
402
403 def test_frame_slot_overflow_error(self):
404 """When total slots > frame_slots, error with FRAME category."""
405 # Create nodes that would exceed frame slots
406 nodes_on_pe = {}
407 all_nodes = {}
408 for i in range(100):
409 node = IRNode(
410 name=f"&op{i}",
411 opcode=ArithOp.ADD,
412 pe=0,
413 act_id=0,
414 iram_offset=i,
415 mode=(OutputStyle.INHERIT, True, 2), # Each needs 3+ slots
416 )
417 nodes_on_pe[f"&op{i}"] = node
418 all_nodes[f"&op{i}"] = node
419
420 edges_by_source = {f"&op{i}": [] for i in range(100)}
421 edges_by_dest = {}
422
423 updated, errors = _compute_frame_layouts(
424 nodes_on_pe,
425 edges_by_source,
426 edges_by_dest,
427 all_nodes,
428 frame_slots=64,
429 matchable_offsets=8,
430 pe_id=0,
431 )
432
433 # Should report frame slot overflow
434 assert len(errors) > 0
435 assert errors[0].category == ErrorCategory.FRAME
436
437 def test_match_slots_at_low_offsets(self):
438 """Match operands occupy slots 0 to matchable_offsets-1."""
439 # Create dyadic (matchable) node
440 node1 = IRNode(
441 name="&add",
442 opcode=ArithOp.ADD,
443 pe=0,
444 act_id=0,
445 iram_offset=0,
446 mode=(OutputStyle.INHERIT, False, 1),
447 )
448 # Monadic node shouldn't count as match
449 node2 = IRNode(
450 name="&inc",
451 opcode=ArithOp.INC,
452 pe=0,
453 act_id=0,
454 iram_offset=1,
455 mode=(OutputStyle.INHERIT, False, 1),
456 )
457
458 nodes_on_pe = {"&add": node1, "&inc": node2}
459 all_nodes = nodes_on_pe.copy()
460 edges_by_source = {"&add": [], "&inc": []}
461 edges_by_dest = {}
462
463 updated, errors = _compute_frame_layouts(
464 nodes_on_pe,
465 edges_by_source,
466 edges_by_dest,
467 all_nodes,
468 frame_slots=64,
469 matchable_offsets=8,
470 pe_id=0,
471 )
472
473 assert not errors
474 layout = updated["&add"].frame_layout
475 assert layout is not None
476 # Match slots should start at 0
477 assert 0 in layout.slot_map.match_slots
478
479 def test_matchable_offset_exceedance_warning(self):
480 """AC5.8: Warn when dyadic_count exceeds matchable_offsets.
481
482 Creates an activation with 9+ dyadic nodes but matchable_offsets=8.
483 Verifies: (1) errors list contains warning with FRAME category and WARNING severity,
484 (2) the function still returns a valid layout (not a hard error).
485 """
486 # Create 9 dyadic nodes (more than matchable_offsets=8)
487 nodes_on_pe = {}
488 all_nodes = {}
489 for i in range(9):
490 node = IRNode(
491 name=f"&add{i}",
492 opcode=ArithOp.ADD, # Dyadic
493 pe=0,
494 act_id=0,
495 iram_offset=i,
496 mode=(OutputStyle.INHERIT, False, 1),
497 )
498 nodes_on_pe[f"&add{i}"] = node
499 all_nodes[f"&add{i}"] = node
500
501 edges_by_source = {f"&add{i}": [] for i in range(9)}
502 edges_by_dest = {}
503
504 updated, errors = _compute_frame_layouts(
505 nodes_on_pe,
506 edges_by_source,
507 edges_by_dest,
508 all_nodes,
509 frame_slots=64,
510 matchable_offsets=8, # Less than 9 dyadic nodes
511 pe_id=0,
512 )
513
514 # Should generate a warning with FRAME category and WARNING severity
515 assert len(errors) > 0, "Expected warning about matchable offset exceedance"
516 warning = errors[0]
517 assert warning.category == ErrorCategory.FRAME
518 assert warning.severity == ErrorSeverity.WARNING
519 assert "dyadic" in warning.message.lower()
520 assert "match slot" in warning.message.lower()
521
522 # Function should still return valid layout (not a hard error)
523 assert updated is not None
524 assert len(updated) == 9
525 for i in range(9):
526 assert f"&add{i}" in updated
527 assert updated[f"&add{i}"].frame_layout is not None
528
529 def test_constants_deduplicated_by_value(self):
530 """Instructions with same constant value can share slot."""
531 node1 = IRNode(
532 name="&op1",
533 opcode=ArithOp.ADD,
534 const=42,
535 pe=0,
536 act_id=0,
537 iram_offset=0,
538 mode=(OutputStyle.INHERIT, True, 1),
539 )
540 node2 = IRNode(
541 name="&op2",
542 opcode=ArithOp.SUB,
543 const=42, # Same constant
544 pe=0,
545 act_id=0,
546 iram_offset=1,
547 mode=(OutputStyle.INHERIT, True, 1),
548 )
549
550 nodes_on_pe = {"&op1": node1, "&op2": node2}
551 all_nodes = nodes_on_pe.copy()
552 edges_by_source = {"&op1": [], "&op2": []}
553 edges_by_dest = {}
554
555 updated, errors = _compute_frame_layouts(
556 nodes_on_pe,
557 edges_by_source,
558 edges_by_dest,
559 all_nodes,
560 frame_slots=64,
561 matchable_offsets=8,
562 pe_id=0,
563 )
564
565 assert not errors
566 # Both should have same layout with one const slot
567 layout = updated["&op1"].frame_layout
568 assert len(layout.slot_map.const_slots) == 1
569
570 def test_per_node_fref_differentiation(self):
571 """AC5.2: Nodes in same activation with different modes get different frefs.
572
573 Verifies that each node gets a unique fref based on its mode's slot count.
574 Node A: has_const=True, dest_count=1 → 2 slots → fref=8
575 Node B: has_const=False, dest_count=1 → 1 slot → fref=10 (8+2)
576 """
577 # Create two nodes with different modes
578 node_a = IRNode(
579 name="&op_a",
580 opcode=ArithOp.ADD,
581 const=42, # has_const=True
582 pe=0,
583 act_id=0,
584 iram_offset=0,
585 mode=(OutputStyle.INHERIT, True, 1), # has_const=True, 1 dest → 2 slots
586 )
587 node_b = IRNode(
588 name="&op_b",
589 opcode=ArithOp.SUB,
590 pe=0,
591 act_id=0,
592 iram_offset=1,
593 mode=(OutputStyle.INHERIT, False, 1), # has_const=False, 1 dest → 1 slot
594 )
595
596 # Create edge from &op_a to &op_b
597 edge = IREdge(source="&op_a", dest="&op_b", port=Port.L)
598
599 nodes_on_pe = {"&op_a": node_a, "&op_b": node_b}
600 all_nodes = nodes_on_pe.copy()
601 edges_by_source = {"&op_a": [edge], "&op_b": []}
602 edges_by_dest = {"&op_b": [edge]}
603
604 matchable_offsets = 8
605 updated, errors = _compute_frame_layouts(
606 nodes_on_pe,
607 edges_by_source,
608 edges_by_dest,
609 all_nodes,
610 frame_slots=64,
611 matchable_offsets=matchable_offsets,
612 pe_id=0,
613 )
614
615 assert not errors, f"Unexpected errors: {errors}"
616
617 # Verify both nodes got frefs
618 assert updated["&op_a"].fref is not None
619 assert updated["&op_b"].fref is not None
620
621 # Verify they have different frefs
622 fref_a = updated["&op_a"].fref
623 fref_b = updated["&op_b"].fref
624 assert fref_a != fref_b, f"Expected different frefs, got {fref_a} and {fref_b}"
625
626 # Verify fref_a starts at matchable_offsets
627 assert fref_a == matchable_offsets, f"Expected fref_a={matchable_offsets}, got {fref_a}"
628
629 # Verify fref_b = fref_a + slot_count_a (where slot_count_a = 2)
630 # Mode A: (INHERIT, True, 1) → 1 (const) + 1 (dest) = 2 slots
631 expected_fref_b = fref_a + 2
632 assert fref_b == expected_fref_b, f"Expected fref_b={expected_fref_b}, got {fref_b}"
633
634
635class TestFrameLayoutIntegration:
636 """Integration tests for full allocation pipeline."""
637
638 def test_minimal_graph(self):
639 """Minimal graph with one node allocates successfully."""
640 node = IRNode(
641 name="&add",
642 opcode=ArithOp.ADD,
643 pe=0,
644 act_id=0,
645 iram_offset=0,
646 mode=(OutputStyle.INHERIT, False, 1),
647 )
648
649 updated, errors = _compute_frame_layouts(
650 {"&add": node},
651 {"&add": []},
652 {},
653 {"&add": node},
654 frame_slots=64,
655 matchable_offsets=8,
656 pe_id=0,
657 )
658
659 assert not errors
660 assert updated["&add"].frame_layout is not None
661 assert updated["&add"].fref is not None
662
663 def test_multiple_activations_separate_layouts(self):
664 """Nodes with different act_ids get separate layout configurations."""
665 node1 = IRNode(
666 name="&op1",
667 opcode=ArithOp.ADD,
668 pe=0,
669 act_id=0,
670 iram_offset=0,
671 mode=(OutputStyle.INHERIT, False, 1),
672 )
673 node2 = IRNode(
674 name="&op2",
675 opcode=ArithOp.ADD,
676 pe=0,
677 act_id=1, # Different activation
678 iram_offset=1,
679 mode=(OutputStyle.INHERIT, True, 1), # Different mode
680 )
681
682 updated, errors = _compute_frame_layouts(
683 {"&op1": node1, "&op2": node2},
684 {"&op1": [], "&op2": []},
685 {},
686 {"&op1": node1, "&op2": node2},
687 frame_slots=64,
688 matchable_offsets=8,
689 pe_id=0,
690 )
691
692 assert not errors
693 layout1 = updated["&op1"].frame_layout
694 layout2 = updated["&op2"].frame_layout
695 # Layouts can have different sizes depending on mode
696 # (one has const, one doesn't)
697 assert layout1 is not None
698 assert layout2 is not None
699
700 def test_full_allocate_pipeline(self):
701 """Full integration test: allocate() with complete IRGraph.
702
703 Tests that allocate() correctly:
704 - Assigns IRAM offsets
705 - Assigns activation IDs
706 - Computes modes
707 - Computes frame layouts
708 - Deduplicates IRAM entries
709 - Resolves destinations to FrameDest
710 """
711 # Create a simple graph: two arithmetic nodes with an edge
712 node_add = IRNode(
713 name="&add",
714 opcode=ArithOp.ADD,
715 const=None,
716 pe=0,
717 loc=SourceLoc(1, 1),
718 )
719 node_inc = IRNode(
720 name="&inc",
721 opcode=ArithOp.INC,
722 const=None,
723 pe=0,
724 loc=SourceLoc(2, 1),
725 )
726 edge = IREdge(
727 source="&add",
728 dest="&inc",
729 port=Port.L,
730 loc=SourceLoc(1, 5),
731 )
732
733 # Create the IRGraph
734 system = SystemConfig(
735 pe_count=1,
736 sm_count=0,
737 iram_capacity=64,
738 frame_count=8,
739 frame_slots=64,
740 matchable_offsets=8,
741 )
742
743 graph = IRGraph(
744 nodes={"&add": node_add, "&inc": node_inc},
745 edges=[edge],
746 regions=[],
747 system=system,
748 call_sites=[],
749 )
750
751 # Call allocate()
752 result = allocate(graph)
753
754 # Verify no errors
755 assert not result.errors, f"Unexpected errors: {result.errors}"
756
757 add_node = result.nodes["&add"]
758 inc_node = result.nodes["&inc"]
759
760 # IRAM offsets: &add is dyadic -> offset 0, &inc is monadic -> offset 1
761 assert add_node.iram_offset == 0
762 assert inc_node.iram_offset == 1
763
764 # Both in root scope -> act_id 0
765 assert add_node.act_id == 0
766 assert inc_node.act_id == 0
767
768 # &add has 1 outgoing edge, no const -> INHERIT, False, 1
769 assert add_node.mode == (OutputStyle.INHERIT, False, 1)
770 # &inc has 0 outgoing edges, no const -> SINK, False, 0
771 assert inc_node.mode == (OutputStyle.SINK, False, 0)
772
773 # Frame layouts assigned
774 assert add_node.frame_layout is not None
775 assert inc_node.frame_layout is not None
776
777 # Frefs: both in same activation, &add needs 1 dest slot, &inc needs 0
778 assert add_node.fref is not None
779 assert inc_node.fref is not None
780
781 # &add dest_l resolves to &inc
782 assert add_node.dest_l is not None
783 assert add_node.dest_l.frame_dest is not None
784 assert add_node.dest_l.frame_dest.target_pe == 0
785 assert add_node.dest_l.frame_dest.offset == 1
786 assert add_node.dest_l.frame_dest.act_id == 0
787 assert add_node.dest_l.frame_dest.port == Port.L
788 assert add_node.dest_l.frame_dest.token_kind == TokenKind.MONADIC