A positional element of an array or slice literal (e.g. the elements of
[]T{a, b}) previously created a 'lit-index' node holding its implicit
index constant (0, 1, ...), in addition to the 'lit-init' write node.
Array and slice content flow is index-insensitive (the store step
ignores the element index), map literals always use explicit keys, and
literal bases are not tracked by VariableWithFields, so the implicit
index value is never actually consumed. Drop the 'lit-index' node and
let the lit-init instruction act as its own opaque index. All 100
data-flow library tests pass unchanged, confirming no content-flow loss.
A named result variable previously created two additional nodes:
'result-zero-init:i' computing the zero value, and 'result-init:i'
writing that value into the result variable. The result-init node
existed only to write the value already computed by result-zero-init.
Make the result-zero-init instruction perform the write itself
(InitResultInstruction is now keyed on result-zero-init with its own
value as RHS) and drop the separate result-init node. All zero-value
constant semantics stay on the same node, so GVN and constant analysis
are unchanged; the only data-flow change is the SSA def relabeling
(result-init -> result-zero-init) with no flow loss.
Each parameter previously created two additional nodes: an 'arg:i'
node (ReadArgumentInstruction) holding the incoming argument value, and
a 'param-init:i' node (InitParameterInstruction) writing it to the
parameter's SSA variable. The 'arg' node existed only to be the RHS of
the param-init write; the ParameterNode and inter-procedural flow are
already anchored on param-init.
Make InitParameterInstruction its own RHS and drop the 'arg:i' node,
halving the per-parameter node count. DeadStoreOfLocal now excludes
InitParameterInstruction (instead of ReadArgumentInstruction). The
data-flow changes are pure relabelings (arg -> param-init) with no flow
loss.
A slice expression with omitted bounds (e.g. `s[:]`, `s[a:]`, `s[:b]`)
previously created up to three synthetic control-flow nodes: implicit-low
(constant 0), implicit-high (length) and implicit-max (capacity). These
carried no data-flow value beyond the implicit lower bound of 0.
Stop emitting these nodes: control flow now skips directly to the next
present bound (or the slice evaluation). SliceInstruction.getLow/getHigh/
getMax return only explicit bounds. The one consumer of the implicit
lower bound, StringOps' HasPrefix_Substring, now treats an absent lower
bound as 0. No data-flow or StringOps results change.
A compound assignment (`x += y`) previously produced two sequential
control-flow nodes: a 'compound-rhs' node computing the binary operation
`x + y`, followed by an 'assign:0' write node storing the result.
Make the compound-rhs instruction itself perform the write (it becomes a
WriteInstruction whose right-hand side is its own value), and stop
emitting the separate assign:0 node for compound assignments. This saves
one CFG node per compound assignment while preserving the BinaryOperationNode
model (so string-concatenation taint through `s += ...` still holds) and
SSA semantics (the def value is the binary operation).
Increment/decrement statements previously produced three control-flow
nodes: an 'implicit-one' node for the constant 1, an 'incdec-rhs' node
for the 'operand + 1' value, and the write node. The implicit-one node
existed only to serve as the right operand of the increment's binary
operation model.
Remove the implicit-one node, modelling the implicit constant 1 directly
on the incdec-rhs instruction. This saves one CFG node per inc/dec. The
inc/dec disjunct of BinaryOperationNode is dropped as well; it only fed
string-concatenation taint (impossible for ++/--) and GVN of a synthetic
node, so no data-flow results are lost.
Category 1 — GVN named-result zero-init:
Named result variables now get an explicit result-zero-init:0 node (the migrated CFG models result vars as zero-initialized in the prologue). Test passes; intended modeling.
Category 2 — ParenExpr (...) missingInNodeForPostOrInOrder.
postOrInOrder starts with n instanceof Go::ReferenceExpr, and ReferenceExpr includes the this.(ParenExpr).getExpr() instanceof ReferenceExpr disjunct, so parenthesized reference expressions are correctly treated pre-order and excluded. Accepted.
Category 3 — select multipleSuccessors/deadEnd:
Same pattern already accepted in the committed ControlFlowGraph consistency file.
Category 4 — defer-invoke / after-block multipleSuccessors:
A return/after-block node flows both to Normal Exit and into the deferred-call chain. Already accepted in committed ControlFlowGraph and experimental/InconsistentCode.
- An empty select statement is correctly identified as a dead end.
- Select statements have multiple successors.
- Return statements have multiple successors when there are conditionally called defer statements.
This occurs as a direct consequence of the CFG migration.
IR::EvalInstruction now deliberately creates a value-producing
instruction for a NotExpr (or LogicalBinaryExpr) from its after-node,
but only when the expression is not in a boolean conditional contex. !d
here sits in a const initializer, not an if/for/switch condition, so
isInBooleanCondContext is false and the after-node becomes an
EvalInstruction — surfacing as the After !... data-flow node. That node
reports getBoolValue() = true and isPlatformIndependentConstant(), so
GVN can match it to the true literal.
On main, a NotExpr in this position produced no such value node (the old
hand-written CFG split the ! evaluation across branch outcomes rather
than exposing a single combined value node), so there was no
DataFlow::Node for !d to be numbered, and hence no row. The new design
intentionally re-introduces a single combined value node in
non-conditional contexts, which is exactly what makes this (correct) GVN
equivalence visible.
So: the line is right, and it's expected fallout from the migration's
handling of !/&&/|| value nodes.
Model `defer`ed calls so the call runs at function exit rather than inline
at the `defer` statement, reproducing the previous control-flow semantics:
- Add a per-defer "defer-invoke" node for the deferred call.
- deferExitStep wires normal-exit predecessors (return nodes and body
fall-through) through the active deferred-call invocations in
last-in-first-out order, then on to the normal exit target (the
result-read epilogue for named results, or the normal exit node).
- The chain is reachability-gated using the defer-free successor relation
(succIgnoringDeferExit / isInOrderNode), so only deferred calls that were
actually registered on a path are run on that path.
- overridesCallableBodyExit / overridesCallableEndAbruptCompletion suppress
the default body-exit and return routing for functions containing
`defer`, so the epilogue is interposed instead.
Add opt-in InputSig2 predicates to support a function-exit epilogue whose
placement depends on reachability (such as Go's deferred calls, run at
exit in last-in-first-out order):
- deferExitStep: language-provided exit-epilogue edges, wired into
explicitStep but excluded from the defer-free reachability.
- overridesCallableBodyExit: suppresses the default fall-through edge from
a body's "after" node to the normal exit, so the epilogue can be
interposed on fall-through paths.
To let a language compute the reachability gate for those edges without
observing them (and without a non-monotonic cycle through reachable):
- explicitStep is split into explicitStepCommon (defer-free) plus
deferExitStep, and defaultCfg now negates explicitStepCommon so default
evaluation does not depend on deferExitStep.
- succIgnoringDeferExit exposes the defer-free successor relation, typed
over PreControlFlowNode so it does not depend on reachable.
- getASuccessorIgnoringDeferredExit exposes the same relation as a
ControlFlowNode member for general use.
- isInOrderNode exposes a structural, reachability-free node-identity test
for use inside the negations a language needs when computing its gate.
- EntryNodeImpl is no longer private, so a language can identify the entry
node over PreControlFlowNode.
All InputSig2 additions default to none(), leaving other languages
unchanged.