summaryrefslogtreecommitdiff
path: root/chapter25
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25')
-rw-r--r--chapter25/README.ja.md4
-rw-r--r--chapter25/README.md75
-rw-r--r--chapter25/ROADMAP.md39
-rw-r--r--chapter25/initialization.md718
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java102
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java100
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java585
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java56
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java126
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java60
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java63
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java52
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java49
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java57
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java112
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java134
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Node.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java13
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java13
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java25
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/BulkMemPhiTest.java40
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java32
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java22
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java22
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java40
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java46
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java34
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java26
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java14
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java30
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java42
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java94
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java40
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java6
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java27
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java18
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java61
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java296
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Eval2.java18
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Main.java2
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java4
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/PhaseTest.java64
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/Simple.java8
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java20
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/codegen/FlagSpillTestSupport.java141
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java16
-rw-r--r--chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java2
88 files changed, 2102 insertions, 1777 deletions
diff --git a/chapter25/README.ja.md b/chapter25/README.ja.md
index 989161eb..b2ef41fb 100644
--- a/chapter25/README.ja.md
+++ b/chapter25/README.ja.md
@@ -74,7 +74,7 @@ return new Box(new i64[1]);
宣言で割り当て補助関数を生成します。これは割り当てを行い、隠れた `<init>` とユーザーコンストラクタを順に呼び、`EscapeNode` を通じて初期化済みの非公開メモリを公開します。パーサーの `Var` メタデータは、早すぎるフィールド読み取りやコンストラクタ出口での未初期化を検出します。Store の幅は保存する値ではなく、対象の宣言から得ます。
-ファイルレベルのコードは、インスタンス初期化とは異なるクラス初期化子(`<clinit>`)で表します。[遅延初期化の設計(英語)](initialization.md) は、一度だけ実行するためのチェック、モジュール間の非循環な依存関係、シリアライズの対応を提案しています。現在の実装すべてを記述する仕様ではありません。[ロードマップ](ROADMAP.ja.md) にも追加の提案があります。
+ファイルレベルのコードは、インスタンス初期化とは異なるクラス初期化子(`<clinit>`)で表します。遅延初期化と循環検査については[英語版の説明](README.md#lazy-class-initialization)を参照してください。今後の計画は[英語版ロードマップ](ROADMAP.md#class-initialization-and-compiler-follow-ups)にあります。
## システムライブラリと例
@@ -98,7 +98,7 @@ return new Box(new i64[1]);
第21章の隣接コピー転送も引き継ぎます。次の命令だけで使うコピーは、オペランドがコピー元レジスタを受け入れ、命令の結果に結び付けられていなければ除去します。
-以下は、必須の `cs` フィールドに明示的なコンストラクタを与えて保存済み String ソース3件を修正した後の、完全な2026-10-02再実行です。過去の212項目すべてが成功します。これはフィクスチャ調整と最近のコンパイラ修正後の新たな基準値を確立します。以前の監査との割り当て器だけの比較ではありません。[バックポート記録](../docs/chapter-backports.md) に古い部分比較を残しています。
+以下は、必須の `cs` フィールドに明示的なコンストラクタを与えて保存済み String ソース3件を修正した後の、完全な2026-10-02再実行です。過去の212項目すべてが成功します。これはフィクスチャ調整と最近のコンパイラ修正後の新たな基準値を確立します。以前の監査との割り当て器だけの比較ではありません。
`make spill-stats` を実行してください。各行は **第25章のコンパイラ** を使います。最初の5行は以前の212のプログラム/対象項目を、この章の構文とライブラリ構成に適応して保持しています。[フィクスチャノート](src/test/java/com/seaofnodes/simple/spill/README.ja.md) にその調整と測定の制限を記載しています。最適化器のシードは123です。最後の行には、既存シードで記録した `Chapter25Test` の13割り当てと、ドライバのシード456での新規システムライブラリコンパイルを含みます。`sys.o` を再利用して、この大きな処理対象を測定から消してはいけません。この結果は Windows x86-64 のものです。以前の群には RISC-V/ARM SystemV と x86 SystemV も含みます。報告器は対象ごとの合計も表示します。
diff --git a/chapter25/README.md b/chapter25/README.md
index 4029827f..8e51c557 100644
--- a/chapter25/README.md
+++ b/chapter25/README.md
@@ -10,20 +10,6 @@ their types and ideal IR into later compilations, and links native programs
against a separately built system library. It also contains a substantial
parser, type, and memory-SSA redesign to support incomplete information.
-These are the contents of the current snapshot. Independent correctness fixes
-will move to earlier chapters first; moving the larger SSA redesign backward,
-or splitting this chapter, is deferred. See the [backport queue](../docs/chapter-backports.md).
-
-The memory work from Chapters 21-24 carries forward here: Load search proves
-all arms of a merge before moving a read out of a loop, Phi factoring checks
-memory clobbers, and scheduling follows aliases through memory aggregates.
-The search follows `EscapeNode` to private or public memory and waits for
-unresolved Store aliases. Array-length reads have no element bounds check;
-array-element reads retain control. Integer value Phis use the existing typed
-conversion machinery to preserve the load's width across loop widening.
-New still produces private constructor memory, with publication handled by
-Escape; the earlier chapters' allocation-input design does not replace it.
-
You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter25)
in the [linear history](https://github.com/SeaOfNodes/Simple/tree/linear) and
[compare it with Chapter 24](https://github.com/SeaOfNodes/Simple/compare/linear-chapter24...linear-chapter25).
@@ -107,9 +93,21 @@ optimize, and validate the graph.
- Guards carry branch-proven zero/nonzero facts before the input family is known.
`FunPtrNode` retains an explicit edge to its function's return graph.
-The driver runs parsing, pessimistic iteration, SCCP, and final type checking
-before serialization and machine selection. Nontrivial inlining is coordinated
-with iteration instead of always occurring as an immediate peephole.
+The driver separates optimization from lowering:
+
+```text
+Parse -> Iter -> Opto -> IterPostOpto -> FinalizeIR -> TypeCheck
+ -> Serialize (when producing an object) -> Lower -> LoopTree
+ -> Unlink -> Select -> Schedule -> LocalSched -> RegAlloc -> Encoding
+```
+
+`Opto` runs SCCP and resolves numeric modes. `IterPostOpto` applies peepholes
+and inlining using those results; `FinalizeIR` settles field layouts and offsets.
+Type checking sees the optimized ideal graph. Serialization saves that graph
+before lowering introduces implementation details such as initialization guards.
+For machine compilation, `Unlink` detaches imported function bodies before
+instruction selection; calls retain the symbols needed for linking. Ideal-IR
+evaluation keeps those bodies available and skips machine selection.
## Memory and construction
@@ -134,11 +132,46 @@ memory through `EscapeNode`s. Parser `Var` metadata detects early field reads
and missing initialization on constructor exits. Store widths come from target
declarations, not from the values being stored.
+## Lazy class initialization
+
File-level code is represented by a class initializer (`<clinit>`), distinct
-from instance initialization. The [lazy initialization design](initialization.md)
-specifies proposed run-once checks, acyclic dependencies across modules, and
-serialization support. It describes planned work, not all currently implemented
-behavior. The [roadmap](ROADMAP.md) contains additional proposals.
+from instance initialization. An active class-field access ensures that the
+class is initialized. Taking a class reference is passive: it identifies the
+class without running its body. Initialization runs once, on the path that
+first needs it, and a parent initializes before its child.
+
+For example, if `B.smp` computes `value`, then this code initializes B only
+when `take` is true; the second access reuses the initialized class:
+
+```java
+return take ? B.value + B.value : 0;
+```
+
+The compiler represents the obligation with an opaque `CLInit` node. It carries
+control, whole-memory effects, the class pointer, and a load of the hidden
+`$init` flag. Its results include a memory slice where that flag is known true.
+Ordinary memory SSA and load simplification can therefore remove repeated
+checks, including after a merge when every incoming path has initialized the
+class. Calls conservatively account for arbitrary memory effects.
+
+After optimization, the compiler builds a graph containing function calls,
+class touches, and parent requirements. A strongly connected component with
+more than one class initializer is an error. Ordinary recursion and helper
+cycles involving just one initializer are allowed. Checking after optimization
+means eliminated branches and call targets do not create false dependencies.
+Diagnostics show the cyclic edges and their source locations, including calls
+through helpers and imported code.
+
+Object files preserve opaque initialization nodes and their source locations.
+Imported ideal bodies participate in the combined cycle check before lowering.
+Lower then expands each remaining obligation into a flag test, a conditional
+initializer call, and control/memory merges. The initializer owns the flag store;
+the store is at body entry to support permitted same-class reentry. Cross-class
+cycles have already been rejected. This is a single-threaded protocol.
+
+The selected entry class receives the current integer `arg`; other initializers
+receive zero. Planned argument arrays and further initialization optimizations
+are described in the [roadmap](ROADMAP.md).
## System library and examples
diff --git a/chapter25/ROADMAP.md b/chapter25/ROADMAP.md
index 749a1614..966bf8e9 100644
--- a/chapter25/ROADMAP.md
+++ b/chapter25/ROADMAP.md
@@ -462,3 +462,42 @@ Here:
- Then it evaluates `z++`, which is false (0).
- Since `&&` requires all conditions to be true, the final result `g` is 0.
- All operands up to the false one are evaluated, so `x`, `y`, and `z` are all incremented.
+
+## Class initialization and compiler follow-ups
+
+### Entry arguments
+
+Replace the integer initializer argument with an argument array. The selected
+entry class receives a non-null array, empty when there are no arguments;
+other class initializers receive null. Coordinate the parser, entry signature,
+runtime driver, and separately compiled interfaces.
+
+### Pre-initialize classes from ELF data
+
+Prove when a class's entire initialization can be represented by ELF static
+data and relocations, with no remaining runtime effects. Emit its initial
+contents and start its `$init` flag as true, so no initializer call is needed.
+Carry that fact through separate compilation so importing compilers can remove
+checks too. A constant field alone is not proof that the whole class can be
+pre-initialized; I/O, argument-dependent work, and other observable effects
+still require execution. Preserve any dynamic parent requirement, and do not
+let cyclic dependencies justify their own pre-initialization.
+
+Mutable fields may have static initial contents without becoming constants.
+Choose read-only storage only when no runtime writes remain. Static reference
+cycles can use relocations without requiring a dynamic initialization cycle.
+
+### CFG frequency estimates
+
+After lowering and loop-tree construction, estimate block frequencies for GCM
+and code layout. Treat initialization and known-failure paths as rare; use
+bounded loop-trip estimates and branch probabilities when no profile is
+available. Keep estimates consistent with control flow and shared between
+scheduling and layout. These are backend estimates, not ideal-IR types.
+
+### Constant instance-field storage
+
+When a final instance field has the same constant value in every instance,
+fold accesses or store the value on the class object and omit the instance
+slot. Preserve field-access semantics and public function reachability:
+externally accessible function fields remain callable regardless of storage.
diff --git a/chapter25/initialization.md b/chapter25/initialization.md
deleted file mode 100644
index 30b1589c..00000000
--- a/chapter25/initialization.md
+++ /dev/null
@@ -1,718 +0,0 @@
-# Lazy class initialization design
-
-Design and implementation plan, revised 2026-10-10. Checkpoint 1 implements
-post-Opto initialization dependencies and cycle checking for review. Declared
-constant final fields already have static-data lowering. General lazy run-once
-initialization, serialized dependency validation, and initialization guards
-remain to be implemented; the compiler does not yet enforce the full protocol.
-
-Initialization metadata belongs with class identity and compilation-unit
-ownership, not solely on the `<clinit>` FunNode. Represent active touches in
-ideal IR, validate an acyclic graph across source and imported objects, and
-lower remaining checks to a hidden flag in the class object and a conditional
-call. The initializer itself owns the store that sets the flag.
-Use one runtime bit in the first, single-threaded implementation.
-
-## Language rules
-
-- Dynamic class initialization runs once, on the first active touch. A class
- never actively touched executes no initialization code.
-- A dynamic parent completes before a dynamic child starts. There is no early
- publication point or special tail-call escape from this rule.
-- Outside the initializer's own checked initialization context, no code may
- observe fields before their required initialization completes.
-- Class identity exists before initialization. Taking a class reference, as
- in `sys`'s `val io = io`, is passive and does not execute the child's body.
- Storing a function identity is also distinct from invoking it.
-- Proven static-data accesses require no dynamic work. This does not authorize
- a called function to observe unfinished dynamic state in an ancestor or any
- other class.
-- Initialization requirements form a DAG across the complete linked program,
- including module boundaries. Count only requirements surviving Opto and its
- call-graph analysis. Reject cycles behind conditionals that remain unresolved;
- eliminated branches and call targets contribute no edges. Ordinary recursive
- functions are still legal.
-- Initialization order follows actual execution and parent requirements, not
- parser discovery order, worklist seed, or filesystem enumeration order.
-
-For example, A conditionally reading `B.foo`, with B initializing `foo` from
-`A.bar`, is rejected if that read survives Opto. Neither an initial zero nor an early evaluation of
-`A.bar` resolves that cycle. If B is A's child, the parent requirement already
-provides the reverse dependency.
-
-An initializer may read its own previously initialized fields under the existing
-definite-initialization rules. That is not an ordinary external active touch.
-Do not infer permission for arbitrary callbacks or same-class helper calls from
-the fact that their owner is currently being initialized. Such helpers need a
-proof that they do not observe unfinished state; otherwise reject their use.
-
-## Ownership and identity
-
-Keep `CompUnit._deps` for source/object discovery and rebuild dependencies.
-Do not reuse it as the initialization graph: passive references belong there,
-and its elements are files rather than necessarily individual classes.
-
-Use CompUnit directly for the current runtime initialization graph. The parser
-creates a file `<clinit>` in `parseStruct(true, ...)`; nested struct declarations
-use `parseStruct(false, ...)` for instance initialization. Do not introduce a
-separate ClassInit object or invent a dynamic class initializer for those
-instance constructors.
-
-Reuse `_cname` for stable identity, `_par` for the filesystem parent, `_clz` for
-the canonical class object and hidden field, and existing function identities
-for the body. Add only initialization-specific information:
-
-| Member | Meaning |
-|---|---|
-| `_classInitDeps` | Lazily allocated `Ary<CompUnit>` of direct initialization requirements |
-| `_initComplete` | All initializer touches and relevant call targets have resolved |
-| `_initStatic` | Proven that this unit's own initializer needs no execution |
-
-Keep diagnostic witnesses in the checker's temporary function summaries: source
-locations and indirect call witnesses where applicable. CompUnit retains only
-the edges and analysis flags after validation. Derive the flag alias from `_clz`'s
-`$init` field rather than maintaining a duplicate mapping. Locate the body by
-its stable function identity; a transient FunNode cache may be useful but does
-not define class identity. An unloaded or eliminated FunNode is not proof of
-static initialization.
-
-Static class objects for nested struct declarations keep their existing named
-TypeStruct identity and defining CompUnit. Their materialized data need no new
-runtime graph vertex; an active use that requires the enclosing file initialized
-maps to its CompUnit. Preserve initialization-independent static accesses. If a
-future language feature adds an independent dynamic initializer inside one file,
-then introduce the additional per-class graph identity that feature needs.
-
-`CompUnit.addClassInitDep(code, target)` allocates the collection lazily, deduplicates
-edges, and invalidates a compilation-wide validation stamp. Keep self-edges so
-they can be diagnosed. Explicit namespace-only parents have no dynamic body;
-a missing parent definition must not silently become an empty initializer.
-
-There are two different dataflows. Runtime `$init` values use ordinary memory
-aliases and support flag-load/CLInitNode optimization. Dependency discovery
-records which initializers may be requested, independently of incoming flag
-values and normal return. A dedicated analysis-only memory slice could encode
-that second forward dataflow, but the first implementation uses explicit
-CompUnit edges plus small function summaries. It does not add a second family
-of memory aliases, and it does not infer cycles from runtime `$init` types.
-
-## Discovering and validating dependencies
-
-Use the convention `A -> B` to mean that initializing A may require B completed.
-Topological output therefore places prerequisites before dependents. A dynamic
-child has a structural dependency on its parent. An active touch of another
-class during A's initializer contributes an edge; a passive reference does not.
-
-Insert active touches as CLInitNodes on executable control. After Opto finishes
-SCCP, inlining, and cleanup, collect surviving touches and calls. Use final
-function-pointer target sets and propagate initialization effects through the
-resulting call graph with a worklist. Recursive function SCCs reach a fixed
-point of finite dependency sets; they are not themselves initialization cycles.
-Indirect calls contribute the union of targets still possible after optimization.
-
-Do not accumulate historical targets or source touches eliminated by Opto.
-There is no side table watching Node replacements or type changes. An inlined
-helper's surviving CLInitNodes belong to its caller; an uninlined helper's
-summary propagates through its surviving calls. Walk executable control
-forwards so touches before nonreturning calls and infinite loops are included.
-Preserve initializer boundaries until validation; expanding/inlining CLInit
-operations early must not erase the class identity needed for checking.
-
-A field value becoming constant does not prove its initializer unnecessary.
-CLInitNode survives independently of the load. Dead control may remove both;
-a static-initialization proof may remove the obligation. Such proofs must not
-assume the very acyclicity being checked.
-
-Acceptance now depends on what Opto proves. Worklist determinism remains an
-implementation goal, but preserving every intermediate target set is not the
-language rule. Before declaring a graph complete, every surviving required
-initialization-time call must have a complete effect summary.
-
-An unknown function-pointer target or native callback is not an empty summary.
-Reject such a call during initialization unless an explicit trusted contract
-establishes its initialization effects, including that it cannot call back into
-unfinished Simple initialization. Ordinary native I/O can use such a contract;
-this is independent of whether the call has memory or I/O effects.
-
-Proving a class entirely static is a separate, deterministic classification
-step. Static object/reference cycles are legal and need no dynamic ordering.
-Dropping a dynamic requirement requires proof that the relevant operation needs
-no initialization, not merely that its guard or result optimized away. A real
-cycle must not bootstrap its own static-ready proof.
-
-Validate the union of parent edges and active initialization requirements after
-the source/import closure is resolved and again after loading or replacing
-summaries. Report a cycle with class names and the source/call witnesses for
-its edges. An incomplete library may retain explicitly unresolved obligations;
-it cannot be certified as a complete runnable program.
-
-Use a stable name order to break ties in diagnostic/topological output. Cache
-module-local results, but validate cross-module edges at final composition.
-One module's list is not necessarily a contiguous block: `X.A -> Y.B -> X.C`
-in prerequisite-first order requires interleaving X and Y. Retain edges as the
-authority rather than serializing a local order as a global proof.
-
-The topological order validates permitted completion order; it is not a startup
-schedule. If A conditionally uses independent B, B initializes only on the
-taken path. A may start before B and finish after it. That is safe precisely
-because B cannot directly or indirectly require unfinished A. Do not eagerly
-initialize every possible dependency, or a prefix of the topological list.
-
-## Ideal IR and runtime protocol
-
-Introduce an effectful `CLInitNode` with a stable target CompUnit identity:
-
-```text
-CLInit(C, ctrl, memory) -> (ctrl, memory)
-```
-
-Control is input/output slot 0 and memory slot 1. Store the target class as
-semantic metadata or a separate constant input, never in the control/memory
-slots. Include target identity in equality, copying, printing, and serialization.
-Its normal return guarantees the required initialization of C and its ancestors.
-It can execute arbitrary initializer effects, so it is not a pure flag test.
-
-At dynamic class-field reads and writes, required instance creation, and calls
-requiring initialized class state, consult parser flow facts first and insert
-CLInitNode only if the required initialization is not already established.
-A check omitted by a valid dominating initialization proof needs no separate
-historical dependency record: its prerequisite is represented on the covering
-path. Passive Simple class references remain subject to checks at later field accesses.
-Native escape of class references is a separate boundary described below.
-Forward references must preserve the pending check until the target is known.
-
-For `ary.copy(args)`, check the class at the field access:
-
-```text
-CLInit(ary)
-f = load ary.copy
-call f(args)
-```
-
-The ordinary function header, calling convention, and static function-pointer
-field do not change. There is no per-function checked wrapper. Folding the
-field load to a constant function pointer does not remove the preceding
-initialization effect unless that effect is independently redundant. Once the
-checked load yields `f`, it can be passed along and called without repeating
-the check for `ary`. Materializing a function address in static data is not a
-source-level field access. An initializer's raw body remains private compiler
-machinery, not a callable reset operation.
-
-Keep CLInitNode in ideal IR so its peepholes can eliminate or simplify frequent
-checks. Lower a remaining node to ordinary Load/If/Call/Region/Phi IR before
-machine selection, using a shared class-initialization helper where an
-out-of-line guard is useful. The checking code contains no flag store:
-
-```text
-CLInit_C(ctrl, memory):
- (ctrl, memory) = CLInit_parent_if_required(ctrl, memory)
- if load(C.$init, memory):
- return (ctrl, memory)
- return call C.<clinit>(ctrl, memory)
-
-C.<clinit>(ctrl, memory):
- memory = store(C.$init, true, memory)
- ... original initializer body ...
- return (ctrl, memory)
-```
-
-Entry to `<clinit>` already means that its check took the not-initialized path.
-Place the flag store in the initializer, naturally at its entry. This covers
-all its normal exits without replicating a store in each CLInitNode expansion.
-Inlining the body may of course copy its store, just as it copies other body
-instructions. The body must not guard itself through ordinary method-entry
-instrumentation.
-
-There is no requirement to schedule the store last. With a validated acyclic
-initialization graph and one thread, no other user can observe this class's
-flag while its initializer is running. A nonreturning initializer can leave
-the bit set because no permitted observer can resume. The physical flag store
-is not an early publication point: the class's initialization still completes
-only when its body returns. Cycle validation and definite-initialization checks
-must remain independent of that store and of optimizations which expose it.
-
-The first runtime contract is single-threaded initialization, with no recovery
-and retry after initializer failure. The validated DAG rules out recursive
-entry to an unfinished initializer. Thus no runtime `initializing` state is
-needed. Concurrent first touches or recoverable initialization failures require
-an extended protocol; the flag is not a synchronization mechanism.
-
-## Hidden flag and known completion
-
-Add a hidden boolean field `$init` to the ordinary `<clinit>` class object.
-There are few such objects, one per class; no separate allocation, symbol, or
-special bit-packing scheme is needed. Use the existing small-integer field
-representation and normal struct layout/offset machinery. It is a logical bit,
-initially false for a dynamic body, stored true by that body, and inaccessible
-to user writes. Separate importers share it through the existing `C.$class`
-identity and its canonical field layout.
-
-Allocate/remap these aliases during class discovery, before the current
-`freezePublicInterface()` boundary. Hidden flags are not source-visible fields,
-but their memory effects must participate in call summaries and memory merging.
-Keep CLInitNode's complete public-memory effect until a narrower summary is
-proved; tracking only the flag would lose the initializer's field and I/O effects.
-
-There are two different facts:
-
-1. **Static body completion:** `_initStatic` proves that C's own initial contents
- are supplied by data and there are no remaining initializer effects. Its
- `$init` field starts true, and its redundant body/store can disappear.
-2. **Completion at a program point:** normal return from CLInitNode establishes
- readiness in that control/memory state. At a permitted external touch, a
- true flag branch proves completion of C's own body; its ancestors must also
- be known complete before concluding that an ordinary active use is safe.
-
-Inside C's own initializer, the flag can already be true while fields are still
-being initialized. Keep that lexical initialization context and its field-read
-checks; a folded true flag does not grant access to unfinished fields. In
-particular, it must not make a callback or cyclic initialization dependency
-legal. Record and validate those dependencies before suppressing their checks.
-
-Neither fact means mutable fields remain constant. Do not refine their declared
-types to their static initial values. Also, a static-only child with a dynamic
-ancestor is not globally ready for every active use: ordinary ensures still
-establish the ancestor. Passive references and proven initialization-independent
-static accesses need no such ensure, including during the parent's initializer.
-
-## Parser flow and ideal peepholes
-
-CLInitNode will occur frequently. Avoid constructing obvious duplicates in the
-parser, then use normal ideal optimization as more types, call effects, and
-control-flow facts become available. A separate late must-analysis is optional,
-not a prerequisite for removing straightforward redundant checks.
-
-Carry known-initialized class identities with parser control flow, for example
-as a BitSet in ScopeNode keyed by the existing remapped `$init` alias IDs. These
-are path facts, not mutable global properties of CompUnit or its declared
-TypeStruct:
-
-- On an active touch, consult the facts. If C and its required ancestors are
- known ready, omit the CLInitNode. Otherwise insert the obligation in the IR;
- post-Opto analysis collects surviving requirements.
-- Normal return from CLInit(C) adds C and its required ancestors. Do not add
- every possible dependency: a conditional initializer may never touch one.
-- Copy facts on a branch and intersect them at a reachable merge. An abrupt
- or nonreturning path contributes no completion facts to a reachable merge.
-- A loop's first iteration has only entry-path guarantees. Body/backedge facts
- alone cannot suppress its first check or establish readiness after a
- potentially zero-trip loop. Preserve pre-loop facts and refine only with a
- proof covering all relevant incoming paths.
-- A call cannot reset established facts. Add new ones only from a proven normal
- return summary; do not seed arbitrary exported function entries with facts
- learned at one caller. Checks at field-access sites establish their facts.
-
-Give CLInitNode ideal peepholes for these cases:
-
-| Proof | Rewrite |
-|---|---|
-| Fully static class / flag known true at a permitted touch | Remove the class's own check and call; retain any still-required ancestor initialization |
-| Flag known false at this memory/control point | Replace the test with an unconditional initializer call; retain the call's effects and results |
-| Earlier normally returning CLInit of the same class dominates | Forward control/memory through the redundant node |
-| Dominating true flag test covers this path | Remove the covered check, as with redundant if-test reasoning |
-| All reachable arms of a merge establish completion | Fold the repeated check after the merge |
-
-Known false must come from the memory reaching the node, not simply the false
-byte in the original object image. Known true on one branch is not known true
-after a merge with an unchecked branch. Match stable class identity and required
-ancestors, not just a pointer-shaped value or a similar field name.
-
-Use the existing dependency/wakeup mechanism when a node consults a flag value,
-memory state, or another node's proof, so later refinement revisits the check.
-Retain control and whole-memory ordering through the conditional call: removing
-a check must preserve preceding initialization effects, and hoisting a check
-onto an untaken path would violate laziness. In particular, CLInitNode is not
-freely speculatable or an ordinary pure expression suitable for unrestricted GVN.
-
-Parser omission must have an enduring IR justification: earlier effectful
-CLInit/control edges, a proven caller precondition, or static metadata.
-When bodies are inlined or deserialized, those proofs travel with the IR;
-transient parser facts alone cannot justify an unchecked exported body. A
-shared declared type must not be mutated to encode a path-local true flag.
-
-## Static data classification
-
-A class is fully static only when every required initial value is representable
-in data/relocations and no runtime effect remains: no I/O, observable allocation,
-potential failure, or required dynamic dependency. A missing/dead body alone
-does not prove this, and startup arguments are not static inputs.
-
-Ignore the compiler-inserted `$init = true` bookkeeping store when checking
-whether the original initializer is entirely static. Otherwise every inserted
-store would prevent this optimization. Once the proof succeeds, emit `$init`
-as true in the object image and eliminate the runtime store with the body.
-
-Mutable fields may have static initial values. Folding their initializing
-stores must still preserve reads and escapes during the initializer. Merely
-knowing the final value and that the body runs once is insufficient. Preserve
-object identity and canonical layout separately from initial contents, and do
-not collapse distinct class objects with equal bytes.
-
-This extends the current StaticData machinery; it does not change pointer
-widths or require class objects to become read-only. An object with a dynamic
-`$init` field stays writable even if its source fields are final. A fully static
-object whose source fields are also immutable can later use read-only storage,
-with its hidden field already true and no runtime writes to it.
-
-## Serialization and final composition
-
-Add a class-initialization directory to the `.simple` data that can be read
-without materializing every function body. Serialize:
-
-- Stable class and parent names, defining-unit identity, and initializer symbols.
-- Static/dynamic/unknown classification and summary-completeness information.
-- Direct initialization requirements and function-effect summaries, including
- unresolved obligations and useful cycle-diagnostic locations.
-- The hidden `$init` field's type, alias identity, initial image value, and
- canonical layout, alongside the other class fields. Remap aliases using file
- identity and local order rather than copying process-local numbers.
-- CLInitNode operations and the initializer's own flag Store in ideal IR,
- including all semantic fields needed to rebuild them.
-
-Do not serialize transient node references, a cached global topo index, or the
-runtime value of a dynamic flag as an unconditional type fact. A definition
-and its imported declarations must resolve to the same owning CompUnit and
-canonical class/flag identity.
-
-On load, register identities first, resolve parent/dependency edges second,
-then resolve function summaries and IR. Check conflicting owners, missing
-definitions, alias remapping, summary completeness, and combined cycles.
-Loaded bodies must agree with their summaries; report newly found requirements
-and revalidate rather than trusting an earlier module-local topo sort.
-
-Preserve these summaries before `unlinkImports()` removes imported bodies.
-Serialize ideal CLInitNode operations before lowering to machine code. Native
-objects retain checks at their class-field access sites even when a particular
-importing compiler inlines and removes its local checks. Ordinary function
-symbols and function-pointer constants are not replaced by checked wrappers.
-
-Do not serialize a parser's transient BitSet as unconditional entry knowledge.
-Its local alias IDs can change on reload; retained checks, control flow, and
-explicit entry contracts must support any omitted checks. Recompute flow facts
-for loaded graphs and verify hidden-field ownership/layout and that the flag
-store belongs to `<clinit>`, not to each checking caller.
-
-Final validation needs a Simple-aware executable/library composition step.
-An ordinary native linker cannot validate dependencies stored only in `.simple`.
-Enumerate the complete intended Simple object set, including exported entry
-points that native code could invoke; loading only the source-discovered
-references is insufficient. Validate that metadata set before handing objects
-to GCC/the native linker. Dynamically loading additional Simple modules is
-outside this first protocol and would require validation before activation.
-
-Keep the current unversioned `C0DE` convention and require rebuilding objects
-when the serialized layout changes. Retain/validate summaries for the exact
-objects being linked, not cached descriptions of older builds.
-
-## Program entry and other boundaries
-
-Program startup is another CLInitNode on the starting class. In a fresh runtime
-image with a dynamic starting class, its `$init` is known false, so the check
-folds to the initial `<clinit>` call. The starting class sets its own bit just
-like every other class; there is no adapter-specific flag store or protocol.
-A proven fully static starting class needs no dynamic initialization call.
-
-The current file `<clinit>` can receive `arg` and return a program/test result.
-Preserve those arguments and the call result on the known-first-call startup
-path. Ordinary class-use checks need only control and memory; they do not need
-to cache or invent a result for the skip path. For fully static startup, the
-program result must also be available as a constant or retained computation;
-static object contents alone do not prove the entry computation unnecessary.
-Existing tests executing file code with different arguments use independent
-runtime images, not a reset of an already initialized class. The startup fact
-must not be reused for an arbitrary native entry into a live image.
-
-Own-initializer helper calls and construction of a special instance during
-class initialization need an explicit definite-initialization proof. Do not
-carry forward the old module notes' unrestricted “golden instance” exception.
-Initially reject cases that would publish or inspect unfinished class state.
-
-## Native handoff
-
-Native callers are not required to understand or explicitly run Simple class
-initialization. The compiler/runtime must establish initialization before
-exposing a supported value, or reject an unsupported handoff.
-
-For the first implementation, reject passing a class object or raw `<clinit>`
-function to native code, including through a class-to-address conversion.
-Do not confuse these with an ordinary function pointer obtained by a checked
-field access: loading `ary.copy` already initializes `ary`, so that value does
-not need a new initialization wrapper when handed to native code, wherever the
-ordinary FFI supports its signature. Initialization-time callbacks still need
-the effect analysis described above; an uninitialized class cannot be hidden
-behind a callback to evade cycle validation.
-
-Later, support a selected class value at the handoff point:
-
-```text
-X = rand ? B : C // Passive class references
-CLInit(selected class X) // Initialize the actual value being passed
-libc.native(X)
-```
-
-Validate the initialization graph for all possible targets, B and C, including
-their parents and transitive requirements, regardless of the selected value.
-At runtime initialize only the selected target before the native call. This
-can lower to checks on the corresponding control-flow arms, or dispatch on a
-known target identity. The plan's initial CLInitNode has a fixed class identity;
-this dynamic selection is a later extension, not an assumed existing facility.
-
-Initializing every possible target before handoff is an alternative policy,
-but it runs effects for the unselected class. Prefer the selected-target form
-to retain the rule that untouched classes run no code. Unknown target sets or
-native traversal to further uninitialized class objects require additional
-analysis; initializing just X is not a blanket proof for everything reachable
-through X. Until supported, reject the handoff rather than transferring that
-responsibility to native code.
-
-A native call directly to an exported symbol, without a preceding Simple
-handoff, likewise needs compiler/runtime initialization support. Its eventual
-export/startup mechanism is deferred; it must not require the native caller to
-invoke `<clinit>`. Existing native-linking tests are not evidence that this
-future initialization guarantee is already implemented.
-
-## Implementation sequence
-
-Implement in Chapter 25 only, with the following reviewable checkpoints. Keep
-existing source/native entry conventions and pointer widths. Each checkpoint
-must build; do not claim the runtime guarantee until the executable protocol,
-serialization, and final-composition validation are all present.
-
-### 1. CompUnit dependencies and cycle checking
-
-Reviewed (2026-10-10). Files: `CompUnit.java`, `ParseAll.java`,
-`Parser.java`, `ClassInitDependencies.java`, `CLInitNode.java`, `CallEndNode.java`,
-`Opto.java`, and `CodeGen.java`. Generic `Node` needs no changes.
-
-- CompUnit holds direct `_classInitDeps`, `_initComplete`,
- and reserved `_initStatic`. Existing file-discovery `_deps` is unchanged.
- Diagnostic witnesses belong to the dependency checker and serialized summaries,
- not CompUnit.
-- Parser inserts a control-flow CLInitNode for an active class-field touch or
- a receiver whose class is not yet resolved. Ordinary instance receivers
- fold the obligation away when resolved. Passive references and direct
- own-initializer field accesses do not insert a check.
-- This checkpoint's ideal node has control at input 0 and receiver at input 1;
- it neither consumes nor produces memory yet. The full runtime form described
- above will add memory at slot 1 and move the receiver to slot 2. No hidden
- flag or runtime initialization call is generated in this checkpoint.
-- After Opto, the collector walks live control forwards in each surviving
- function, reads final call-target sets, and propagates sparse may-touch
- summaries. Ordinary helper inlining naturally relocates its checks into the
- caller. Class initializers are not inlined before validation.
-- Reachable nonreturning calls retain a conservative CallEnd during dataflow.
- Calls left out-of-line retain their continuations, so otherwise unreachable
- touches after them can contribute initialization dependencies. This extra
- ordering is accepted; users can remove such unreachable code.
- Inlining discovers bodies forwards from their entry, including non-exiting
- paths, and explicitly retains the Return node even when disconnected from
- executable control. Nonreturning functions can therefore inline normally.
-- TypeCheck builds fresh CompUnit requirements from those summaries and parent
- edges. It rejects cycles with source/call witnesses and computes a stable
- prerequisite-first order. No source-history or Node watcher table is kept.
-- Following validation and serialization, the temporary CLInit obligations are
- erased for the existing backend. Executable lowering remains a later
- checkpoint. The full lazy-initialization runtime guarantee is not implemented.
-
-Unknown native effects and unresolved targets leave `_initComplete`
-false; imported summaries are now available in checkpoint 2. The returned order
-covers known edges and is not a completeness
-certificate. `_initStatic` remains false until static classification is added.
-
-Validation covers direct, helper-mediated, forward, conditional, indirect,
-sibling, and parent/child cycles; legal ordinary recursion and passive
-references; unknown native/imported effects; dependencies eliminated by SCCP;
-unused helpers; discarded field values; and effects before versus after a
-nonreturning call. Cycle and optimized-target cases run with three worklist
-seeds. A bounded Eval2 run verifies that a retained nonreturning call executes
-rather than falling through; its x86, ARM, and RISC-V encoding checks also pass.
-All 492 tests in the parallel Chapter 25 Make suite pass. The encoding checks
-were added afterward and pass in the focused suite. Two existing RISC-V spill
-expectations change from 9 to 7; execution and allocation checks pass.
-Generated fixtures are under
-`chapter25/build/init-deps/`; full-suite logs are under `build/init-deps-opto*.log`.
-
-### 2. Serializable flags and dependency summaries
-
-Implemented for review (2026-10-10). Files: `Parser.java`, `Serialize.java`,
-`ElfReader.java`, `ParseAll.java`, `ClassInitDependencies.java`, `CodeGen.java`,
-and `CLInitNode.java`. Existing `GlobalBits` and `StaticData` mechanisms handle
-alias/function remapping and the zero flag image without special cases.
-
-- Add hidden `$init` fields with the normal field/alias/layout mechanism before
- `freezePublicInterface()`. Declared flag type is boolean; initial image value
- is false unless static initialization has been proved. Do not make a dynamic
- flag's declared type the constant false.
-- Serialize per-unit initialization metadata and function may-touch summaries
- in `.simple`, early enough for graph validation without loading every body.
- Preserve stable owner/alias/function identities and diagnostic witnesses.
-- Register imported units first, resolve/remap edges and hidden fields second,
- then validate the combined graph. Neither a module-local topological order
- nor a summary from an older object file is a final-link proof.
-- Keep metadata for fully static or body-eliminated units. Rebuild objects
- under the existing unversioned `C0DE` convention; add no compatibility layer.
-
-Checkpoint: a two-module cycle rejected from source is also rejected after
-serialization; an acyclic diamond resolves to one shared class and flag.
-Two local topo orders can be merged even when their modules must interleave.
-
-The object header carries owned class identities, initialization edges,
-completeness/static flags, and per-function may-touch summaries with diagnostic
-witnesses. Imports use those summaries even when their initializer bodies are
-not live in the importing compilation. Class layouts survive body elimination.
-Each `ClassCyclicError` witness holds existing owner/field name references, a
-numeric line/column, and a shared suffix for indirect calls; a null field denotes
-a call. Diagnostic text is formatted only when a cycle is rejected. Serialization
-stores shared witnesses once and preserves numeric locations without requiring
-the original source text.
-Ideal CLInitNodes also survive serialization, including source positions, so
-inlining an imported helper retains its active touches. Erasure now happens in
-Unlink, after serialization; executable checks still belong to checkpoint 3.
-
-Object lookup distinguishes class definitions from references to other objects.
-Objects serialize their own bodies; imported function pointers retain symbolic
-identities. Loading registers all classes supplied by each object, resolves
-prerequisites against existing source/imported units, and selects the requested
-nested class's type rather than always the object's root class. Cross-object
-calls are relinked after all bodies load, before Iter validates saved types.
-
-Validation covers separately compiled cyclic objects, a shared diamond across
-three worklist seeds, interleaving orders from two multi-class objects, imported
-helpers with and without inlining, type-only loading with stable flag aliases,
-and the emitted zero flag byte. The C0DE layout changed: rebuild existing objects.
-The runtime still does not test or set the flag, and static classification remains
-deferred. All 494 tests pass in the Chapter 25 Make suite. Logs:
-`build/init-checkpoint2-focused.log` and
-`build/init-checkpoint2-all.log`.
-
-### 3. CLInitNode and the executable protocol
-
-Files: new `node/CLInitNode.java`; `Node.java`, `Parser.java`, `CodeGen.java`,
-`Serialize.java`, `Eval2.java`, and the printer adapters as needed.
-
-- Add an effectful, serializable CLInitNode targeting the resolved CompUnit,
- with control/memory inputs and outputs in slots 0/1. Pending targets remain
- explicit until resolved. Its type computation is conservative about the
- initializer's whole memory effects; final normal output guarantees completion.
-- Integrate its possible call with existing Call/CallEnd linking, argument and
- return propagation, and body liveness. A reference to CompUnit metadata alone
- must not let SCCP discard the initializer as uncalled. Reuse the call machinery
- or a generated guard helper, rather than implementing a second independent
- calling convention in the node. Test an out-of-line imported initializer as
- well as an inlined one.
-- Insert checks at class-field accesses, including function-pointer loads;
- use the same operation for required instance creation and startup. Do not
- instrument ordinary function headers or rewrite their function-pointer data.
-- Insert `$init = true` once in each dynamic `<clinit>` body, naturally at entry.
- The checking node performs the test/conditional call and contains no store.
- A false startup test becomes a direct call preserving `arg` and the result.
-- Give Eval2 the same shared-object/flag behavior. Lower checks to existing
- control, memory, and call IR for machine compilation; use an out-of-line
- helper if necessary rather than adding target-specific machine operations.
-- Reject the presently unsupported native class/raw-initializer handoffs. Keep
- ordinary checked-field function pointers unchanged. Do not add an explicit
- initialization obligation to C drivers or other native callers. Direct native
- entry into a dynamic class remains outside the new guarantee until a
- compiler-managed startup/export mechanism exists.
-
-Checkpoint: sequential touches run the body once; parent effects precede child
-initialization; an untaken branch runs no child code. Verify result/argument
-preservation and flag sharing in Eval2, ARM/RISC emulation, and native execution
-through Simple startup. Existing direct native tests must not be mistaken for
-coverage of the deferred dynamic-export case.
-
-### 4. Parser flow and ideal simplification
-
-Files: `ScopeNode.java`, `Parser.java`, `CLInitNode.java`, and existing
-control/memory lookup helpers.
-
-- Add path-local known-initialized facts to ScopeNode. Copy in `dup`, intersect
- reachable predecessors in merges, and respect entry/backedge distinctions in
- loop completion, break, continue, and return handling. The established
- control/memory path must justify each check omitted by the parser.
-- Implement constant-true, constant-false, preceding dominating CLInit, and
- dominating true-test peepholes. Use dependencies to revisit proofs after SCCP
- or inlining sharpens values. Keep remaining parent requirements when only a
- child's own body is static.
-- Preserve initialization effects when folding field loads into function
- constants. Never hoist a check onto a path that previously did not touch the
- class. Do not confuse an early true flag inside `<clinit>` with permission
- to read that initializer's unfinished fields.
-- Add fully-static classification using the initializer body and its effects,
- excluding the generated flag store from the test. Emit a true initial flag
- only after proof; remove the now-redundant body/store/checks. Extend mutable
- constant-field lowering only where observations during initialization remain
- correct. Do not use classification to erase evidence of a genuine cycle.
-
-Checkpoint: distinguish a dominating check from a one-arm-only check, a
-zero-trip loop from a definitely executed touch, and a static child from an
-unfinished dynamic parent. Check that early flag folding cannot hide a cycle.
-
-### 5. Phase ordering and final composition
-
-Files: `CodeGen.java`, `ParseAll.java`, `Serialize.java`, `ElfReader.java`,
-`Main.java`, and the native linking path in `TestC.java` as appropriate.
-
-The current driver runs parse, Iter, Opto, TypeCheck, LoopTree,
-`unlinkImports`, serialization, then selection and scheduling. Add explicit
-initialization-analysis boundaries without relying on a particular peephole
-worklist order:
-
-1. During parse/load, register identities and represent active initialization
- obligations in ideal IR. Ordinary Opto resolves targets and removes dead
- control; do not collect a historical union of temporary types or calls.
-2. After Opto finishes, collect surviving CLInitNodes and calls, complete
- function summaries, classify provably static work without circular
- assumptions, and validate the graph. Any remaining unknown required
- initialization effects prevent certification.
-3. Before `unlinkImports`, retain imported summaries needed for validation and
- serialization. Serialize ideal CLInitNodes and their proof-bearing control
- and memory edges, plus the independent requirements.
-4. Lower remaining CLInitNodes before final loop-tree construction and machine
- selection. Because lowering introduces branches/calls, rebuild affected
- control information and run ordinary ideal/type validation on that graph.
- Move the final LoopTree step if needed rather than using a stale tree from
- before expansion. Keep the serialized representation ideal and reloadable.
- Ensure the generated call participates in call linking before the final
- unlink step; imported bodies may remain represented by symbolic targets.
-5. Before native executable composition, load metadata for the exact full
- Simple object set being linked, merge requirements, and validate again.
- A plain GCC/ELF link is not an initialization-graph checker. Do the same
- validation for a complete in-memory image.
-
-If optimization or loading discovers another target, mark summaries dirty and
-rerun validation before emission. No runtime `initializing` state is introduced;
-compile-time DFS bookkeeping is independent of the single runtime flag.
-
-Checkpoint: source, imported ideal IR, and final linked metadata agree on
-acceptance/rejection for equivalent optimized graphs. Exercise different
-worklist seeds and object-discovery orders to check determinism; eliminated
-branches and targets must not leave stale dependencies. Missing required
-metadata fails diagnostically.
-
-### Validation and scope
-
-Use a small set of focused fixtures covering several boundaries each, plus the
-existing full Chapter 25 Make suites. Include effects before a nonreturning
-call so dependency checking cannot accidentally depend on returned memory.
-Exercise duplicate touches, conditional cycles, native-handoff diagnostics,
-serialization alias remapping, class/flag ownership, and normal startup results.
-Run the fixed allocation cohorts only after runtime semantics stabilize; graph
-and spill changes are expected, and any new golden counts must follow execution
-and register-correctness checks. Keep Jig tests ignored.
-
-Do not backport this feature, change pointer widths, introduce concurrent or
-recoverable initialization, add dynamic module loading, or solve the general
-native-export policy in these checkpoints. The extra analysis-only alias slice
-remains an alternative representation if explicit summaries prove awkward;
-implementing both would add work without improving the initial guarantee.
-
-## Module goals retained from the old notes
-
-Keep one-line programs easy to compile/run, filesystem-based nested namespaces,
-leading-underscore privacy, separate compilation without source include files,
-and ordinary object-file/native-linker interoperability. Classes may be declared
-within a file or supplied by nested files. Source and object builds must have
-the same initialization semantics. The current CLI is documented in the
-[chapter README](README.md); historical command examples and conflicting
-initializer-order proposals are superseded by this design.
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index 331ff05d..5ceda6a9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -4,6 +4,7 @@ import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.node.ScopeNode.Kind;
import static com.seaofnodes.simple.util.Utils.TODO;
@@ -168,7 +169,7 @@ public class Parser {
public Ary<FRefNode> parse( CompUnit ref ) {
assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
assert _frefs == null;
- _lexer = new Lexer(ref._src);
+ _lexer = new Lexer(ref);
_frefs = new Ary<>(FRefNode.class);
// Starting Scope has control, memory, initial arguments
_scope = new ScopeNode();
@@ -289,6 +290,18 @@ public class Parser {
_scope.define(ids[i], t, (i==0 && ids[0]=="self") || (finals!=null && finals.get(i)), new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
}
+ // The initializer owns its run-once flag store. Parent initialization
+ // is a structural requirement, executed before this body by CLInit lowering.
+ if( fun.isClz() ) {
+ Node self = fun.parm(2);
+ Field flag = ((TypeMemPtr)self._type)._obj.field("$init");
+ Node prior = mem().keep();
+ Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self,
+ fldoff(self,"$init"),con(1),false));
+ storeMem(st,flag._alias,prior);
+ prior.unkeep();
+ }
+
// Parse the function body.
Node last=_code.ZERO; // Last statement as the default result
@@ -1235,9 +1248,12 @@ public class Parser {
? Field.make(initName,sig,_code.alias(_ref._cname),true)
: oldFld.makeFrom(sig);
Field ctorFld = ts==null ? null : ts.field("<ctor>");
+ // A same-named struct shares its file's class object and flag.
+ Field flag = ts==null ? null : ts.field("$init");
ts = ctorFld==null
? TypeStruct.make(clzName,false,fld)
: TypeStruct.make(clzName,false,fld,ctorFld);
+ if( flag!=null ) ts=ts.add(flag);
TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true));
TYPES.put(clzName,ts);
@@ -2231,6 +2247,8 @@ public class Parser {
return parsePostfix(expr);
}
private Node parsePostfix(Node expr) {
+ _lexer.skipWhiteSpace();
+ Lexer site = loc();
String name;
if( match(".") ) name = requireId();
else if( match("#") ) name = "#";
@@ -2240,7 +2258,7 @@ public class Parser {
else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")"));
else return expr; // No postfix
- return parsePostfixName(expr,name,0);
+ return parsePostfixName(expr,name,0,site);
}
private Node defaultSelf() {
@@ -2261,6 +2279,10 @@ public class Parser {
// pre is +/-1 for a prefix update, otherwise parse a postfix operation.
private Node parsePostfixName(Node expr, String name, int pre) {
+ return parsePostfixName(expr,name,pre,loc());
+ }
+
+ private Node parsePostfixName(Node expr, String name, int pre, Lexer site) {
// Keep expr across possible updates
expr.keep();
@@ -2308,7 +2330,7 @@ public class Parser {
Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) &&
!(ts!=null && (ts._name+".<clinit>").equals(func._name)) )
- ctrl(new CLInitNode(loc(),ctrl(),expr).peephole());
+ classInit(site,expr);
// With no field, we will update the bulk memory
int alias = fld==null ? 1 : fld._alias;
@@ -2473,6 +2495,8 @@ public class Parser {
* </pre>
*/
private Node functionCall(Node fcn, Node self) {
+ Lexer site = loc();
+ site._position--; // Opening '('; arguments can contain nested calls.
if( fcn._type == Type.NIL )
throw error("Calling a null function pointer");
fcn.keep(); // Keep while parsing args
@@ -2495,10 +2519,14 @@ public class Parser {
// Unkeep them all
for( Node arg : args )
arg.unkeep();
- return functionCall(args);
+ return functionCall(args,site);
}
private Node functionCall( Ary<Node> args ) {
+ return functionCall(args,loc());
+ }
+
+ private Node functionCall( Ary<Node> args, Lexer site ) {
// Dead into the call? Skip all the node gen
if( ctrl()._type == Type.XCONTROL ) {
for( Node arg : args )
@@ -2508,7 +2536,7 @@ public class Parser {
}
// Into the call
- CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+ CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole();
// Post-call setup
CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
call.peephole(); // Rerun peeps after CallEnd, allows early inlining
@@ -2565,6 +2593,22 @@ public class Parser {
private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
ConstantNode con( long con ) { return _code.con(con); }
ConstantNode con( Type t ) { return _code.con(t ); }
+ private void classInit(Lexer site, Node ptr) {
+ // Ordinary load machinery resolves the flag alias and offset when
+ // the receiver is still a forward reference.
+ Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null;
+ int alias = flag==null ? 1 : flag._alias;
+ Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole();
+ CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep();
+ ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole());
+ Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep();
+ flag = init.flag();
+ mem(flag==null ? bulk : mergeAlias(bulk,flag._alias,
+ new ProjNode(init,2,memName(flag._alias)).peephole()));
+ bulk.unkeep();
+ init.unkeep();
+ }
+
// Field offset for a known type (but unknown offset)
public static ConFldOffNode off( TypeStruct ts, String fname ) {
return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
@@ -2694,6 +2738,7 @@ public class Parser {
// Input buffer; an array of text bytes read from a file or a string
private final byte[] _input;
+ public final CompUnit _cu; // Original source, retained across inlining.
// Tracks current position in input buffer
private int _position = 0;
@@ -2704,12 +2749,11 @@ public class Parser {
/** True when this location precedes {@code that} in the same source. */
public boolean before(Lexer that) {
- return that != null && _input == that._input && _position < that._position;
+ return that != null && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position;
}
public String position() { return _line_number+":"+(_position-_line_start+1); }
- // Numeric location for diagnostics that must survive without source text.
- public long lineColumn() { return ((long)_line_number<<32) | (_position-_line_start+1); }
+ public int pos() { return _position; }
/**
* Record the source text for lexing
@@ -2723,6 +2767,45 @@ public class Parser {
*/
public Lexer(byte[] buf) {
_input = buf;
+ _cu = null;
+ }
+
+ private Lexer(CompUnit cu) {
+ _input = cu._src.getBytes();
+ _cu = cu;
+ }
+
+ // Imported location: source text is only needed when reporting an error.
+ private Lexer(CompUnit cu, int pos) {
+ _input = null;
+ _cu = cu;
+ _position = pos;
+ }
+
+ public static void gather(Lexer loc, HashMap<String,Integer> strs) {
+ if( loc==null || loc._cu==null ) return;
+ Serialize.gather(strs,loc._cu._cname);
+ String file=loc._cu.sourceFile();
+ if( file!=null ) Serialize.gather(strs,file);
+ }
+
+ public static void packed(Lexer loc, BAOS out, HashMap<String,Integer> strs) {
+ if( loc==null || loc._cu==null ) { out.packed4(0); return; }
+ out.packed4(loc._position+1);
+ out.packed4(strs.get(loc._cu._cname));
+ String file=loc._cu.sourceFile();
+ out.packed4(file==null ? 0 : strs.get(file));
+ }
+
+ public static Lexer unpack(BAOS in, String[] strs) {
+ int pos=in.packed4();
+ if( pos==0 ) return null;
+ String owner=strs[in.packed4()];
+ String file=strs[in.packed4()];
+ CompUnit cu=CodeGen.CODE._compunits.get(owner.replace('.','/'));
+ assert cu!=null : owner;
+ if( !file.isEmpty() ) cu._sourceFile=file;
+ return new Lexer(cu,pos-1);
}
/**
@@ -2730,6 +2813,7 @@ public class Parser {
*/
private Lexer(Lexer l) {
_input = l._input;
+ _cu = l._cu;
_position = l._position;
_line_number = l._line_number;
_line_start = l._line_start;
@@ -2738,7 +2822,7 @@ public class Parser {
// Very handy in the debugger, shows the unparsed program
@Override
public String toString() {
- return new String(_input, _position, _input.length - _position);
+ return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position);
}
// True if at EOF
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
index 0150219e..6c70b71d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
@@ -1,5 +1,6 @@
package com.seaofnodes.simple.codegen;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.*;
@@ -15,13 +16,11 @@ import java.util.*;
// CompUnits from CLInitNodes, and the set of fcn call targets in f and the
// class parent. These are all reported as fidxs (ints).
//
-// (2) Walk the _edges graph using Tarjans SCC. Stop at cycles containing a
-// <clint> (and continue with cycles NOT containing a <clinit>, which is
-// ordinary recursive functions).
+// (2) Walk the _edges graph using Tarjans SCC. Reject cycles containing more
+// than one <clinit>.
//
// (3) Report on <clinit> cycles
//
-//
public class ClassInitDependencies {
private final CodeGen _code;
// Function FIDX -> callees, touched initializers, and parent initializer.
@@ -35,7 +34,7 @@ public class ClassInitDependencies {
// Build once; tests can inspect the graph independently of cycle checking.
public ClassInitDependencies( CodeGen code ) {
_code = code;
- assert code._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal();
_pres = new int[code._linker._len];
_lows = new int[code._linker._len];
_stk = new int[code._linker._len];
@@ -50,12 +49,13 @@ public class ClassInitDependencies {
for( FunNode fun : code._linker )
if( fun!=null && !fun.isDead() )
// Visit function and collect direct class touches and callees
- _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit);
+ _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null);
visit.clear();
}
// Return the outgoing init edge set for this function
- private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit) {
+ private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit,
+ Ary<Parser.Lexer> sites) {
// Walk function body, looking for outgoing init edges
assert iset .isEmpty(); // int hash set for de-dup
assert visit.isEmpty(); // needs a visit set, because loops
@@ -79,14 +79,17 @@ public class ClassInitDependencies {
if( cfg instanceof CLInitNode init ) {
FunNode clz = code.link(init.cu()); // <clinit> referred to
iset.put(clz.sig().fidx(),1); // Add to edge set
+ if( sites!=null && sites.atX(clz.sig().fidx())==null ) sites.setX(clz.sig().fidx(),init._loc);
}
// Calls union all their fptr fidxs into the callee set
if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) {
assert !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0; // Call is alive with valid targets
// Union call targets into the callee set
- for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) )
+ for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) {
iset.put(dstx,1);
+ if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc);
+ }
}
// Forward recursion to walk the whole function
@@ -106,36 +109,61 @@ public class ClassInitDependencies {
return edges;
}
- // Return an error string, or null for an acyclic class-init graph.
+ // Return an error string, or null if no cycle contains multiple clinits.
public String check() {
+ Arrays.fill(_pres,0);
+ _sp = 0;
_pres[0] = 1; // Preorders stored in pres[0] and start at 1
- BitSet visit = _code.visit();
- int idx = 0;
- for( int fidx = 1; fidx < _edgess.length; fidx++ ) {
- if( _pres[fidx] == 0 ) // Already visited
- idx = scc(fidx);
- if( idx != -1 ) break; // Found a clz cycle
- }
- visit.clear();
+ int idx = -1;
+ for( int fidx = 1; fidx < _edgess.length; fidx++ )
+ if( _pres[fidx] == 0 && // Already visited
+ (idx = scc(fidx)) != -1 ) // And found a cycle
+ return report(idx); // Report cycle
+ return null;
+ }
- // Error report for a clz heading a cycle
- if( idx != -1 ) {
- SB sb = new SB().p("Cyclic class initialization: ");
- String first = null;
- for( int i=idx; i<_sp; i++ ) {
- FunNode fun = _code.link(_stk[i]);
- if( fun.isClz() ) {
- if( first==null ) first = fun._compunit._cname;
- sb.p(fun._compunit._cname).p(" -> ");
- }
- }
- sb.p(first);
- visit.clear();
- return sb.toString();
+ // Every internal edge of an SCC belongs to a cycle. Print all of them:
+ // stack order is not a path, and enumerating all cycle paths can explode.
+ // This bookkeeping and formatting are only needed on an error.
+ private String report(int idx) {
+ BitSet members = new BitSet();
+ for( int i=idx; i<_sp; i++ ) members.set(_stk[i]);
+ SB sb = new SB().p("Cyclic class initialization:");
+ // Recover one surviving source site per edge only after finding an
+ // error. A representative touch is enough; runtime first-touch order
+ // need not be statically known. Lexer snapshots retain the source CU
+ // even when the node has been inlined into a different function.
+ Ary<CFGNode> work = new Ary<>(CFGNode.class);
+ IntHashMap iset = new IntHashMap(0);
+ BitSet visit = new BitSet();
+ Ary<Parser.Lexer> sites = new Ary<>(Parser.Lexer.class);
+ for( int i=idx; i<_sp; i++ ) {
+ int src = _stk[i];
+ FunNode fun = _code.link(src);
+ Arrays.fill(sites._es,null);
+ sites.clear();
+ walk(_code,fun,work,iset,visit,sites);
+ if( _edgess[src]!=null )
+ for( int dst : _edgess[src] )
+ if( members.get(dst) ) {
+ sb.p("\n ").p(name(src)).p(" -> ").p(name(dst));
+ Parser.Lexer loc = sites.atX(dst);
+ if( loc!=null && loc._cu!=null )
+ sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization"));
+ else if( fun.isClz() && fun._compunit._par==_code.link(dst)._compunit )
+ sb.p("\n parent initialization requirement");
+ }
}
+ return sb.toString();
+ }
- return null; // No errors
+ private String name(int fidx) {
+ FunNode fun = _code.link(fidx);
+ String owner=fun._compunit._cname;
+ if( fun.isClz() ) return owner;
+ String label=fun.label();
+ return (label.startsWith(owner+".") ? label : owner+"."+label)+" [fidx "+fidx+"]";
}
// Tarjan SCC
@@ -174,10 +202,12 @@ public class ClassInitDependencies {
private boolean onStk(int e) { return find(e) != -1; }
private boolean hasClzCycle(int idx) {
- if( idx+1 == _sp ) return false; // Cycle of 1 does not count
- for( int i=idx; i<_sp; i++ )
- if( _code.link(_stk[i]).isClz() )
+ int clinits = 0;
+ for( int i=idx; i<_sp; i++ ) {
+ FunNode fun = _code.link(_stk[i]);
+ if( fun!=null && fun.isClz() && ++clinits > 1 )
return true;
+ }
return false;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index ff0be213..47ec0112 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -8,9 +8,6 @@ import com.seaofnodes.simple.print.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.*;
import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.*;
@@ -28,10 +25,13 @@ public class CodeGen {
public enum Phase {
Parse, // Parse ASCII text into Sea-of-Nodes IR
Iter, // Pessimistic peepholes after parse
- Opto, // Run ideal optimizations
+ Opto, // SCCP, numeric modes, seed the peephole worklist
+ IterPostOpto, // Peepholes and inlining using SCCP results
+ FinalizeIR, // Field layout, offset cleanup, function/CU roots
TypeCheck, // Last check for bad programs
+ Serialize, // Save ideal IR, including opaque class initialization
+ Lower, // Expand checked class initialization to ordinary IR
LoopTree, // Build a loop tree; break infinite loops
- Serialize, // Serialize public IR for future compiles to link
Unlink, // Unlink call sites before machine code generation
Select, // Convert to target hardware nodes
Schedule, // Global schedule (code motion) nodes
@@ -97,8 +97,7 @@ public class CodeGen {
return null;
}
- // True when fidx is published through a public field of a public class.
- // TypeStruct.fidxs() already excludes private (underscore) fields.
+ // True when fidx is published through a public class or instance field.
public boolean publicFIDX(int fidx) {
if( fidx < GlobalBits.RESERVED ) return false;
// A public class initializer is itself an external root; unlike
@@ -106,19 +105,21 @@ public class CodeGen {
FunNode fun = _linker.atX(fidx);
if( fun != null && fun.isClz() && fun.isPublic() )
return true;
- // Constructors and methods live on their class objects, including
- // nested classes which do not have their own CompUnit.
+ // Include instance methods even while their constant function fields
+ // are stored in each instance rather than on the class object.
for( Type t : Parser.TYPES.values() )
if( t instanceof TypeStruct clz &&
Parser.startsClzPrefix(clz._name) &&
Parser.isPublicClassName(clz._name) &&
- resolvedClass(clz) &&
- publicClassFIDX(clz,fidx) )
- return true;
+ resolvedClass(clz) ) {
+ if( publicFieldFIDX(clz,fidx) ) return true;
+ Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ if( self instanceof TypeStruct ts && publicFieldFIDX(ts,fidx) ) return true;
+ }
return false;
}
- private static boolean publicClassFIDX(TypeStruct clz, int fidx) {
+ private static boolean publicFieldFIDX(TypeStruct clz, int fidx) {
boolean hasCtor = clz.field("<ctor>") != null;
for( Field fld : clz._fields )
if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp &&
@@ -182,7 +183,7 @@ public class CodeGen {
public final XCtrlNode XCTRL;
public Node constant( Type t ) {
- assert _phase==null || _phase.ordinal() <= Phase.Opto.ordinal();
+ assert _phase==null || _phase.ordinal() <= Phase.Lower.ordinal();
return ConstantNode.make(t).peephole();
}
public ConstantNode con( Type t ) { return (ConstantNode)constant(t); }
@@ -236,7 +237,9 @@ public class CodeGen {
}
- // Run requested phases.
+ // Run requested phases. File export must request Serialize before Lower;
+ // requesting Export up front does that automatically. Encoding alone does
+ // not retain reloadable ideal IR.
// No code emission, just IR generation
public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); }
@@ -253,23 +256,64 @@ public class CodeGen {
// Write an object file for a specific cpu/os pair
public CodeGen driver( String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol ) { return driver(Phase.Export,cpu,callingConv,inMemory,emitEntrySymbol,0); }
- // Generic driver
- public CodeGen driver( Phase phase, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
- int p1 = phase.ordinal(), p2 = _phase==null ? -1 : _phase.ordinal();
- if( p2 < p1 && p2 < Phase.Parse .ordinal() ) { parse(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.Iter .ordinal() ) { iter(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.Opto .ordinal() ) { opto(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.TypeCheck .ordinal() ) { typeCheck(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.LoopTree .ordinal() ) { loopTree(); p2 = dump(dump); }
- if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { unlinkImports(); p2 = dump(dump); }
- if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { serialize(); p2 = dump(dump); } // Include ideal graph in object file
- if( p2 < p1 && p2 < Phase.Unlink .ordinal() ) { unlink(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.Select .ordinal() && cpu != null ) { instSelect(cpu,callingConv); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.Schedule .ordinal() ) { GCM(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.LocalSched.ordinal() ) { localSched();p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.RegAlloc .ordinal() ) { regAlloc(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.Encoding .ordinal() ) { encode(); p2 = dump(dump); }
- if( p2 < p1 && p2 < Phase.Export .ordinal() ) { exportELF(inMemory,emitEntrySymbol); p2 = dump(dump); }
+
+ // The Generic driver
+ public CodeGen driver( Phase target, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
+ // Parse a Simple file, and for mentioned external symbols either load
+ // from .o or recursively parse
+ step(target, Phase.Parse, this::parse, dump);
+ // Run peepholes to a fixed point
+ step(target, Phase.Iter, this::iter, dump);
+ // Run SCCP (and resolve int/flt expressions)
+ step(target, Phase.Opto, this::opto, dump);
+ // Run peepholes to a fixed point again, on the better types.
+ step(target, Phase.IterPostOpto, this::iterPostOpto, dump);
+ // Field layouts; field symbols become constant offsets
+ step(target, Phase.FinalizeIR, this::finalizeIR, dump);
+ // Final error check - bad programs fail here
+ step(target, Phase.TypeCheck, this::typeCheck, dump);
+ // Save reloadable ideal IR for a later seperate compilation
+ step(target, Phase.Serialize, () -> Serialize.forTarget(this,target,inMemory), dump);
+ // Replace "fat" nodes with their exploded explicit IR; this can
+ // introduce CFG diamonds
+ step(target, Phase.Lower, this::lower, dump);
+ // Compute the loop-tree, used for approximating exec frequencies for
+ // scheduling and register allocation
+ step(target, Phase.LoopTree, this::loopTree, dump);
+ // Drop imports for machine selection, retain them for ideal evaluation.
+ step(target, Phase.Unlink, () -> unlink(cpu!=null), dump);
+ // Instruction selection
+ step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump);
+ // Global Code Motion
+ step(target, Phase.Schedule, this::GCM, dump);
+ // Local (basic block only) scheduling
+ step(target, Phase.LocalSched, this::localSched, dump);
+ // Register allocation
+ step(target, Phase.RegAlloc, this::regAlloc, dump);
+ // Encode machine instructions into bits
+ step(target, Phase.Encoding, this::encode, dump);
+ // Export bits (and serialized IR) into ELF files
+ step(target, Phase.Export, () -> exportELF(inMemory,emitEntrySymbol), dump);
+ return this;
+ }
+
+ // The driver names the order; optional phases decide locally whether to run.
+ private void step(Phase target, Phase next, Runnable work, int dump) {
+ if( next.ordinal() > target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return;
+ Phase prior = _phase;
+ work.run();
+ if( _phase!=prior ) dump(dump);
+ }
+
+ // All phase entry points share ordering checks, timing, and viewer notification.
+ // Record elapsed time even on failure, but report completion only on success.
+ CodeGen phase(Phase next, boolean ordered, Runnable work) {
+ assert ordered : "Cannot run "+next+" after "+_phase;
+ _phase = next;
+ long t0 = System.currentTimeMillis();
+ work.run(); // Do The Phase
+ _times[next.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs!=null ) _obs.phase(next.name()); // Record for viewer
return this;
}
@@ -278,17 +322,14 @@ public class CodeGen {
}
// Verbose printing during compilation
- private int dump(int dump) {
+ private void dump(int dump) {
int p2 = _phase.ordinal();
- if( (dump & (1<<p2)) == 0 ) return p2;
-
-
+ if( (dump & (1<<p2)) == 0 ) return;
if( (dump & (1<<30)) != 0 )
System.err.println("After "+_phase+":");
System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal()
? asm(new SB()).toString()
: IRPrinter.prettyPrint(this));
- return p2;
}
@@ -425,25 +466,7 @@ public class CodeGen {
public final Parser P;
// Parse ASCII text into Sea-of-Nodes IR
- public CodeGen parse() {
- assert _phase == null;
- _phase = Phase.Parse;
- long t0 = System.currentTimeMillis();
-
- Parser.TYPES.clear();
- Parser.TYPES.putAll(Parser.INIT_TYPES);
- if( _src != null ) {
- // No source file, just the source itself.
- ParseAll.parseSource(this,_srcName==null ? "Test" : _srcName,_src);
- } else {
- // Path from module root to source file
- ParseAll.parsePath(this,_srcName);
- }
-
- _times[Phase.Parse.ordinal()] = System.currentTimeMillis() - t0;
- if( _obs != null ) _obs.phase(_phase.name());
- return this;
- }
+ public CodeGen parse() { return phase(Phase.Parse, _phase==null, () -> ParseAll.parse(this,_src)); }
// ---------------------------
@@ -459,80 +482,20 @@ public class CodeGen {
// Pessimistic peepholes after parsing. This lifts loaded and parsed
// types before the optimistic Opto pass resets types and lets them fall.
- public CodeGen iter() {
- assert _phase == Phase.Parse;
- _phase = Phase.Iter;
- long t0 = System.currentTimeMillis();
+ public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); }
- // The phase shift closes all parser-in-progress states and freezes
- // unknown external inputs. Imported nodes carry post-Opto escape
- // precision, but Iter is a fresh pessimistic solve: memory escape
- // summaries must restart at FULL so newly discovered private escapes
- // can only be removed, never added. Seed the complete graph once.
- boolean hasImports = _externPaths != null;
- _start.walk(n -> {
- if( !(n instanceof ConstantNode) )
- n._type = hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type);
- add(n);
- return null;
- });
- _iter.iterate(this);
- // Expensive assert.
- assert !expensiveAssert() || Opto.fixedPointCheck(this);
+ // SCCP and numeric-mode resolution; leave cleanup work queued for Iter.
+ public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); }
- _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0;
- if( _obs != null ) _obs.phase(_phase.name());
- return this;
- }
+ public CodeGen iterPostOpto() { return phase(Phase.IterPostOpto, _phase==Phase.Opto, () -> _iter.iterate(this)); }
- private static Type iterEscapeReset(Type t) {
- if( t instanceof TypeMem mem )
- return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
- if( t instanceof TypeTuple tt ) {
- TypeTuple rez = tt;
- for( int i=0; i<tt._types.length; i++ ) {
- Type tx = iterEscapeReset(tt._types[i]);
- if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
- }
- return rez;
- }
- return t;
- }
+ public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); }
- private static Type iterImportReset(Type t) {
- if( t.isHigh() )
- return t.dual();
- if( t instanceof TypeMem mem )
- return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL);
- if( t instanceof TypeMemPtr )
- return t.makeStorage();
- if( t instanceof TypeStruct )
- return t.makeStorage();
- if( t instanceof TypeTuple tt ) {
- TypeTuple rez = tt;
- for( int i=0; i<tt._types.length; i++ ) {
- Type tx = iterImportReset(tt._types[i]);
- if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
- }
- return rez;
- }
- return t;
- }
-
-
- // Run ideal optimizations
- public CodeGen opto() {
- if( _phase == Phase.Parse )
- iter();
- assert _phase == Phase.Iter;
- _phase = Phase.Opto;
- long t0 = System.currentTimeMillis();
-
- Opto.opto(this);
-
- _times[Phase.Opto.ordinal()] = System.currentTimeMillis() - t0;
- if( _obs != null ) _obs.phase(_phase.name());
- return this;
+ // Root a surviving function without claiming that it has unknown callers.
+ void keepInCompUnit(FunNode fun) {
+ StopCUNode stop = fun._compunit._stop;
+ if( stop._inputs.find(fun.ret()) == -1 )
+ add(stop).addDef(fun.ret());
}
public <N extends Node> N add( N n ) { return _iter.add(n); }
public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
@@ -540,60 +503,7 @@ public class CodeGen {
// ---------------------------
// Last check for bad programs
- public CodeGen typeCheck() {
- // Demand phase Opto for cleaning up dead control flow at least,
- // required for the following GCM.
- assert _phase.ordinal() <= Phase.Opto.ordinal();
- _phase = Phase.TypeCheck;
- long t0 = System.currentTimeMillis();
-
- // Check for class-init cycles first; such a cycle can prevent other
- // things from resolving types. The dependency graph is temporary.
- String clzInitCycle = new ClassInitDependencies(this).check();
- if( clzInitCycle != null )
- throw Parser.error(clzInitCycle,null);
-
- Ary<Node> errs = new Ary<>(Node.class);
- _stop.walk( n -> {
- if( n.err() != null )
- errs.add(n);
- return null;
- });
- _times[Phase.TypeCheck.ordinal()] = System.currentTimeMillis() - t0;
-
- if( !errs.isEmpty() ) {
- // Prefer an originating error over a secondary error caused by a
- // BOTTOM input. If all errors are downstream-poisoned, retain
- // the old first-error behavior.
- Node min = null;
- for( Node n : errs )
- if( noBottomInputs(n) )
- { min = n; break; }
- if( min==null )
- min = errs.at(0);
- for( Node n : errs )
- if( n!=min &&
- noBottomInputs(n) &&
- ((n instanceof MemOpNode nmop &&
- min instanceof MemOpNode mmop &&
- nmop._loc.before(mmop._loc)) ||
- (n instanceof CFGNode ncfg &&
- min instanceof CFGNode mincfg &&
- ncfg.idepth() < mincfg.idepth())) )
- min = n;
- if( _obs != null ) _obs.error(min, min.err().getMessage());
- throw min.err();
- }
- if( _obs != null ) _obs.phase("TypeCheck");
- return this;
- }
-
- private static boolean noBottomInputs(Node n) {
- for( Node def : n._inputs )
- if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM )
- return false;
- return true;
- }
+ public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); }
public static TypeStruct requiredStruct(Node n) {
// CNC: remove? cleanup?
@@ -604,62 +514,21 @@ public class CodeGen {
}
// ---------------------------
+ // TypeCheck queued surviving CLInits during its existing error walk.
+ public CodeGen lower() {
+ return phase(Phase.Lower, (_phase==Phase.TypeCheck || _phase==Phase.Serialize), () -> _iter.iterate(this));
+ }
+
+ // ---------------------------
// Build the loop tree; break never-exit loops
public CodeGen loopTree() {
- assert _phase.ordinal() <= Phase.Serialize.ordinal();
- _phase = Phase.LoopTree;
- long t0 = System.currentTimeMillis();
- // Build the loop tree, fix never-exit loops
- _start.buildLoopTree( _linker, _stop);
- // Invariant theory: No more type or code changes after Opto. The
- // LoopTree stuff is used to do layouts now, but not core opts. TODO:
- // at some future date we'll do loop opts (e.g. peeling, unrolling)
- // that will totally impact code shape, types and core opts - but this
- // will be done in tandem with Opto changes.
- //_iter.iterate(this);
- _times[Phase.LoopTree.ordinal()] = System.currentTimeMillis() - t0;
- return this;
+ return phase(Phase.LoopTree, _phase==Phase.Lower, () -> _start.buildLoopTree(_linker,_stop));
}
// ---------------------------
public BAOS _serial;
public void serialize() {
- assert _phase.ordinal() <= Phase.Serialize.ordinal();
- _phase = Phase.Serialize;
- long t0 = System.currentTimeMillis();
- // Does not change compiler phase; just records IR
- Serialize.serialize(this);
- _times[Phase.Serialize.ordinal()] = System.currentTimeMillis() - t0;
- }
-
- // Unlink imported function bodies before serialization. Calls to them
- // remain as normal calls through their function pointer, and codegen later
- // turns them into external relocations.
- private void unlinkImports() {
- assert _phase.ordinal() <= Phase.Serialize.ordinal();
- // Imported function constants keep their symbolic identity, not an
- // embedded copy of the other object's Return and body.
- for( Node n : _start._outputs.asAry() )
- if( n instanceof FunPtrNode ptr && ptr.ret()!=null && !owns(ptr.fun()) ) {
- ptr.liftType(ptr._type);
- ptr.setDef(1,null);
- }
- for( FunNode fun : _linker ) {
- if( fun==null || fun.isDead() || owns(fun) )
- continue;
- for( int i=1; i<fun.nIns(); i++ )
- if( fun.in(i) instanceof CallNode call )
- call.unlink(fun,i--);
- }
- for( int i=0; i<_stop.nIns(); i++ ) {
- StopCUNode stop=(StopCUNode)_stop.in(i);
- CompUnit cu=_compunits.get(stop.start()._fname);
- if( cu._src==null ) _stop.delDef(i--);
- }
- // The whole-object wrappers now summarize only the emitted units.
- _stop._type=_stop.compute();
- _start._type=_start.compute();
- //_iter.iterate(this);
+ phase(Phase.Serialize, _phase==Phase.TypeCheck, () -> Serialize.serialize(this));
}
// ---------------------------
@@ -685,208 +554,15 @@ public class CodeGen {
// Convert to target hardware nodes
public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); }
public CodeGen instSelect( String cpu, String callingConv, String base ) {
- assert _phase.ordinal() <= Phase.Unlink.ordinal();
- _phase = Phase.Select;
-
- _callingConv = callingConv;
-
- // Not timing the class load...
- // Look for CPU in fixed named place:
- // com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
- String clzFile = base+"."+cpu+"."+cpu;
- try { _mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(this); }
- catch( Exception e ) { throw new RuntimeException(e); }
-
- _cCallingConv = _mach.cCallingConv(callingConv);
-
- // Build global copies of common register masks.
- long callerSave = _mach.callerSave();
- long cCallerSave= _mach.callerSave(_cCallingConv);
- long neverSave = _mach. neverSave();
- int maxReg = Math.min(64,_mach.regs().length);
- assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave
- assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave
- _callerSave = new RegMask(callerSave);
- _cCallerSave= new RegMask(cCallerSave);
-
- // Build a Return RegMask array. All returns have the following inputs:
- // 0 - ctrl
- // 1 - memory
- // 2 - varies with returning GPR,FPR
- // 3 - RPC
- // 4+- Caller save registers
- _retMasks = new RegMask[(maxReg-_callerSave.size()-Long.bitCount( neverSave ))+4];
- for( int reg=0, i=4; reg<maxReg; reg++ )
- if( !_callerSave.test(reg) && ((1L<<reg)&neverSave)==0 )
- _retMasks[i++] = new RegMask(reg);
- _rpcMask = new RegMask(_mach.rpc());
- _retMasks[3] = _rpcMask;
-
- // Convert to machine ops
- long t0 = System.currentTimeMillis();
- _uid = 1; // All new machine nodes reset numbering
- var map = new IdentityHashMap<Node,Node>();
- _instSelect( _stop, map );
- _stop = (StopNode)map.get(_stop );
- StartNode start = (StartNode)map.get(_start);
- _start = start==null ? new StartNode(_start) : start;
- _instOuts(_stop,visit());
- _visit.clear();
-
- // Replace the CompUnit stop (and list of functions)
- // with hardware-specific ones
- for( CompUnit cu : _compunits.values() ) {
- cu._start= (StartCUNode)map.get(cu._start);
- cu._stop = (StopCUNode )map.get(cu._stop );
- }
-
- _times[Phase.Select.ordinal()] = System.currentTimeMillis() - t0;
- return this;
- }
-
- // Walk all ideal nodes, recursively mapping ideal to machine nodes, then
- // make a machine node for "this".
- private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
- if( n==null ) return null;
- if( n instanceof StoreNode st && StaticData.preinitialized(this,st) )
- return _instSelect(st.mem(),map);
- Node x = map.get(n);
- if( x !=null ) return x; // Been there, done that
-
- // If n is a MachNode already, then its part of a multi-node expansion.
- // It does not need instruction selection (already selected!)
- // but it does need its inputs walked.
- if( n instanceof MachNode ) {
- for( int i=0; i < n.nIns(); i++ )
- n._inputs.set(i, _instSelect(n.in(i),map) );
- return n;
- }
-
- // Produce a machine node from n; map it to flag as done so stops cycles.
- if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
- // External fields replace base+offset with symbol+0. Keep the ideal
- // declaring type on the address for machine load/store width selection.
- TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
- n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
- n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
- }
- map.put(n, x=_mach.instSelect(n) );
- // Carry loop-tree and pre-order info across the ideal->mach transition
- if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) {
- xcfg._ltree = ncfg._ltree;
- xcfg._pre = ncfg._pre;
- }
- // Walk machine op and replace inputs with mapped inputs
- for( int i=0; i < x.nIns(); i++ )
- x._inputs.set(i, _instSelect(x.in(i),map) );
- // Post selection action
- if( x instanceof MachNode mach ) {
- if( n instanceof ReturnNode ret )
- ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun);
- mach.postSelect(this); // Post selection action
- }
-
- return x;
- }
-
-
- // Walk all machine Nodes, and set their output edges
- private void _instOuts( Node n, BitSet visit ) {
- if( visit.get(n._nid) ) return;
- visit.set(n._nid);
- for( Node in : n._inputs )
- if( in!=null ) {
- in._outputs.push(n);
- // CallNode special: outputs are partially ordered; CallEnd in slot 0
- if( in instanceof CallNode call && n instanceof CallEndNode cend && call.out(0) != cend )
- call._outputs.swap(0,call.nOuts()-1);
- _instOuts(in,visit);
- }
+ return InstSelect.select(this,cpu,callingConv,base);
}
// ---------------------------
public void unlink() {
- assert _phase.ordinal() <= Phase.Select.ordinal();
- _phase = Phase.Unlink;
- long t0 = System.currentTimeMillis();
-
- // The remaining passes assume all calls are unlinked; i.e. we are throwing
- // away the Call Graph here. Functions only reachable from internal calls
- // need to be re-hooked to stop/start less they go dead.
- for( FunNode fun : _linker ) {
- // Already linked to start, not going dead
- if( fun==null || fun.isDead() )
- continue;
-
- // Imported functions are not emitted by this object. Unlink any
- // concrete calls so they become normal external relocations.
- if( !owns(fun) ) {
- while( fun.nIns() > 1 ) {
- if( fun.in(1) instanceof CallNode call )
- call.unlink(fun,1);
- else
- fun.removeDeadPath(1);
- }
- StartCUNode start = fun._compunit._start;
- StopCUNode stop = fun._compunit._stop;
- // Unhook the Stop->Return edge also
- ReturnNode ret = fun.ret();
- int idx = stop._inputs.find(ret);
- if( idx != -1 ) {
- stop._inputs.del(idx);
- ret.delUse(stop);
- }
- //
- if( stop.nIns()==0 ) {
- idx = _stop._inputs.find(stop);
- if( idx != -1 )
- _stop.delDef(idx);
- if( start.nIns() > 0 && start.in(0) != null )
- start.setDef(0,null);
- if( start.nIns() > 1 && start.in(1) != null )
- start.setDef(1,null);
- }
- continue;
- }
-
- // Insert the post-Opto code-generation hook. Unlike an Opto-time
- // Start input, this no longer carries unknown-caller semantics.
- ReturnNode ret = fun.ret();
- StartNode start = fun._compunit._start;
- assert start!=null;
- if( fun.nIns() <= 1 || fun.in(1) != start ) {
- fun.insertDef(1,start);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode parm )
- parm.insertDef(1,ConstantNode.raw(parm._type));
- if( fun._compunit._stop._inputs.find(ret) == -1 )
- fun._compunit._stop.addDef(ret);
- }
- // Unlink from Call
- for( int i=2; i<fun.nIns(); i++ )
- ((CallNode)fun.in(i)).unlink(fun,i--);
- if( fun.rpc()==null ) { // Ensure valid RPC
- // First make sure a valid RPC; single-call into multi-fun will have each
- // function having a single call site, so the RPC becomes a constant and
- // folds - but since multiple targets, the Call never inlines. Recreate a
- // valid RPC so codegen (and Eval2) understands the calling convention.
- ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun );
- rpc.addDef(ret.rpc());
- ret.setDef( 3, rpc.init() );
- }
- }
-
- _stop.walk(n -> {
- if( n instanceof CallEndNode cend )
- for( int i=1; i<cend.nIns(); i++ )
- if( cend.in(i) instanceof ReturnNode )
- cend.delDef(i--);
- if( n instanceof CallNode call )
- call.unlink_all();
- return null;
- });
-
- _times[Phase.Unlink.ordinal()] = System.currentTimeMillis() - t0;
+ unlink(true);
+ }
+ private void unlink(boolean removeImports) {
+ phase(Phase.Unlink, _phase==Phase.LoopTree, () -> Unlink.run(this,removeImports));
}
// ---------------------------
@@ -895,39 +571,22 @@ public class CodeGen {
// Global schedule (code motion) nodes
public CodeGen GCM() {
- assert _phase.ordinal() <= Phase.Select.ordinal();
- _phase = Phase.Schedule;
- long t0 = System.currentTimeMillis();
-
- GlobalCodeMotion.buildCFG(this);
- _times[Phase.Schedule.ordinal()] = System.currentTimeMillis() - t0;
-
- return this;
+ return phase(Phase.Schedule, (_phase==Phase.Unlink || _phase==Phase.Select), () -> GlobalCodeMotion.buildCFG(this));
}
// ---------------------------
// Local (basic block) scheduler phase, a classic list scheduler
- public CodeGen localSched() {
- assert _phase == Phase.Schedule;
- _phase = Phase.LocalSched;
- long t0 = System.currentTimeMillis();
- ListScheduler.sched(this);
- _times[Phase.LocalSched.ordinal()] = System.currentTimeMillis() - t0;
- return this;
- }
+ public CodeGen localSched() { return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); }
// ---------------------------
// Register Allocation
public RegAlloc _regAlloc;
public CodeGen regAlloc() {
- assert _phase == Phase.LocalSched;
- _phase = Phase.RegAlloc;
- long t0 = System.currentTimeMillis();
- _regAlloc = new RegAlloc(this);
- _regAlloc.regAlloc();
- _times[Phase.RegAlloc.ordinal()] = System.currentTimeMillis() - t0;
- return this;
+ return phase(Phase.RegAlloc, _phase==Phase.LocalSched, () -> {
+ _regAlloc = new RegAlloc(this);
+ _regAlloc.regAlloc();
+ });
}
// Human-readable register name
@@ -943,32 +602,16 @@ public class CodeGen {
// ---------------------------
// Encoding
public Encoding _encoding;
- public CodeGen encode() {
- assert _phase == Phase.RegAlloc;
- _phase = Phase.Encoding;
- long t0 = System.currentTimeMillis();
-
- _encoding = new Encoding(this).encode();
-
- _times[Phase.Encoding.ordinal()] = System.currentTimeMillis() - t0;
- return this;
- }
+ public CodeGen encode() { return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> _encoding = new Encoding(this).encode()); }
// ---------------------------
// Exporting to external formats
ElfWriter _elf;
public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) {
- assert _phase == Phase.Encoding;
- _phase = Phase.Export;
- long t0 = System.currentTimeMillis();
- if( _encoding!=null ) {
- if( inMemory )
- new LinkMem(this).link(_encoding); // In memory patching
- else
- _elf = new ElfWriter(this).export(emitEntrySymbol);
- }
- _times[Phase.Export.ordinal()] = System.currentTimeMillis() - t0;
- return this;
+ return phase(Phase.Export, _phase==Phase.Encoding, () -> {
+ if( inMemory ) new LinkMem(this).link(_encoding);
+ else _elf = new ElfWriter(this).export(emitEntrySymbol);
+ });
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
index 544ca27e..128c4136 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -24,6 +24,13 @@ public class CompUnit {
final File _smp; // Simple source file, e.g. module_root/A/B/C.smp
public final File _obj; // ELF output file, e.g. build/A/B/C.o
final public String _src; // Source code, from file or test case
+ // Original source path from an imported diagnostic location. Does not
+ // affect discovery/rebuild decisions, which use _smp and _src.
+ public String _sourceFile;
+
+ public String sourceFile() {
+ return _smp!=null ? _smp.getAbsolutePath() : _sourceFile;
+ }
public TypeStruct _clz; // The one TypeStruct being published
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
index 1b6281b8..b3eac583 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -282,6 +282,8 @@ public class ElfWriter {
// ------------------------------------------------------------------------
public ElfWriter export(boolean emitEntrySymbol) {
+ if( _code._serial==null )
+ throw new IllegalStateException("ELF export needs ideal IR: request Export from the driver, or Serialize before Lower");
// Sections are created in the order they are emitted.
_sections = new Ary<>(Section.class);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
new file mode 100644
index 00000000..923486c6
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
@@ -0,0 +1,44 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import java.util.Arrays;
+
+// Comnpute field layouts only after SCCP and its peepholes converge.
+// Keep surviving functions rooted without introducing unknown callers.
+abstract class FinalizeIR {
+ static void run(CodeGen code) {
+ // Compute field layouts and replace symbolic field offsets by constants.
+ for( int i=0; i<code._start.nOuts(); i++ ) {
+ Node use = code._start.out(i);
+ if( use instanceof ConFldOffNode off ) {
+ Node con = off.asOffset();
+ assert con != null : off._ts._name+"."+off._fname;
+ off.subsume(con);
+ i--;
+ }
+ }
+ code._iter.iterate(code); // Propagate field offsets and clean up.
+
+ // Retain surviving functions for emission without adding unknown callers.
+ for( FunNode fun : code._linker )
+ if( fun != null ) {
+ // A folded static function field can leave a detached body
+ // with internal cycles. It has no callers; later phases must
+ // not revive it merely because its nodes are still allocated.
+ if( fun.isDead() || fun._type.isHigh() ) {
+ code._linker.set(fun.sig().fidx(),null);
+ continue;
+ }
+ code.keepInCompUnit(fun);
+ }
+ for( CompUnit cu : code._compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem != null ) code.add(mem.unkeep());
+ }
+ // Stable print order: initializers first, then other Returns by NID.
+ for( Node stop : code._stop._inputs )
+ Arrays.sort(stop._inputs._es,0,stop.nIns(),
+ (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
+ (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 8eebc15b..09eb8d33 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -60,7 +60,7 @@ public abstract class GlobalCodeMotion {
if( use==null || globals.containsKey(use) ) continue;
FunNode fun = useFun(use);
if( fun==null ) continue; // Global metadata has no function owner.
- assert CodeGen.CODE.owns(fun);
+ assert CodeGen.CODE._mach==null || CodeGen.CODE.owns(fun); // Ideal evaluation includes imported bodies.
var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>());
Node copy = cloneGlobal(con,fun,globals,local);
for( int i=0; i<use.nIns(); i++ )
@@ -142,8 +142,9 @@ public abstract class GlobalCodeMotion {
// Record Load NIDs at all their CFG block choices, then check against
// Store block choices to force a Load above an anti-dependent Store.
int[] anti = new int[code.UID()];
+ Ary<Node> antideps = new Ary<>(Node.class);
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti);
+ breadth(code._stop,ns,late,anti, antideps);
// Copy the best placement choice into the control slot
for( int i=0; i<late.length; i++ )
@@ -151,7 +152,7 @@ public abstract class GlobalCodeMotion {
ns[i].setDef(0,late[i]);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// Things on the worklist have some (but perhaps not all) uses done.
WorkList<Node> work = new WorkList<>();
work.push(stop);
@@ -172,12 +173,12 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof MemOpNode ld && ld._isLoad )
- for( Node memuse : ld.antiDeps() )
+ for( Node memuse : ld.antiDeps(antideps) )
if( late[memuse._nid]==null )
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti);
+ _doSchedLate(n,ns,late,anti,antideps);
}
// A use just finished; reconsider its inputs and waiting loads,
@@ -194,7 +195,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
+ private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -205,7 +206,7 @@ public abstract class GlobalCodeMotion {
// Loads may need anti-dependencies, raising their LCA
if( n instanceof MemOpNode load && load._isLoad )
- lca = find_anti_dep(lca,load,early,late,anti);
+ lca = find_anti_dep(lca,load,early,late,anti, antideps);
// Walk up from the LCA to the early, looking for best place. This is
// the lowest execution frequency, approximated by least loop depth and
@@ -256,34 +257,26 @@ public abstract class GlobalCodeMotion {
if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
}
- private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
+ private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
- for( Node mem : load.antiDeps() ) {
- switch( mem ) {
- case MemOpNode st:
- assert late[st._nid]!=null;
- lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
- break;
- case EscapeNode st:
- assert late[st._nid]!=null;
- lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
- break;
- case CallNode call:
- assert late[call._nid]!=null;
- lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call,anti);
- break;
- case PhiNode phi:
- // Repeat anti-dep for matching Phi inputs.
- // No anti-dep edges but may raise the LCA.
+ for( Node mem : load.antiDeps(antideps) ) {
+ if( mem instanceof PhiNode phi ) {
+ // Inputs carrying different memory might overwrite this alias.
+ // Raise the LCA for those paths; the unchanged-memory paths
+ // do not clobber the load. No scheduling edge to the Phi.
for( int i=1; i<phi.nIns(); i++ )
- if( phi.in(i)==load.mem() )
+ if( phi.in(i)!=load.mem() )
lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti);
- break;
- default: throw Utils.TODO("Should not reach here");
+ } else {
+ // Hunt for clobbering stores
+ assert mem instanceof MemOpNode || mem instanceof EscapeNode ||
+ mem instanceof CallNode || mem instanceof CLInitNode;
+ assert late[mem._nid]!=null;
+ lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti);
}
}
return lca;
@@ -293,12 +286,15 @@ public abstract class GlobalCodeMotion {
private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) {
// A conditional writer can precede the load without dominating its late block.
// Walk back to the first overlap with the load's placement range.
- for( ; stblk != defblk.idom(); stblk = stblk.idom() ) {
+ // A different Phi input need not be dominated by this memory's
+ // definition. Stop at its depth rather than looking for that ancestor.
+ // Retain the sentinel too: a function entry has a null idom.
+ for( ; stblk != defblk.idom() && stblk.idepth() >= defblk.idepth(); stblk = stblk.idom() ) {
// Store and Load overlap, need anti-dependence
if( anti[stblk._nid]==load._nid ) {
lca = stblk.domLCA(lca,null); // Raise Loads LCA
if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved,
- st.addDef(load); // Add anti-dep as well; Calls already end their block.
+ st.addDef(load); // Add anti-dep as well (calls already end their block)
return lca; // Cap this stores' anti-dep to here
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
new file mode 100644
index 00000000..5fc002a1
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
@@ -0,0 +1,126 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import java.util.BitSet;
+import java.util.IdentityHashMap;
+
+// Replace ideal nodes with machine nodes while preserving CFG/loop metadata.
+// Selection also owns target ABI masks and rebuilding the copied graph's uses.
+class InstSelect {
+ private final CodeGen _code;
+ private InstSelect(CodeGen code) { _code=code; }
+
+ static CodeGen select(CodeGen code, String cpu, String callingConv, String base) {
+ // Ideal evaluation schedules the ideal graph without a CPU port.
+ if( cpu==null ) return code;
+ return code.phase(CodeGen.Phase.Select, code._phase==CodeGen.Phase.Unlink,
+ () -> new InstSelect(code).run(cpu,callingConv,base));
+ }
+
+ private void run(String cpu, String callingConv, String base) {
+ _code._callingConv = callingConv;
+
+ // Look for CPU in fixed named place:
+ // com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
+ String clzFile = base+"."+cpu+"."+cpu;
+ try { _code._mach = Class.forName(clzFile).asSubclass(Machine.class).getDeclaredConstructor(CodeGen.class).newInstance(_code); }
+ catch( Exception e ) { throw new RuntimeException(e); }
+
+ _code._cCallingConv = _code._mach.cCallingConv(callingConv);
+
+ // Build global copies of common register masks.
+ long callerSave = _code._mach.callerSave();
+ long cCallerSave= _code._mach.callerSave(_code._cCallingConv);
+ long neverSave = _code._mach. neverSave();
+ int maxReg = Math.min(64,_code._mach.regs().length);
+ assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave
+ assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave
+ _code._callerSave = new RegMask(callerSave);
+ _code._cCallerSave= new RegMask(cCallerSave);
+
+ // Build a Return RegMask array. All returns have the following inputs:
+ // 0 - ctrl
+ // 1 - memory
+ // 2 - varies with returning GPR,FPR
+ // 3 - RPC
+ // 4+- Caller save registers
+ _code._retMasks = new RegMask[(maxReg-_code._callerSave.size()-Long.bitCount( neverSave ))+4];
+ for( int reg=0, i=4; reg<maxReg; reg++ )
+ if( !_code._callerSave.test(reg) && ((1L<<reg)&neverSave)==0 )
+ _code._retMasks[i++] = new RegMask(reg);
+ _code._rpcMask = new RegMask(_code._mach.rpc());
+ _code._retMasks[3] = _code._rpcMask;
+
+ // Convert to machine ops
+ _code._uid = 1; // All new machine nodes reset numbering
+ var map = new IdentityHashMap<Node,Node>();
+ _instSelect( _code._stop, map );
+ _code._stop = (StopNode)map.get(_code._stop );
+ StartNode start = (StartNode)map.get(_code._start);
+ _code._start = start==null ? new StartNode(_code._start) : start;
+ _instOuts(_code._stop,_code.visit());
+ _code._visit.clear();
+
+ // Replace the CompUnit stop (and list of functions)
+ // with hardware-specific ones
+ for( CompUnit cu : _code._compunits.values() ) {
+ cu._start= (StartCUNode)map.get(cu._start);
+ cu._stop = (StopCUNode )map.get(cu._stop );
+ }
+ }
+
+ private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
+ if( n==null ) return null;
+ Node x = map.get(n);
+ if( x !=null ) return x; // Been there, done that
+
+ // If n is a MachNode already, then its part of a multi-node expansion.
+ // It does not need instruction selection (already selected!)
+ // but it does need its inputs walked.
+ if( n instanceof MachNode ) {
+ for( int i=0; i < n.nIns(); i++ )
+ n._inputs.set(i, _instSelect(n.in(i),map) );
+ return n;
+ }
+
+ // Produce a machine node from n; map it to flag as done so stops cycles.
+ if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
+ // External fields replace base+offset with symbol+0. Keep the ideal
+ // declaring type on the address for machine load/store width selection.
+ TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
+ n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
+ n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
+ }
+ map.put(n, x=_code._mach.instSelect(n) );
+ // Carry loop-tree and pre-order info across the ideal->mach transition
+ if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) {
+ xcfg._ltree = ncfg._ltree;
+ xcfg._pre = ncfg._pre;
+ }
+ // Walk machine op and replace inputs with mapped inputs
+ for( int i=0; i < x.nIns(); i++ )
+ x._inputs.set(i, _instSelect(x.in(i),map) );
+ // Post selection action
+ if( x instanceof MachNode mach ) {
+ if( n instanceof ReturnNode ret )
+ ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun);
+ mach.postSelect(_code); // Post selection action
+ }
+
+ return x;
+ }
+
+ private void _instOuts( Node n, BitSet visit ) {
+ if( visit.get(n._nid) ) return;
+ visit.set(n._nid);
+ for( Node in : n._inputs )
+ if( in!=null ) {
+ in._outputs.push(n);
+ // CallNode special: outputs are partially ordered; CallEnd in slot 0
+ if( in instanceof CallNode call && n instanceof CallEndNode cend && call.out(0) != cend )
+ call._outputs.swap(0,call.nOuts()-1);
+ _instOuts(in,visit);
+ }
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java
new file mode 100644
index 00000000..c5120370
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java
@@ -0,0 +1,60 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+
+// Establish a pessimistic fixed point after parsing/loading. Later Iter runs
+// reuse the worklist and types produced by the preceding phase.
+abstract class Iter {
+ static void run(CodeGen code) {
+ // The phase shift closes all parser-in-progress states and freezes
+ // unknown external inputs. Imported nodes carry post-Opto escape
+ // precision, but Iter is a fresh pessimistic solve: memory escape
+ // summaries must restart at FULL so newly discovered private escapes
+ // can only be removed, never added. Seed the complete graph once.
+ boolean hasImports = code._externPaths != null;
+ code._start.walk(n -> {
+ if( !(n instanceof ConstantNode) )
+ n._type = hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type);
+ code.add(n);
+ return null;
+ });
+ code._iter.iterate(code);
+ // Expensive assert.
+ assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code);
+ }
+
+ private static Type iterEscapeReset(Type t) {
+ if( t instanceof TypeMem mem )
+ return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterEscapeReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+
+ private static Type iterImportReset(Type t) {
+ if( t.isHigh() )
+ return t.dual();
+ if( t instanceof TypeMem mem )
+ return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL);
+ if( t instanceof TypeMemPtr )
+ return t.makeStorage();
+ if( t instanceof TypeStruct )
+ return t.makeStorage();
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterImportReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
index 3a4596fe..fdde8838 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
@@ -62,6 +62,8 @@ public class LRG {
public int nadj() { return _adj==null ? 0 : _adj._len; }
+ public RegMask useMask() { return _machUse==null ? null : _machUse.regmap(_uidx); }
+
void addNeighbor(LRG lrg) {
if( _adj==null ) _adj = new Ary<>(LRG.class);
_adj.push(lrg);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index efdcff6c..ef222f60 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -3,9 +3,7 @@ package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
-import java.util.Arrays;
import java.util.BitSet;
// Optimistic Algorithm
@@ -45,36 +43,6 @@ abstract public class Opto {
// After SCCP add everything changed to the worklist and see if
// peepholes can make more progress.
moveChangesToWorklist(code, oldTypes);
-
- // Progress with improved types. E.g. since we have a full Call Graph
- // here, we might do better inlining.
- code._iter.iterate(code);
-
- // TODO:
- // loop unroll, peel, RCE, etc
-
- // Freeze field sizes; do struct layouts; convert field offsets into
- // constants.
- fieldSizes(code);
- // Propagate field size constants
- code._iter.iterate(code);
-
- // Retain surviving functions in their CompUnits for later code
- // emission. Start inputs are semantic: they mean unknown callers.
- for( FunNode fun : code._linker )
- if( fun != null && !fun.isDead() && !fun._type.isHigh() )
- keepInCompUnit(code,fun);
-
- for( CompUnit cu : code._compunits.values() ) {
- ProjNode mem = cu._start.proj(1);
- if( mem != null ) mem.unkeep();
- }
-
- // To help with testing, sort StopNode Returns by NID
- for( Node stop : code._stop._inputs )
- Arrays.sort(stop._inputs._es,0,stop.nIns(),
- (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
- (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
}
// Reset Types before running the worklist algorithm
@@ -105,8 +73,10 @@ abstract public class Opto {
FunNode fun = ret.fun();
// Start is a semantic unknown-caller edge. Public functions
// keep it; closed-world functions must be reached by a
- // concrete Call edge discovered during SCCP.
- if( !(code.owns(fun) && fun.isClz()) &&
+ // concrete Call edge discovered during SCCP. Initializers,
+ // including private/imported ones, are called by opaque CLInit
+ // obligations whose Call edges are introduced only in Lower.
+ if( !fun.isClz() &&
!code.publicFIDX(fun.sig().fidx()) &&
fun.in(1) instanceof StartNode ) {
fun.removeDeadPath(1);
@@ -178,7 +148,7 @@ abstract public class Opto {
if( fun.nIns() < 2 ) {
assert fun.nIns() < 2 || fun.in(1) != start;
assert !start.isDead();
- keepInCompUnit(code,fun);
+ code.keepInCompUnit(fun);
// Function is reachable by any *remote* caller who gets the pointer!
fun.insertDef(1,start);
code._iter.add(fun);
@@ -191,14 +161,6 @@ abstract public class Opto {
}
}
- // Keep a surviving function rooted in its compilation unit without
- // claiming that it has unknown callers.
- private static void keepInCompUnit(CodeGen code, FunNode fun) {
- StopCUNode stop = fun._compunit._stop;
- if( stop._inputs.find(fun.ret()) == -1 )
- code.add(stop).addDef(fun.ret());
- }
-
// The core SCCP algorithm
private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
@@ -353,19 +315,4 @@ abstract public class Opto {
return evidence;
}
-
-
- // Freeze field sizes; do struct layouts; convert field offsets into
- // constants.
- private static void fieldSizes( CodeGen code ) {
- for( int i=0; i<code._start.nOuts(); i++ ) {
- Node use = code._start.out(i);
- if( use instanceof ConFldOffNode off ) {
- Node con = off.asOffset();
- assert con != null; // Can be null for trying to reference missing field
- off.subsume( con );
- i--;
- }
- }
- }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
index bb123634..7ce727fd 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -13,6 +13,18 @@ import java.util.*;
// Parse a given file, and all its dependencies
public abstract class ParseAll {
+ static void parse(CodeGen code, String src) {
+ Parser.TYPES.clear();
+ Parser.TYPES.putAll(Parser.INIT_TYPES);
+ if( src != null ) {
+ // No source file, just the source itself.
+ parseSource(code,code._srcName==null ? "Test" : code._srcName,src);
+ } else {
+ // Path from module root to source file
+ parsePath(code,code._srcName);
+ }
+ }
+
// Worklist of symbols to search and files to parse
private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
index 1ce68080..ac792988 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -518,7 +518,7 @@ public class RegAlloc {
// fixed register uses. Ordinary pressure splits keep the old shallow
// boundary behavior.
for( Node n : _ns ) {
- if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop
+ if( n instanceof SplitNode split && !split.isNonStackSplit() && min!=max ) continue; // Ordinary splits already reach stack storage.
if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
// If this is a 2-address commutable op (e.g. AddX86, MulX86) and the rhs has only a single user,
// commute the inputs... which chops the LHS live ranges' upper bound to just the RHS.
@@ -572,8 +572,8 @@ public class RegAlloc {
}
private static long ldepth( long ld, Node n, CFGNode cfg ) {
- // Do not count splits
- if( n instanceof SplitNode ) return ld;
+ // Ordinary splits already reach stack storage; register-only transfers do not.
+ if( n instanceof SplitNode split && !split.isNonStackSplit() ) return ld;
// Collect min/max loop depth
int min = (int)ld;
int max = (int)(ld>>32);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
index 1806be24..6a23252d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -8,6 +8,13 @@ import java.util.*;
abstract public class Serialize {
+ // Encoding/emulation need only machine code. File export also needs ideal
+ // IR for a later compiler to import; an explicit Serialize request stops here.
+ static void forTarget(CodeGen code, CodeGen.Phase target, boolean inMemory) {
+ if( target==CodeGen.Phase.Serialize || (target.ordinal()>=CodeGen.Phase.Export.ordinal() && !inMemory) )
+ code.serialize();
+ }
+
static void serialize( CodeGen code ) {
// Get all Nodes in a sane order
@@ -68,6 +75,15 @@ abstract public class Serialize {
// Initialize the mapping from bits/tags to types
Type.TAGOFFS();
+ IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>();
+ for( Node n : nodes ) anodes.put(n,anodes.size()+1); // 0 is null.
+ // The saved wrappers summarize this object's units only. Keep the
+ // live whole-program graph intact for subsequent lowering.
+ TypeTuple stopType = TypeTuple.STOP_HIGH;
+ for( Node n : nodes.last()._inputs )
+ if( anodes.containsKey(n) ) stopType = (TypeTuple)stopType.meet(n._type);
+ Type startType = TypeTuple.make(Type.CONTROL,stopType.mem(),((StartNode)nodes.at(0)).arg());
+
BAOS baos = new BAOS();
// A - Print the C0DE header. The format is not versioned yet.
baos.write('C').write('0').write('D').write('E');
@@ -91,6 +107,8 @@ abstract public class Serialize {
if( n instanceof ParmNode parm ) parm._declaredType.gather(types);
if( n instanceof TypeNode con ) con._con .gather(types);
}
+ stopType.gather(types);
+ startType.gather(types);
// A defined class remains importable even when its initializer body dies.
for( TypeStruct ts : pubTypes )
if( Parser.startsClzPrefix(ts._name) &&
@@ -190,21 +208,20 @@ abstract public class Serialize {
// How many nodes?
baos.packed4(nodes._len);
- // Radix sort by Node ID#, new compressed NIDs
- IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>();
- for( Node n : nodes )
- anodes.put(n,anodes.size()+1); // +1 bias, reserve 0 for null
// First cut: 1 byte opcode, optional per-node info, includes variable nIns
for( Node n : nodes ) {
baos.packed1(n.serialTag().ordinal());
- baos.packed2(types.get(n._type));
+ baos.packed2(types.get(n==nodes.at(0) ? startType : n==nodes.last() ? stopType : n._type));
n.packed(baos,strs,types,anodes);
}
// Write out the input indices packed.
for( Node n : nodes ) {
for( int j=0; j<n.nIns(); j++ ) {
+ if( !writeInput(n,j,anodes) ) continue;
Node def = n.in(j);
Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def);
+ // Imported function constants retain their symbol, not a body.
+ if( idx==null && n instanceof FunPtrNode && j==1 ) idx=0;
assert idx != null : "Missing serialized input for "+n+" input "+j+" def "+def;
baos.packed2(idx);
}
@@ -214,6 +231,22 @@ abstract public class Serialize {
return baos;
}
+
+ // Omit cross-object call-graph links without unlinking the live IR.
+ // A parameter follows exactly the same retained callers as its function.
+ private static boolean writeInput(Node n, int i, IdentityHashMap<Node,Integer> nodes) {
+ if( n instanceof ParmNode parm ) n=parm.fun();
+ if( (n instanceof FunNode || n instanceof CallEndNode) && i>0 )
+ return nodes.containsKey(n.in(i));
+ if( n instanceof StopNode ) return nodes.containsKey(n.in(i));
+ return true;
+ }
+
+ public static int nIns(Node n, IdentityHashMap<Node,Integer> nodes) {
+ int len=0;
+ for( int i=0; i<n.nIns(); i++ ) if( writeInput(n,i,nodes) ) len++;
+ return len;
+ }
public static <T> void gather( HashMap<T,Integer> strs, T s) {
if( s!=null && !strs.containsKey(s) )
strs.put(s,strs.size());
@@ -450,7 +483,6 @@ abstract public class Serialize {
// --------------------------------------------------
public static Ary<Node> nodeOrder( CodeGen code, CompUnit cu ) {
- assert code._start._ltree != null; // Uses loop tree
BitSet visit = code.visit();
Ary<Node> nodes = new Ary<>(Node.class);
visit.set(code._stop._nid);
@@ -501,7 +533,6 @@ abstract public class Serialize {
// --------------------------------------------------
public static Ary<Node> nodeOrder( CodeGen code ) {
- assert code._start._ltree != null; // Uses loop tree
Ary<Node> nodes = new Ary<>(Node.class);
BitSet visit = code.visit();
nodes.add(code._start);
@@ -555,6 +586,9 @@ abstract public class Serialize {
private static void _funWalk( FunNode fun, Ary<Node> rpo, BitSet visit ) {
int start = rpo._len;
_funRPO(fun,rpo,visit);
+ // A nonreturning function still owns a Return, even before LoopTree
+ // inserts synthetic exits. No loop-depth ordering is needed on disk.
+ _funRPO(fun.ret(),rpo,visit);
int len = rpo._len - start;
for( int i=0; i< len>>1; i++ )
rpo.swap(start+i,start+len-1-i);
@@ -566,13 +600,9 @@ abstract public class Serialize {
visit.set(n._nid); // Stop recursion
// Walk outputs ordered
if( n instanceof CFGNode ncfg ) {
- // If nodes walk outer loops before inner, so they hit the
- // Return first, so Return is at the bottom of the RPO.
if( n instanceof IfNode iff ) {
CProjNode c0 = iff.cproj(0);
CProjNode c1 = iff.cproj(1);
- if( c0._ltree.depth() > c1._ltree.depth() )
- { c0 = c1; c1 = iff.cproj(0); }
_funRPO(c0,rpo,visit);
_funRPO(c1,rpo,visit);
} else if( n instanceof StopNode ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
new file mode 100644
index 00000000..f2f46146
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
@@ -0,0 +1,49 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.SB;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+/** Format one parser byte offset. Source is read and text built only on error. */
+public abstract class SourceError {
+ public static String format(CompUnit cu, int pos, String message) {
+ String file=cu.sourceFile();
+ String name=file==null ? cu._fname+".smp" : file;
+ byte[] source;
+ try {
+ source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8)
+ : file==null ? null : Files.readAllBytes(Path.of(file));
+ } catch( IOException ex ) { source=null; }
+ if( source==null || pos<0 || pos>source.length )
+ return name+": error: "+message+" (source unavailable at byte offset "+pos+")";
+
+ int line=1, start=0;
+ for( int i=0; i<pos; i++ )
+ if( source[i]=='\n' ) { line++; start=i+1; }
+ int end=pos;
+ while( end<source.length && source[end]!='\n' ) end++;
+ if( end>start && source[end-1]=='\r' ) end--;
+ int point=Math.min(pos,end);
+ String before=new String(source,start,point-start,StandardCharsets.UTF_8);
+ String text=new String(source,start,end-start,StandardCharsets.UTF_8);
+ int column=before.codePointCount(0,before.length())+1;
+ SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n ");
+ int width=0, caret=0;
+ for( int i=0; i<text.length(); ) {
+ if( i==before.length() ) caret=width;
+ int ch=text.codePointAt(i);
+ if( ch=='\t' ) {
+ int spaces=8-(width%8);
+ for( int j=0; j<spaces; j++ ) sb.p(' ');
+ width+=spaces;
+ } else { sb.p(new String(Character.toChars(ch))); width++; }
+ i+=Character.charCount(ch);
+ }
+ if( before.length()==text.length() ) caret=width;
+ sb.p("\n ");
+ for( int i=0; i<caret; i++ ) sb.p('-');
+ return sb.p('^').toString();
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
index f9f16008..616753d4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
@@ -56,24 +56,6 @@ public final class StaticData {
return owner;
}
- // A final field whose declaration already denotes this exact constant is
- // available as that value to every ideal Load. Materialize it in the object
- // image instead of emitting the redundant initializer Store. This lowering
- // leaves serialized ideal IR intact, so imports retain their memory edges.
- static boolean preinitialized(CodeGen code,StoreNode st) {
- if( !(st.ptr()._type instanceof TypeMemPtr ptr) || !ptr._one || !isClass(ptr._obj) ) return false;
- CompUnit owner=owner(code,ptr._obj);
- if( owner==null || owner._src==null ) return false;
- if( !(Parser.TYPES.get(ptr._obj._name) instanceof TypeStruct ts) ) return false;
- Field fld=ts.field(st._name);
- if( fld==null || !fld._final || !fld._t.isConstant() || !st.val()._type.isConstant() ) return false;
- Type value=st.val()._type, declared=fld._t;
- if( declared instanceof TypeFunPtr fp && value instanceof TypeFunPtr vp ) return fp.fidx()==vp.fidx();
- if( declared instanceof TypeMemPtr ptr0 && value instanceof TypeMemPtr ptr1 )
- return pointerConstant(ptr0) && ptr0._obj==ptr1._obj;
- return declared==value;
- }
-
ObjectData object(Type t) {
Object key=isClass(t) ? ((TypeStruct)t)._name
: t instanceof TypeFunPtr fp ? functionSymbol(fp) : t;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
new file mode 100644
index 00000000..9b4b0995
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -0,0 +1,57 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Ary;
+
+// Reject invalid optimized programs before lowering erases their source-level
+// obligations. Queue surviving CLInits during the same walk for Lower's Iter.
+abstract class TypeCheck {
+ static void run(CodeGen code) {
+ // Check for class-init cycles first; such a cycle can prevent other
+ // things from resolving types. The dependency graph is temporary.
+ String clzInitCycle = new ClassInitDependencies(code).check();
+ if( clzInitCycle != null )
+ throw Parser.error(clzInitCycle,null);
+
+ Ary<Node> errs = new Ary<>(Node.class);
+ code._stop.walk( n -> {
+ if( n.err() != null )
+ errs.add(n);
+ if( n instanceof CLInitNode ) code.add(n);
+ return null;
+ });
+
+ if( !errs.isEmpty() ) {
+ // Prefer an originating error over a secondary error caused by a
+ // BOTTOM input. If all errors are downstream-poisoned, retain
+ // the old first-error behavior.
+ Node min = null;
+ for( Node n : errs )
+ if( noBottomInputs(n) )
+ { min = n; break; }
+ if( min==null )
+ min = errs.at(0);
+ for( Node n : errs )
+ if( n!=min &&
+ noBottomInputs(n) &&
+ ((n instanceof MemOpNode nmop &&
+ min instanceof MemOpNode mmop &&
+ nmop._loc.before(mmop._loc)) ||
+ (n instanceof CFGNode ncfg &&
+ min instanceof CFGNode mincfg &&
+ ncfg.idepth() < mincfg.idepth())) )
+ min = n;
+ if( code._obs != null ) code._obs.error(min, min.err().getMessage());
+ throw min.err();
+ }
+ }
+
+ private static boolean noBottomInputs(Node n) {
+ for( Node def : n._inputs )
+ if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM )
+ return false;
+ return true;
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java
new file mode 100644
index 00000000..fb1c4a6f
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java
@@ -0,0 +1,112 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+
+// Discard call-graph edges after LoopTree. Machine selection keeps only this
+// object's bodies; ideal evaluation keeps imports so their calls can execute.
+abstract class Unlink {
+ private static void unlinkImports(CodeGen code) {
+ // Imported function constants keep their symbolic identity, not an
+ // embedded copy of the other object's Return and body.
+ for( Node n : code._start._outputs.asAry() )
+ if( n instanceof FunPtrNode ptr && ptr.ret()!=null && !code.owns(ptr.fun()) ) {
+ ptr.liftType(ptr._type);
+ ptr.setDef(1,null);
+ }
+ for( FunNode fun : code._linker ) {
+ if( fun==null || fun.isDead() || code.owns(fun) )
+ continue;
+ for( int i=1; i<fun.nIns(); i++ )
+ if( fun.in(i) instanceof CallNode call )
+ call.unlink(fun,i--);
+ }
+ for( int i=0; i<code._stop.nIns(); i++ ) {
+ StopCUNode stop=(StopCUNode)code._stop.in(i);
+ CompUnit cu=code._compunits.get(stop.start()._fname);
+ if( cu._src==null ) code._stop.delDef(i--);
+ }
+ // The whole-object wrappers now summarize only the emitted units.
+ code._stop ._type=code._stop .compute();
+ code._start._type=code._start.compute();
+ }
+
+ static void run(CodeGen code, boolean removeImports) {
+ if( removeImports ) unlinkImports(code);
+
+ // The remaining passes assume all calls are unlinked; i.e. we are throwing
+ // away the Call Graph here. Functions only reachable from internal calls
+ // need to be re-hooked to stop/start less they go dead.
+ for( FunNode fun : code._linker ) {
+ // Already linked to start, not going dead
+ if( fun==null || fun.isDead() )
+ continue;
+
+ // Imported functions are not emitted by this object. Ideal
+ // evaluation retains them so their calls can actually execute.
+ if( removeImports && !code.owns(fun) ) {
+ while( fun.nIns() > 1 ) {
+ if( fun.in(1) instanceof CallNode call )
+ call.unlink(fun,1);
+ else
+ fun.removeDeadPath(1);
+ }
+ StartCUNode start = fun._compunit._start;
+ StopCUNode stop = fun._compunit._stop;
+ // Unhook the Stop->Return edge also
+ ReturnNode ret = fun.ret();
+ int idx = stop._inputs.find(ret);
+ if( idx != -1 ) {
+ stop._inputs.del(idx);
+ ret.delUse(stop);
+ }
+ //
+ if( stop.nIns()==0 ) {
+ idx = code._stop._inputs.find(stop);
+ if( idx != -1 )
+ code._stop.delDef(idx);
+ if( start.nIns() > 0 && start.in(0) != null )
+ start.setDef(0,null);
+ if( start.nIns() > 1 && start.in(1) != null )
+ start.setDef(1,null);
+ }
+ continue;
+ }
+
+ // Insert the post-Opto code-generation hook. Unlike an Opto-time
+ // Start input, this no longer carries unknown-caller semantics.
+ ReturnNode ret = fun.ret();
+ StartNode start = fun._compunit._start;
+ assert start!=null;
+ if( fun.nIns() <= 1 || fun.in(1) != start ) {
+ fun.insertDef(1,start);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.insertDef(1,ConstantNode.raw(parm._type));
+ if( fun._compunit._stop._inputs.find(ret) == -1 )
+ fun._compunit._stop.addDef(ret);
+ }
+ // Unlink from Call
+ for( int i=2; i<fun.nIns(); i++ )
+ ((CallNode)fun.in(i)).unlink(fun,i--);
+ if( fun.rpc()==null ) { // Ensure valid RPC
+ // First make sure a valid RPC; single-call into multi-fun will have each
+ // function having a single call site, so the RPC becomes a constant and
+ // folds - but since multiple targets, the Call never inlines. Recreate a
+ // valid RPC so codegen (and Eval2) understands the calling convention.
+ ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun );
+ rpc.addDef(ret.rpc());
+ ret.setDef( 3, rpc.init() );
+ }
+ }
+
+ code._stop.walk(n -> {
+ if( n instanceof CallEndNode cend )
+ for( int i=1; i<cend.nIns(); i++ )
+ if( cend.in(i) instanceof ReturnNode )
+ cend.delDef(i--);
+ if( n instanceof CallNode call )
+ call.unlink_all();
+ return null;
+ });
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index bc47ebfc..92ebd587 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -55,6 +55,9 @@ public abstract class ArithNode extends Node implements ModeNode {
if( n instanceof ModeNode mode && mode.mode()==0 ) {
mode.setMode((byte)1);
n.setType(n.compute());
+ // Mode changes remove n from GVN even if the caller's remaining
+ // conditions prevent replacing the int-only operation by n.
+ CodeGen.CODE.add(n);
}
return true;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index 82c4af10..c6a6ff3b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -125,7 +125,7 @@ public class BulkMemPhiNode extends PhiNode {
// True if the MemMerge has no precise slice still covered by this Phi.
private boolean canPeek(MemMergeNode mmm) {
- return missingAlias(mmm,false)==0;
+ return missingAlias(mmm)==0;
}
// Return an alias required by an input but still covered by this Phi, or 0.
@@ -137,7 +137,7 @@ public class BulkMemPhiNode extends PhiNode {
assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
yield 0;
}
- case MemMergeNode mmm -> missingAlias(mmm,false);
+ case MemMergeNode mmm -> missingAlias(mmm);
case MemOpNode mem -> unsplit(mem._alias);
case BulkMemPhiNode bulk -> missingAlias(bulk);
default -> throw Utils.TODO("Should not reach here");
@@ -154,13 +154,13 @@ public class BulkMemPhiNode extends PhiNode {
return switch(use) {
case ScopeNode scope -> 0;
case ParmNode parm -> 0;
- case MemMergeNode mmm -> missingAlias(mmm,true);
+ case MemMergeNode mmm -> missingAlias(mmm);
case BulkMemPhiNode bulk -> missingAlias(bulk);
case MemOpNode mem -> unsplit(mem._alias);
case MemPhiNode phi -> unsplit(phi._alias);
case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
default -> {
- assert use instanceof ReturnNode || use instanceof CallNode
+ assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode
: "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
yield 0;
}
@@ -183,14 +183,14 @@ public class BulkMemPhiNode extends PhiNode {
return 0;
}
- // First precise MemMerge slice still covered by this Phi. When inspecting
- // a user, ignore slots which point back to this Phi.
- private int missingAlias(MemMergeNode mmm, boolean user) {
+ // First explicit MemMerge slice still covered by this Phi. A user's slice
+ // may point to this Phi after a precise Phi folds; splitting is still valid.
+ private int missingAlias( MemMergeNode mmm ) {
for( int alias=2; alias<mmm.nIns(); alias++ )
if( mmm.in(alias)!=null &&
mmm.alias(alias)!=mmm.in(1) &&
!_aliases.get(alias) )
- { assert (!user || mmm.in(alias)!=this); return alias; }
+ return alias;
return 0;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index fe3382a6..4f225fbe 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -109,6 +109,7 @@ public abstract class CFGNode extends Node {
}
private CFGNode _safeCFG() {
return switch(this) {
+ case CLInitNode init when init.bypass() -> init.cfg0()._safeCFG();
case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
index 5131049e..7bc17c75 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
@@ -6,33 +6,44 @@ import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.BAOS;
-import com.seaofnodes.simple.util.Utils;
import java.util.*;
// Track class initializers - the code just after this point expects the named
// class to be initialized. If this code path dies, then the dependency dies
// also - dead code does not force class init.
//
-public class CLInitNode extends CFGNode {
+public class CLInitNode extends CFGNode implements MultiNode {
public Parser.Lexer _loc; // For error reporting
+ // Inputs: control, whole memory, class pointer, loaded $init flag
+ // Results: control, whole memory, known-true flag alias memory.
// For field references, the ptr might be the dst clz ptr or not.
- public CLInitNode(Parser.Lexer loc, Node ctrl, Node ptr) {
- super(ctrl,ptr);
+ public CLInitNode(Parser.Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) {
+ super(ctrl,mem,ptr,ready);
_loc = loc;
}
- CLInitNode(BAOS bais) { super(null,null); }
+ CLInitNode(BAOS bais, String[] strs) { super(null,null,null,null); _loc=Parser.Lexer.unpack(bais,strs); }
@Override public Tag serialTag() { return Tag.CLInit; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ Parser.Lexer.packed(_loc,baos,strs);
}
+ @Override public void gather(HashMap<String,Integer> strs) { Parser.Lexer.gather(_loc,strs); }
public CLInitNode(CLInitNode init) {
super(init);
_loc = init._loc;
}
- Node ptr() { return in(1); }
+ public Node mem() { return in(1); }
+ public Node ptr() { return in(2); }
+ public Node ready() { return in(3); }
+
+ // The field/alias can be unresolved until forward references are loaded.
+ public Field flag() {
+ return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name)
+ ? tmp._obj.field("$init") : null;
+ }
@Override public String label() { return "CLInit"; }
// Best effort dst compunit, based on ptr - or null
@@ -53,14 +64,109 @@ public class CLInitNode extends CFGNode {
// Block head is Start, Region, CProj, but not e.g. If, Return, Stop
public boolean blockHead() { return true; }
- @Override public Type compute() { return in(0)._type; }
+ private boolean instance() {
+ return ptr()._type==Type.NIL ||
+ (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT &&
+ !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref &&
+ !Parser.startsClzPrefix(tmp._obj._name));
+ }
+
+ public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; }
+
+ @Override public Type compute() {
+ if( in(0)._type.isHigh() || ptr()._type.isHigh() )
+ return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP);
+ if( instance() )
+ return TypeTuple.make(in(0)._type,mem()._type,mem()._type);
+ Field flag = flag();
+ Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE);
+ // The initializer may write arbitrary public memory. Only its flag
+ // has a guaranteed value on the normal continuation.
+ Type bulk = ready()._type.isHigh() ? TypeMem.TOP :
+ ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT;
+ return TypeTuple.make(in(0)._type,bulk,slice);
+ }
+
+ @Override public Node pcopy(int idx, Node dep) {
+ dep.addDep(ptr());
+ dep.addDep(ready());
+ if( instance() ) return idx==0 ? in(0) : mem();
+ if( ready()._type!=TypeInteger.TRUE ) return null;
+ if( idx==0 ) return in(0);
+ if( idx==1 ) return mem();
+ // Already established on the incoming path; this is a memory fact,
+ // not a new store to the class object.
+ return CodeGen.CODE.con(((TypeTuple)_type)._types[2]);
+ }
+
@Override public Node idealize() {
- // A receiver discovered to be an ordinary instance needs no class init.
- if( ptr()._type instanceof TypeMemPtr ptr && ptr._obj!=TypeStruct.BOT &&
- !ptr._obj.isHigh() && !Parser.startsClzPrefix(ptr._obj._name) )
- return in(0); // Normal instance, no class init
- if( ptr()._type == Type.NIL || ptr()._type.isHigh() )
- return in(0);
- return null;
+ if( CodeGen.CODE._phase!=CodeGen.Phase.Lower || bypass() || in(0)._type.isHigh() ) return null;
+ if( CodeGen.CODE._midAssert ) return this;
+ // Until entry arguments become arrays, ordinary initializers receive zero.
+ return expand(CodeGen.CODE.con(TypeInteger.ZERO));
+ }
+
+ // Called only after cycle/type checking. The initialized arm skips the
+ // call; both arms rejoin with whole memory and a known-true flag slice.
+ private Node expand(Node arg) {
+ FunNode target = CodeGen.CODE.link(cu());
+ assert target!=null && !target.isDead();
+ keep();
+ Node ctrl = in(0);
+ Node memory = mem();
+
+ // A child body may assume its parent has finished. Establish that
+ // requirement first, then test the child's flag in the resulting memory.
+ CompUnit parent = cu()._par;
+ if( parent!=null ) {
+ Node ptr = CodeGen.CODE.con(TypeMemPtr.make((byte)2,parent._clz,true));
+ Field flag = parent._clz.field("$init");
+ Node off = CodeGen.CODE.con(parent._clz.offset(parent._clz.find("$init")));
+ Node parentReady = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag._alias,ctrl,memory,ptr,off).init());
+ CLInitNode init = CodeGen.CODE.add(new CLInitNode(_loc,ctrl,memory,ptr,parentReady).init());
+ ctrl = CodeGen.CODE.add(new CProjNode(init,0,ScopeNode.CTRL).init());
+ memory = CodeGen.CODE.add(new ProjNode(init,1,ScopeNode.MEM0).init());
+ }
+
+ Node childOff = CodeGen.CODE.con(cu()._clz.offset(cu()._clz.find("$init")));
+ // Read the child's flag after establishing its parent's memory effects.
+ Node ready = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag()._alias,ctrl,memory,ptr(),childOff).init());
+
+ IfNode iff = CodeGen.CODE.add(new IfNode(ctrl,ready).init());
+ Node done = CodeGen.CODE.add(new CProjNode(iff,0,"True").init());
+ Node cold = CodeGen.CODE.add(new CProjNode(iff,1,"False").init());
+
+ Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole();
+ CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init());
+ CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname)));
+ // This is a lowered call, after interprocedural analysis. Do not add
+ // new parameter flows to a body already optimized by SCCP.
+ CodeGen.CODE.add(end.init());
+ Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init());
+ Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init());
+ RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init());
+ Node merged = CodeGen.CODE.add(new BulkMemPhiNode(ScopeNode.MEM0,region,memory,after).init());
+
+ // Replace the individual outputs; no tuple-pack node or second
+ // representation of CLInit is needed while the projections collapse.
+ for( Node use : _outputs.asAry() ) {
+ if( use==null ) continue; // Our temporary keep.
+ assert use instanceof Proj;
+ int idx = ((Proj)use).idx();
+ use.subsume(idx==0 ? region : idx==1 ? merged : CodeGen.CODE.con(((TypeTuple)_type)._types[2]));
+ }
+ CodeGen.CODE.invalidateIDepthCaches();
+ return unkeep(); // Iter removes this now-unused node.
+ }
+
+ // Resolve a precise flag slice even when the parser did not yet know the
+ // class/alias and could only install the whole-memory projection.
+ static Node slice(Node mem, int alias, Node dep) {
+ if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) )
+ return mem;
+ dep.addDep(init.ptr());
+ Field flag = init.flag();
+ if( flag==null || flag._alias!=alias ) return mem;
+ return new ProjNode(init,2,Parser.memName(alias)).peephole();
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index 12fde6c0..073e4658 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -62,7 +62,7 @@ public class CProjNode extends CFGNode implements Proj {
return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
// Copy of some other input
- return ((MultiNode)ctrl()).pcopy(_idx);
+ return ((MultiNode)ctrl()).pcopy(_idx,this);
}
// Only called during basic-block layout, inverts a T/F CProj
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 13e7ac12..cfc58ce9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -40,7 +40,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
}
@Override public Tag serialTag() { return Tag.CallEnd; }
public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
- baos.packed1(nIns());
+ baos.packed1(Serialize.nIns(this,anodes));
// Linked CallEnds depend on Return types which depend on CallEnds;
// break the cycle
baos.packed2(types.get(_type));
@@ -108,7 +108,9 @@ public class CallEndNode extends CFGNode implements MultiNode {
// effects.
if( (nIns()-1)+externs < tfp.nfcns() ) {
// If during Opto, assume call won't be called and thus won't return anything.
- boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ // Lowered calls deliberately have no links into optimized bodies.
+ boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
+ CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Lower.ordinal();
Type tmem = opto ? TypeMem.TOP : TypeMem.BOT;
Type ret = opto ? Type.TOP : tfp.ret();
return TypeTuple.make(Type.CONTROL,tmem,ret);
@@ -302,7 +304,7 @@ public class CallEndNode extends CFGNode implements MultiNode {
}
- @Override public Node pcopy(int idx) {
+ @Override public Node pcopy(int idx, Node dep) {
return _folding ? in(1).in(idx) : null;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 38117f23..71f6d146 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -22,16 +22,17 @@ public class CallNode extends CFGNode {
// Source location for late reported errors
public final Parser.Lexer _loc;
- public CallNode(Parser.Lexer loc, Node... nodes) { this(loc,loc==null ? 0 : loc.lineColumn(),nodes); }
- private CallNode(Parser.Lexer loc, long position, Node... nodes) { super(nodes); _loc=loc; }
+ public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc=loc; }
public CallNode(CallNode call) { super(call); _loc = call._loc; }
@Override public Tag serialTag() { return Tag.Call; }
public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
baos.packed1(nIns());
+ Parser.Lexer.packed(_loc,baos,strs);
}
- static Node make( BAOS bais ) {
+ @Override public void gather(HashMap<String,Integer> strs) { Parser.Lexer.gather(_loc,strs); }
+ static Node make( BAOS bais, String[] strs ) {
int nIns = bais.packed1();
- return new CallNode(null,new Node[nIns]);
+ return new CallNode(Parser.Lexer.unpack(bais,strs),new Node[nIns]);
}
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -100,6 +101,9 @@ public class CallNode extends CFGNode {
@Override
public Node idealize() {
+ // Lowering may introduce calls after the call graph has been analyzed.
+ // These use the callee interface without reopening its parameter flow.
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Lower.ordinal() ) return null;
CallEndNode cend = cend();
if( cend==null ) return null; // Still building
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
index 47a7bbf6..0569f352 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -37,13 +37,13 @@ public class ConstantNode extends TypeNode {
}
static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); }
- // Semantic constant construction through Opto. A unique internal
+ // Semantic constant construction through Lower. A unique internal
// function identity carries a lifetime edge to the function's Return.
public static Node make( Type type ) {
if( type==Type. CONTROL ) return new CtrlNode();
if( type==Type.XCONTROL ) return new XCtrlNode();
if( CodeGen.CODE._phase != null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() )
return raw(type);
if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
FunNode fun = CodeGen.CODE._link(tfp);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index b6c3b0c9..cdc890a7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -30,10 +30,10 @@ public class ConvertNode extends Node {
}
// The declaration fixes the result family during pessimistic parsing.
- // Optimistic SCCP must retain stronger facts already inside that family.
+ // From SCCP onward, preserve dead inputs and stronger facts in that family.
@Override public Type compute() {
Type src = val()._type;
- if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) {
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() ) {
if( src.isHigh() ) return Type.TOP;
if( (src.isa(_dst) && src.accessISA(_dst)) ) return src;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 7f9a2819..edde247a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -45,7 +45,7 @@ public class FunNode extends RegionNode {
@Override public Tag serialTag() { return Tag.Fun; }
@Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) {
assert !_folding;
- baos.packed1(nIns()); // Number of linked calls
+ baos.packed1(Serialize.nIns(this,anodes)); // Object-local linked calls
baos.packed2(types.get(_sig)); // NPE if fails lookup
baos.packed2(_name==null ? 0 : strs.get(_name));
baos.packed2(_approxUIDs);
@@ -171,7 +171,8 @@ public class FunNode extends RegionNode {
// through class-field loads and other initially imprecise values.
// - FIDX is not in the escape set
// - Not called anywhere else; post Opto called-not-escaped functions need to still be hooked to start
- if( in(1) instanceof StartCUNode start ) {
+ // Opaque CLInit obligations need their bodies even without Call edges.
+ if( !isClz() && in(1) instanceof StartCUNode start ) {
boolean preOpto = CodeGen.CODE._phase == null ||
CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index a557f97c..0a997083 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -47,7 +47,8 @@ public class LoadNode extends MemOpNode {
dep.addDepForwards(use);
if( use instanceof StoreNode || use instanceof PhiNode ||
use instanceof MemMergeNode ||
- use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ) return true;
+ use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ||
+ use instanceof CLInitNode ) return true;
}
return false;
}
@@ -134,6 +135,9 @@ public class LoadNode extends MemOpNode {
if( _alias == 1 )
return null;
+ Node slice = CLInitNode.slice(mem,_alias,this);
+ if( slice!=mem ) { setDef(1,slice); return this; }
+
if( mem instanceof MemMergeNode merge ) {
setDef(1,merge.alias(_alias));
// In support of uplifting control to a prior dominating load, if
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index 373b8825..bfbd46c3 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -6,13 +6,9 @@ import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.*;
import java.lang.StringBuilder;
-import java.util.BitSet;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
+import java.util.*;
/**
* Convenience common base for Load and Store.
@@ -130,37 +126,34 @@ public abstract class MemOpNode extends TypeNode {
if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
return Parser.error( "Might be null accessing '" + _name + "'",_loc);
if( tmp._obj.field(_name)==null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
// Sane field
- if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
return null;
}
// Memory writers which must follow this read. A packaging node is not a
// write; follow this alias through aggregates to its writers.
- public ArrayList<Node> antiDeps() {
- ArrayList<Node> deps = new ArrayList<>();
- antiDeps(mem(),deps,new BitSet());
- return deps;
+ public Ary<Node> antiDeps( Ary<Node> antideps ) {
+ antideps.clear();
+ return antiDeps(mem(),antideps);
}
- private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) {
- if( visit.get(mem._nid) ) return;
- visit.set(mem._nid);
+ private Ary<Node> antiDeps( Node mem, Ary<Node> antideps ) {
for( Node use : mem._outputs ) {
if( use instanceof MemMergeNode merge ) {
- Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
- if( (slice==mem) || (slice==null && merge.in(1)==mem) )
- antiDeps(merge,deps,visit);
- } else if( use instanceof CallNode ||
+ if( merge.alias(_alias)==mem )
+ antiDeps(merge,antideps);
+ } else if( use instanceof CallNode || use instanceof CLInitNode ||
(use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
(use instanceof EscapeNode esc && esc.fld()._alias==_alias) ||
(use instanceof MemPhiNode phi && phi._alias==_alias) ||
(use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
- if( !deps.contains(use) ) deps.add(use);
+ if( !antideps.contains(use) ) antideps.add(use);
}
}
+ return antideps;
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
index cdfaaf97..aaaedb89 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -56,11 +56,14 @@ public class MemPhiNode extends PhiNode {
public Node idealize() {
// A precise memory Phi can always bypass a MemMerge and select its
// matching slice.
- for( int i=1; i<nIns(); i++ )
+ for( int i=1; i<nIns(); i++ ) {
if( in(i) instanceof MemMergeNode mem ) {
setDef(i,mem.alias(_alias));
return this;
}
+ Node slice = CLInitNode.slice(in(i),_alias,this);
+ if( slice!=in(i) ) { setDef(i,slice); return this; }
+ }
if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() &&
in(2) instanceof StoreNode st && st.mem()==this ) {
Node sink = st.sink(this);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
index b4d6d78e..152d525a 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -25,5 +25,5 @@ public interface MultiNode {
// Return not-null if this projection index is a ideal copy.
// Called by ProjNode ideal and used to collapse Multis.
- default Node pcopy(int idx) { return null; }
+ default Node pcopy(int idx, Node dep) { return null; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
index 796965b5..77b43ba8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -112,9 +112,9 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
case LE -> new BoolNode.LE (null,null,(byte)bais.packed1());
case ULT -> new BoolNode.ULT(null,null,(byte)bais.packed1());
- case Call -> CallNode.make(bais);
+ case Call -> CallNode.make(bais,strs);
case CallEnd-> CallEndNode.make(bais ,types);
- case CLInit -> new CLInitNode(bais);
+ case CLInit -> new CLInitNode(bais,strs);
case CProj -> CProjNode.make(bais,strs);
case Con -> ConstantNode.make(bais ,types);
case Escape-> EscapeNode.make(bais ,types);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 899d7639..278c5308 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -3,6 +3,7 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.util.BAOS;
import java.util.HashMap;
@@ -23,7 +24,7 @@ public class ParmNode extends PhiNode {
public ParmNode(ParmNode parm) { super(parm, parm._label ); _idx = parm._idx; _declaredType = parm._declaredType; }
@Override public Tag serialTag() { return Tag.Parm; }
@Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
- baos.packed1(nIns()); // Number of linked calls
+ baos.packed1(Serialize.nIns(this,anodes)); // Object-local linked calls
baos.packed2(_label==null ? 0 : strs.get(_label));
baos.packed2(types.get(_declaredType)); // NPE if fails lookup
baos.packed1(_idx);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index 4b29cc3c..fcc113a8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -55,7 +55,7 @@ public class ProjNode extends Node implements Proj {
return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob();
}
- @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); }
+ @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx,this); }
@Override
public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
index 0e104c15..3042f857 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -13,6 +13,8 @@ public abstract class SplitNode extends MachConcreteNode {
public final String _kind; // Kind of split
public final byte _round; // Debugging info for which RegAlloc round
public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; }
+ // A register-only transfer may need another split to reach stack storage.
+ public boolean isNonStackSplit() { return false; }
@Override public String op() { return "mov"; }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
p.p("mov(");
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
index 84c8a3a7..495be0a2 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -45,11 +45,11 @@ public class StartNode extends LoopNode implements MultiNode {
// Find module.<clinit>, it's the start.
CFGNode C = null;
for( Node use : _outputs )
- if( use instanceof FunNode fun && fun.isModInit() )
+ if( use instanceof FunNode fun && CodeGen.CODE.entryClinitName().equals(fun._name) )
{ assert C==null; C = fun; }
if( C==null )
for( FunNode fun : CodeGen.CODE._linker )
- if( fun!=null && !fun.isDead() && fun.isModInit() )
+ if( fun!=null && !fun.isDead() && CodeGen.CODE.entryClinitName().equals(fun._name) )
{ assert C==null; C = fun; }
return C;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
index ab0f5da6..d3a356ed 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -40,7 +40,8 @@ public class StopCUNode extends StopNode {
// can widen from a precise set to FULL; the public-only normalization
// below must not forget private bits already discovered precisely from
// that Return on an earlier SCCP step.
- if( CodeGen.CODE._phase == CodeGen.Phase.Opto &&
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
+ CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() &&
_type instanceof TypeTuple old && old.mem() instanceof TypeMem omem )
xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs),
XInt.meet(xmem._escAs,omem._escAs));
@@ -100,7 +101,8 @@ public class StopCUNode extends StopNode {
// precise finite sets. Replacing FULL early there would let a later
// input refinement add bits, reversing Iter's monotonic direction.
// This normalization is needed only for Opto's optimistic grand cycle.
- if( CodeGen.CODE._phase != CodeGen.Phase.Opto ) return mem;
+ if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ||
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem;
int[] escFs = XInt.isHigh(mem._escFs)
? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
: mem._escFs;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
index e52a49a2..a7e52563 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -3,6 +3,7 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.BAOS;
@@ -18,7 +19,7 @@ public class StopNode extends CFGNode {
}
public StopNode(StopNode stop) { super(stop); }
@Override public Tag serialTag() { return Tag.Stop; }
- @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(Serialize.nIns(this,anodes)); }
static Node make( BAOS bais ) {
StopNode stop = new StopNode();
stop.setDefX(bais.packed1()-1,null);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 4917e890..8a0bf8d4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -89,6 +89,16 @@ public class StoreNode extends MemOpNode {
Type decl = declaredType();
if( decl != Type.BOTTOM )
{ assert decl != Type.TOP; val = val.join(decl); }
+ // A singleton class's final constant field lives in the static image.
+ // Its precise memory slice is a constant too; ordinary peepholes can
+ // replace this Store before scheduling or instruction selection.
+ // A constant runtime value alone is not enough: mutable fields (notably
+ // $init) still need their writes, and instance fields need allocation.
+ Field fld = ptr._obj.field(_name);
+ if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) &&
+ fld!=null && fld._final && decl.isConstant() )
+ return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val);
+
// Allocation uses a private TypeMem and nothing else does. This
// memory is truly private; a temporary singleton until it escapes -
// which is never does in a constructor.
@@ -108,7 +118,6 @@ public class StoreNode extends MemOpNode {
Type t = val.meet(tfld);
// Bulk memory's final bit describes its conservative remainder, not
// this newly precise slice. Slice finality comes from the field.
- Field fld = ptr._obj.field(_name);
boolean xfinal = fld != null && fld._final;
return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val);
}
@@ -304,7 +313,7 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
- assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Opto.ordinal();
+ assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal();
if( ptr()._type == Type.TOP ) // Dead store?
return null;
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
new file mode 100644
index 00000000..25e5d501
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
@@ -0,0 +1,21 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.isa.Arm64;
+import com.seaofnodes.simple.codegen.*;
+
+// NZCV transfers require a GPR. Further splits can spill that GPR to the stack.
+public class SplitFlagsARM extends SplitARM {
+ private static final RegMask MASK = new RegMask(arm.WR_BITS,1L);
+ SplitFlagsARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ @Override public boolean isNonStackSplit() { return true; }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+
+ @Override public void encoding(Encoding enc) {
+ short dst=enc.reg(this), src=enc.reg(in(1));
+ if( dst==src ) return;
+ if( src==arm.FLAGS ) enc.add4(Arm64.flags(dst,false));
+ else if( dst==arm.FLAGS ) enc.add4(Arm64.flags(src,true));
+ else super.encoding(enc);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
new file mode 100644
index 00000000..c9b2127a
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
@@ -0,0 +1,11 @@
+package com.seaofnodes.simple.node.cpus.arm;
+
+import com.seaofnodes.simple.codegen.*;
+
+// Keep a saved NZCV value in GPRs and stack slots through subsequent spills.
+public class SplitGPRARM extends SplitARM {
+ private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L);
+ SplitGPRARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 660f1559..3c965da5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -288,7 +288,16 @@ public class arm extends Machine {
@Override public int rpc() { return X30; }
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(LRG lrg, String kind, byte round) { return new SplitARM(lrg,kind,round); }
+ @Override public SplitNode split(LRG lrg, String kind, byte round) {
+ RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask();
+ if( (def!=null && def.size1() && def.test(FLAGS)) ||
+ (use!=null && use.size1() && use.test(FLAGS)) )
+ return new SplitFlagsARM(lrg,kind,round);
+ // Spilling the saved flags requires a GPR between NZCV and the stack.
+ if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM )
+ return new SplitGPRARM(lrg,kind,round);
+ return new SplitARM(lrg,kind,round);
+ }
// Return a MachNode unconditional branch
@Override public CFGNode jump() {
@@ -305,6 +314,7 @@ public class arm extends Machine {
case PtrToIntNode ptr -> new PtrToIntMach(ptr);
case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend-> new CallEndMach(cend);
+ case CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case ConstantNode con-> con(con);
case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
@@ -459,6 +469,7 @@ public class arm extends Machine {
}
private Node prj(ProjNode prj) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendARM(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 19b91866..0195542c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -218,6 +218,7 @@ public class riscv extends Machine {
case PtrToIntNode ptr -> new PtrToIntMach(ptr);
case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend -> new CallEndMach(cend);
+ case CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case ConstantNode con -> con(con);
case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
@@ -412,6 +413,7 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendRISC(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
new file mode 100644
index 00000000..cb7694f9
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
@@ -0,0 +1,25 @@
+package com.seaofnodes.simple.node.cpus.x86_64_v2;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.simple.codegen.*;
+
+// Flag snapshots can use GPRs or stack slots, but never XMM registers.
+public class SplitFlagsX86 extends SplitX86 {
+ private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L);
+ SplitFlagsX86(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ @Override public RegMask regmap(int i) { return MASK; }
+ @Override public RegMask outregmap() { return MASK; }
+
+ @Override public void encoding(Encoding enc) {
+ short dst=enc.reg(this), src=enc.reg(in(1));
+ if( dst==src ) return;
+ if( dst==x86_64_v2.FLAGS || src==x86_64_v2.FLAGS ) {
+ boolean toFlags = dst==x86_64_v2.FLAGS;
+ int reg = toFlags ? src : dst;
+ if( reg>=x86_64_v2.MAX_REG )
+ X86.flagsStack(enc,fun(enc).computeStackOffset(enc._code,reg),toFlags);
+ else
+ X86.flags(enc,reg,toFlags);
+ } else super.encoding(enc);
+ }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
index b2b643d3..46f2cb16 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -16,12 +16,10 @@ public class SplitX86 extends SplitNode {
@Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
@Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
- // Need to handle 8 cases: . reg->reg, reg->xmm, reg->flags, xmm->reg, xmm->xmm, xmm->flags, flags->reg, flags->xmm,
- // flags->flags.
+ // Moves among GPRs, XMM registers, and stack slots.
@Override public void encoding( Encoding enc ) {
short dst=enc.reg(this), src=enc.reg(in(1));
- if( dst==x86_64_v2.FLAGS ) { X86.flags(enc,src,true); return; }
- if( src==x86_64_v2.FLAGS ) { X86.flags(enc,dst,false); return; }
+ assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS;
boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET;
// Stack spills
if( dst >= x86_64_v2.MAX_REG ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 23c4d530..3dc93bf6 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -47,7 +47,7 @@ public class x86_64_v2 extends Machine {
static RegMask DIV_MASK = new RegMask(RD_BITS & ~(1L<<RAX) & ~(1L<<RDX));
static final long SPILLS = -(1L << MAX_REG);
- static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS /* | (1L<<FLAGS)*/ | SPILLS, -1L );
+ static final RegMask SPLIT_MASK = new RegMask(WR_BITS | FP_BITS | SPILLS, -1L );
// Load/store mask; both GPR and FPR
static RegMask MEM_MASK = new RegMask(WR_BITS | FP_BITS);
@@ -267,7 +267,9 @@ public class x86_64_v2 extends Machine {
// Create a split op; any register to any register, including stack slots
@Override public SplitNode split(LRG lrg, String kind, byte round ) {
- return new SplitX86(lrg,kind, round);
+ return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS)
+ ? new SplitFlagsX86(lrg,kind,round)
+ : new SplitX86(lrg,kind,round);
}
// Return a MachNode unconditional branch
@@ -491,6 +493,7 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
+ if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendX86(prj,ret);
return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
index bf8403e1..f2ed2f72 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -17,7 +17,7 @@ public class SimpleGraphObserver extends GraphCapture<Node> {
@Override protected void compile(String src) {
_code = new CodeGen(src);
_code._obs = this;
- _code.parse().opto().typeCheck();
+ _code.driver(CodeGen.Phase.TypeCheck);
}
@Override protected String phase() { return _code._phase.name(); }
@Override protected int pos() { return _code._phase == CodeGen.Phase.Parse ? _code.P.pos() : -1; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 82c37a12..4c5ae6ea 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -597,7 +597,7 @@ public class TypeStruct extends Type {
return _offs[idx];
}
private int[] offsets() { // Field byte offsets
- assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ assert CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.FinalizeIR.ordinal();
// Compute a layout for a collection of fields
assert _fields != null; // No forward refs
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/BulkMemPhiTest.java b/chapter25/src/test/java/com/seaofnodes/simple/BulkMemPhiTest.java
new file mode 100644
index 00000000..31c0302b
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/BulkMemPhiTest.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMem;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class BulkMemPhiTest {
+ // Reduced from Capitalize's Lower-phase failure. A precise MemMerge slot
+ // may still be supplied by bulk memory which covers the requested alias.
+ @Test public void testBulkInPreciseSlot() {
+ CodeGen code=new CodeGen("");
+ code._phase=CodeGen.Phase.Lower;
+ Node m0=code.con(TypeMem.START), m1=code.con(TypeMem.BOT);
+ Node ctrl=new CProjNode(code._start,0,"Ctrl").init();
+ Node iff=new IfNode(ctrl,code.con(TypeInteger.BOOL)).init();
+ RegionNode region=new RegionNode(null,null,
+ new CProjNode(iff,0,"True").init(),
+ new CProjNode(iff,1,"False").init()).init();
+ BulkMemPhiNode bulk=new BulkMemPhiNode("$mem",region,m0,m1).init();
+ // A later init check proves its call arm dead. Its precise Phi can
+ // collapse before the surrounding aggregate gets another peephole.
+ RegionNode done=new RegionNode(null,null,region,code.XCTRL).init();
+ MemPhiNode precise=new MemPhiNode("$4",4,done,bulk,m1).init();
+ MemMergeNode merge=new MemMergeNode(null,m0);
+ merge.alias(4,precise);
+ merge.init();
+ Node folded=precise.peepholeOpt();
+ assertSame(bulk,folded);
+ precise.subsume(folded);
+
+ assertFalse(bulk.isSplit(4));
+ assertSame(bulk,merge.alias(4));
+ // Splitting this still-covered alias must tolerate the direct bulk use.
+ Node mem = bulk.idealize();
+ bulk.subsume(mem);
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
index 79af6ae7..afdb1071 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter05Test.java
@@ -20,7 +20,7 @@ else {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,(arg+2),(arg-3));", code.print());
}
@@ -36,7 +36,7 @@ if (arg == 1) {
}
return c;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,4,3);", code.print());
}
@@ -64,7 +64,7 @@ if( arg==1 )
else
b=a+1;
return a+b;""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return ((arg<<1)+Phi(Region,2,3));", code.print());
}
@@ -79,7 +79,7 @@ if( arg==1 )
else
a=b+1;
return a+b;""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return ((Phi(Region,(arg<<1),arg)+arg)+Phi(Region,4,5));", code.print());
}
@@ -99,7 +99,7 @@ else
a=5;
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,3,5,2,4);", code.print());
}
@@ -115,7 +115,7 @@ if( arg==0 )
b=2;
return arg+a+b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
String ir = code.print();
assertTrue(ir,
ir.equals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));") ||
@@ -133,7 +133,7 @@ if( arg==1 )
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (arg==Phi(Region,3,2));", code.print());
}
@@ -148,7 +148,7 @@ else
a=3;
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java
index fdf6963f..d1af5d14 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter06Test.java
@@ -15,7 +15,7 @@ public class Chapter06Test {
if( true ) return 2;
return 1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 2;", code.print());
assertTrue(code.ctrl() instanceof FunNode);
}
@@ -29,7 +29,7 @@ if (arg)
a = 2;
return (arg < a) < 3; // Because (arg < a) is a bool/uint1/[0-1], its always less than 3
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
}
@@ -44,7 +44,7 @@ else
a=3;
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 2;", code.print());
assertTrue(code.ctrl() instanceof FunNode);
}
@@ -65,7 +65,7 @@ else
b=5;
return b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,42,5);", code.print());
}
@@ -84,7 +84,7 @@ if( arg==2 )
else
b=5;
return b;""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,2,5);", code.print());
}
@@ -104,7 +104,7 @@ else if( arg==3 )
else
a=5;
return a;
-""", TypeInteger.constant(2)).parse().opto();
+""", TypeInteger.constant(2)).driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 5;", code.print());
}
@@ -124,7 +124,7 @@ else if( arg==3 )
else
a=5;
return a;
-""", TypeInteger.constant(1)).parse().opto();
+""", TypeInteger.constant(1)).driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
}
@@ -144,7 +144,7 @@ else
a=5;
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,3,5,4);", code.print());
}
@@ -165,7 +165,7 @@ else
a=5;
return a;
""", TypeInteger.constant(1));
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
}
@@ -186,7 +186,7 @@ else
a=5;
return a;
""", TypeInteger.constant(3));
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
}
@@ -202,7 +202,7 @@ if( arg ) {
}
return a+b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,4,1);", code.print());
}
@@ -221,7 +221,7 @@ if( arg ) {
}
return a+b;
""", TypeInteger.constant(1));
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
}
@@ -239,7 +239,7 @@ if( arg ) {
}
return a+b;
""", TypeInteger.constant(0));
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
}
@@ -257,7 +257,7 @@ if( arg ) {
}
return a+b+c;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,6,1,5);", code.print());
}
@@ -278,7 +278,7 @@ if( arg ) {
}
return a+b+c;
""", TypeInteger.constant(1));
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 6;", code.print());
}
@@ -298,7 +298,7 @@ if( arg ) {
}
return a+b+c;
""", TypeInteger.constant(2));
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 5;", code.print());
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java
index 2ca6f8dd..7094c0d5 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter07Test.java
@@ -17,7 +17,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -34,7 +34,7 @@ if(arg){}else{
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1,Phi(Loop,1,(Phi_a+1)));", code.print());
}
@@ -54,7 +54,7 @@ while(i < arg) {
}
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,Phi(Loop,Phi_sum,(Phi_sum+Phi(Loop,0,(Phi_j+1)))));", code.print());
}
@@ -89,7 +89,7 @@ while(a < 10) {
}
return b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,2,(Phi(Region,Phi_b,4)+1));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -106,7 +106,7 @@ while(a < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -122,7 +122,7 @@ while(a < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -148,7 +148,7 @@ int a = 1;
while(arg) a = 2;
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,2);", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -164,7 +164,7 @@ while(a < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -180,7 +180,7 @@ while(a < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -197,7 +197,7 @@ while(a < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -214,7 +214,7 @@ while(a < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java
index 09669cc7..376e472b 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter08Test.java
@@ -22,7 +22,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,(Phi(Loop,arg,Add)+1),Add,Phi_arg);", code.print());
assertTrue(code.ctrl() instanceof RegionNode);
Assert.assertEquals( "5", Eval2.eval(code, 1));
@@ -45,7 +45,7 @@ while(arg < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,1,Phi(Region,Phi_a,(Phi_a+1),Phi_a));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -64,7 +64,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,Phi(Loop,arg,(Phi_arg+1)),Add);", code.print());
assertTrue(code.ctrl() instanceof RegionNode);
}
@@ -80,7 +80,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,Phi(Loop,arg,(Phi_arg+1)),Add);", code.print());
assertTrue(code.ctrl() instanceof RegionNode);
}
@@ -99,7 +99,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -116,7 +116,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -132,14 +132,14 @@ while( arg < 10 ) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return arg;", code.print());
}
@Test
public void testRegress2() {
CodeGen code = new CodeGen("if(1) return 0; else while(arg>- -arg) arg=arg+1; return 0;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -167,7 +167,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return arg;", code.print());
}
@@ -184,7 +184,7 @@ while(arg < 10) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,Phi(Loop,1,(Phi_a+1)),Add);", code.print());
assertTrue(code.ctrl() instanceof RegionNode);
}
@@ -200,7 +200,7 @@ while(1) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (Phi(Loop,1,Add)+1);", code.print());
assertTrue(code.ctrl() instanceof CProjNode prj && prj._idx==1 );
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java
index d36cdf7f..7025b98a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter09Test.java
@@ -24,7 +24,7 @@ while(v0) {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
}
@Test
@@ -40,7 +40,7 @@ else {
}
return x;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,(arg<<1),(Shl+1));", code.print());
Assert.assertEquals( "2", Eval2.eval(code, 1));
Assert.assertEquals("23", Eval2.eval(code, 11));
@@ -49,7 +49,7 @@ return x;
@Test
public void testGVN2() {
CodeGen code = new CodeGen("return arg*arg-arg*arg;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
Assert.assertEquals("0", Eval2.eval(code, 1, 1));
}
@@ -63,7 +63,7 @@ while (arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,arg,(Phi_arg+2));", code.print());
Assert.assertEquals("11", Eval2.eval(code, 1));
}
@@ -80,7 +80,7 @@ while (arg < 10) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,arg,(Phi_arg+4));", code.print());
Assert.assertEquals("13", Eval2.eval(code, 1));
}
@@ -109,7 +109,7 @@ while (arg) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -128,14 +128,14 @@ while(v1+arg) {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@Test
public void testWhile0() {
CodeGen code = new CodeGen("while(0) continue; if(0) arg=0; return arg;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return arg;", code.print());
}
@@ -148,7 +148,7 @@ if(0) while(0) {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -156,14 +156,14 @@ return 0;
@Test
public void testPrecedence() {
CodeGen code = new CodeGen("return 3-1+2;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
}
@Test
public void testSwap2() {
CodeGen code = new CodeGen("return 1+(1+1);");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
}
@@ -181,7 +181,7 @@ while(arg) {
}
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,(-(Phi_a+3)));", code.print());
}
@@ -195,7 +195,7 @@ arg=0;
int v0=0!=0<-0;
return -0+0+0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Top;", code.print());
}
@@ -203,14 +203,14 @@ return -0+0+0;
@Test
public void testFuzz2() {
CodeGen code = new CodeGen("return 0+-0;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@Test
public void testFuzz3() {
CodeGen code = new CodeGen("int v0=0; while(0==69) while(v0) return 0; return 1;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
}
@@ -222,7 +222,7 @@ while(1) {
if(1<0) while(arg==-0) arg=arg-arg;
}
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Top;", code.print());
}
@@ -244,7 +244,7 @@ while(1) {
}
return 0!=0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Top;", code.print());
}
@@ -257,7 +257,7 @@ while(0==1) while(v0)
v0=1+v0;
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -273,14 +273,14 @@ while(1)
v1=1;
return v1+v0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Top;", code.print());
}
@Test
public void testFuzz8() {
CodeGen code = new CodeGen("while(arg) arg = arg - 1; return arg;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index cfffeb83..924884ac 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -12,7 +12,7 @@ public class Chapter10Test {
"if(1) return new S; else return 0;"
} ) {
String src = "struct S { int x; }; " + body;
- var code = new CodeGen(src).parse().opto().typeCheck();
+ var code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
// This chapter also has constructor/class-init Returns in the CompUnit.
var entry = (com.seaofnodes.simple.node.FunNode)code._start.uctrl();
org.junit.Assert.assertTrue(body,
@@ -102,7 +102,7 @@ if( (arg/13)==0 ) {
int r = g+h;
return p-r;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -122,7 +122,7 @@ bar.a = 1;
bar.a = 2;
return bar.a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 2;", code.print());
}
@@ -138,7 +138,7 @@ else
v.y = 3;
return v;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:___ 3:.x=0; 4:.y=0;]; return Test.Vector2D; ]", code.print());
}
@@ -151,7 +151,7 @@ struct s0 {
s0? v1=null;
int v3=v1.zAicm;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Might be null accessing 'zAicm'",e.getMessage()); }
}
@@ -176,7 +176,7 @@ while (arg) {
}
return bar.a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:___ 3:.a=0;]; return Test.Bar; ]", code.print());
}
@@ -189,7 +189,7 @@ if (arg) bar = null;
bar.a = 1;
return bar.a;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar",e.getMessage()); }
}
@@ -202,7 +202,7 @@ if (arg) bar = new Bar;
bar.a = 1;
return bar.a;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Might be null accessing 'a'",e.getMessage()); }
}
@@ -215,7 +215,7 @@ if (arg) bar = null;
bar.a = 1;
return bar.a;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar", e.getMessage()); }
}
@@ -228,7 +228,7 @@ if (arg) bar = null;
if( bar ) bar.a = 1;
return bar.a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1,.a);", code.print());
}
@@ -244,7 +244,7 @@ if( !bar ) rez=4;
else bar.a = 1;
return rez;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,4,3);", code.print());
}
@@ -261,7 +261,7 @@ while(arg) {
}
return ret;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) {
assertEquals("Might be null accessing 'v0'", e.getMessage()); }
}
@@ -298,7 +298,7 @@ while( i.x < i.len ) {
}
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:___ 3:.x=0; 4:.len=0;]; return Test.Iter; ]", code.print());
}
@@ -317,7 +317,7 @@ while(arg) {
}
return ret;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:___ 3:.v0=0;]; return Test.s0; ]", code.print());
}
@@ -334,7 +334,7 @@ while(arg) {
}
return ret.v0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return .v0;", code.print());
}
@@ -351,7 +351,7 @@ while(arg < 10) {
}
return ret.v0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return .v0;", code.print());
}
@@ -370,7 +370,7 @@ s0 b = new s0();
// a.x==1, b.x==1, cnt==2
return a.x * 100 + b.x * 10 + cnt;
""");
- code.parse().iter().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return 112; return MEM[ 2:___ 3:___ 4:.x=1;]; return Test.s0; ]", code.print());
}
@@ -399,7 +399,7 @@ if(0) {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -413,7 +413,7 @@ if(0) return 0;
else return new s0;
if(new s0.f0) return 0;
""");
- code.parse().iter().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("Stop[ return Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
}
@@ -429,7 +429,7 @@ while(0<arg) {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
}
@Test
@@ -441,7 +441,7 @@ while(v0.f0) {}
s0 v1 = v0;
return v1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:___ 3:.f0=0;]; return Test.s0; ]", code.print());
}
@@ -474,7 +474,7 @@ while(0) {}
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -485,7 +485,7 @@ int v0=arg==0;
while(v0) continue;
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 40db6c4b..fae7611d 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -14,7 +14,7 @@ public class Chapter12Test {
"""
return 3;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
assertEquals("3", Eval2.eval(code, 0));
}
@@ -34,7 +34,7 @@ while( arg ) {
}
return head.next.i;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Might be null accessing 'next'",e.getMessage()); }
}
@@ -56,7 +56,7 @@ _LLI? next = head.next;
if( next==null ) return 1;
return next.q;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return Phi(Region,1,.q,0);", code.print());
assertEquals("2", Eval2.eval(code, 3));
}
@@ -75,7 +75,7 @@ i0.f = f0;
f0.i = i0;
return f0.i.f.i.i;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 17;", code.print());
}
@@ -87,7 +87,7 @@ struct _N { !_N? !next; int i; };
_N n = new _N;
return n.next;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return null;", code.print());
}
@@ -100,7 +100,7 @@ struct N { !M !next; int i; new N = { !M m -> next = m; }; };
N n = new N(new M);
return n.next;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return (const)Test.M; return MEM[ 2:___ 3:.m=0;]; return Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 8:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:___ 8:.i=0;]; return Test.N; ]", code.print());
}
@@ -113,7 +113,7 @@ struct N { !M !next; int i; new N = { !M m -> next = m; }; };
N n = new N(null);
return n.next;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); }
}
@@ -126,20 +126,20 @@ struct N { !N? !next; int i; };
n.i = 3.14;
return n.i;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("3",Eval2.eval(code,0));
}
@Test
public void testNullRef4() {
CodeGen code = new CodeGen("return -null-5/null-5;");
- try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Cannot 5 // null",e.getMessage()); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
+ catch( Exception e ) { assertEquals("Cannot null + 5",e.getMessage()); }
}
@Test public void testNullRef5() {
CodeGen code = new CodeGen("return null+42;");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot null + 42",e.getMessage()); }
}
@@ -150,7 +150,7 @@ return n.i;
struct _S{};
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -162,7 +162,7 @@ return 0;
struct S1 { S2? s; };
return new S2;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); }
}
@@ -174,7 +174,7 @@ struct _S1 { !_S2? !s; };
struct _S2 { int x; };
return (new _S1).s=new _S2;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Test._S2;", code.print());
}
@@ -191,7 +191,7 @@ p2.pi.i = 2;
if (arg) p1 = new P;
return p1.pi.i + 1;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Might be null accessing 'i'",e.getMessage()); }
}
@@ -210,7 +210,7 @@ c.f0=a; c.f1=b;
return a.f0.f1.f0.f1.f0;
""");
- code.parse().opto().typeCheck().GCM().localSched();
+ code.driver(CodeGen.Phase.FinalizeIR).driver(CodeGen.Phase.Schedule).localSched();
assertEquals("return Test._B;", code.print());
assertEquals("Test._B{f0=Test._C{f0=Test._A{f0=$cyclic,f1=$cyclic},f1=$cyclic},f1=$cyclic}", Eval2.eval(code, 0));
}
@@ -276,7 +276,7 @@ z.a=k; z.b=s; z.c=e;
return a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c;
""");
- code.parse().opto().typeCheck().GCM().localSched();
+ code.driver(CodeGen.Phase.FinalizeIR).driver(CodeGen.Phase.Schedule).localSched();
assertEquals("return Test._R;", code.print());
assertEquals("Test._R{a=Test._C{a=Test._N{a=Test._Y{a=Test._J{a=Test._U{a=Test._F{a=Test._Q{a=Test._B{a=Test._M{a=Test._X{a=Test._I{a=Test._T{a=Test._E{a=Test._P{a=Test._A{a=Test._L{a=Test._W{a=Test._H{a=Test._S{a=Test._D{a=Test._O{a=Test._Z{a=Test._K{a=Test._V{a=Test._G{a=$cyclic,b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic},b=$cyclic,c=$cyclic}", Eval2.eval(code, 0));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java
index 7cfa5817..214bc69e 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter13Test.java
@@ -34,7 +34,7 @@ while( prime <= arg ) {
}
return primeCount;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,0,Phi(Loop,1,Phi(Region,Phi_primeCount,Phi_primeCount,(Phi_primeCount+1))));", code.print());
assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below
assertEquals("1", Eval2.eval(code, 2)); // 2
@@ -73,7 +73,7 @@ i=v.f;
if (arg) v.f=1;
return i;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 2;", code.print());
assertEquals("2", Eval2.eval(code, 0));
assertEquals("2", Eval2.eval(code, 1));
@@ -96,7 +96,7 @@ if (arg) {
}
return i;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,.f,0);", code.print());
}
@@ -115,7 +115,7 @@ if (v1) {
}
return v0.f;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,.f,2);", code.print());
}
@@ -133,7 +133,7 @@ if (arg+1) arg= 0;
while (arg) v.f = 2;
return i;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return arg;", code.print());
}
@@ -150,7 +150,7 @@ while(1) {
}
return v.f;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,2);", code.print());
for( int arg : new int[]{-1,0,1,3,100} )
assertEquals("0",Eval2.eval(code,arg));
@@ -167,7 +167,7 @@ while( -arg )
while(1)
arg = arg+ptr.v;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Top;", code.print());
}
@@ -185,7 +185,7 @@ while (arg) {
}
return i;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -205,7 +205,7 @@ while(arg) {
}
return arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -223,7 +223,7 @@ if (arg) {
}
return v.f;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -242,7 +242,7 @@ while (arg > 0) {
}
return v;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
//assertEquals("return new _S;", code.print());
}
@@ -259,7 +259,7 @@ while(0>=0) {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Top;", code.print());
}
@@ -277,7 +277,7 @@ u32 v0=0;
return v1;
}
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index c1354e34..e058a822 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -18,7 +18,7 @@ public class Chapter14FloatTest {
{"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"}
};
for( String[] test : tests ) {
- var code = new CodeGen(test[0]).parse().opto().typeCheck();
+ var code = new CodeGen(test[0]).driver(CodeGen.Phase.TypeCheck);
assertEquals(test[0],test[1],Eval2.eval(code,17));
assertEquals(test[0],test[2],Eval2.eval(code,-17));
}
@@ -26,7 +26,7 @@ public class Chapter14FloatTest {
@Test public void testSubZeroFloat() {
- var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck();
+ var code = new CodeGen("flt x = arg; return 0-x;").driver(CodeGen.Phase.TypeCheck);
// Compare strings so +0.0 and -0.0 remain distinct.
assertEquals("0.0", Eval2.eval(code,0));
assertEquals("-1.0", Eval2.eval(code,1));
@@ -40,7 +40,7 @@ public class Chapter14FloatTest {
{"return 2.5; return 7;", "2.5"}
} ) {
String src = test[0];
- var code = new CodeGen(src).parse().opto().typeCheck();
+ var code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals(src,test[1],Eval2.eval(code,0));
}
}
@@ -53,7 +53,7 @@ public class Chapter14FloatTest {
"""
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -65,7 +65,7 @@ return 0;
flt x = 3.14;
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -84,7 +84,7 @@ return guess;
""");
// TODO: One of the problems with <clinit> returning an OS error code,
// is it chops all results to int.
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,(flt)arg,((Phi_guess+(ToFloat/Phi_guess))*0.5f));", code.print());
assertEquals("3.0", Eval2.eval(code, 9));
assertEquals("1.414213562373095", Eval2.eval(code, 2));
@@ -97,7 +97,7 @@ return guess;
flt x = arg;
return x+1==x;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return ((flt)arg==(ToFloat+1.0f));", code.print());
assertEquals("0", Eval2.eval(code, 1));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 086bb824..bc2ad8d3 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -16,7 +16,7 @@ public class Chapter14Test {
"""
return 3.14;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3.14;", code.print());
assertEquals("3.14", Eval2.eval(code, 0));
}
@@ -32,7 +32,7 @@ if( b < 0 ) c = -1;
if( b > 2 ) c = 1;
return c;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 99;", code.print());
assertEquals("99", Eval2.eval(code, 0));
}
@@ -45,7 +45,7 @@ u8 b = 123;
b = b + 456;// Truncate
return b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 67;", code.print());
assertEquals("67", Eval2.eval(code, 0));
}
@@ -59,7 +59,7 @@ u8 b = 123;
while( b ) b = b + 456;// Truncate
return b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -72,7 +72,7 @@ b = b + 456;// Truncate
u1 c = b; // No more truncate needed
return c;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -85,7 +85,7 @@ int b = 123;
b = b+456 & 31; // Precedence
return b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
assertEquals("3", Eval2.eval(code, 0));
}
@@ -99,7 +99,7 @@ struct _Foo { u1 b; };
f.b = 123;
return f.b;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -111,7 +111,7 @@ return f.b;
i8 b = 255; // Chopped
return b; // Sign extend
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return -1;", code.print());
assertEquals("-1", Eval2.eval(code, 0));
}
@@ -124,7 +124,7 @@ i8 b = arg;
b = b + 1;// Truncate
return b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (((((arg<<56)>>56)+1)<<56)>>56);", code.print());
assertEquals("1", Eval2.eval(code, 0));
assertEquals("-128", Eval2.eval(code, 127));
@@ -138,7 +138,7 @@ u16 mask = (1<<16)-1; // AND mask
int c = 123456789 & mask;
return c; //
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 52501;", code.print());
assertEquals("52501", Eval2.eval(code, 0));
}
@@ -149,7 +149,7 @@ return c; //
"""
return (arg | 123 ^ 456) >>> 1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (((arg|123)^456)>>>1);", code.print());
assertEquals("217", Eval2.eval(code, 0));
}
@@ -162,7 +162,7 @@ flt f = arg;
arg = f & 1;
return arg;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot '&' flt",e.getMessage()); }
}
@@ -175,7 +175,7 @@ while(arg) v1=-v0;
while(v1) break;
return v1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,(((1<<arg)&1)&Shl),0);", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -194,7 +194,7 @@ if(0&0>>>0) {
}
return v0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -220,7 +220,7 @@ f32 ff32 = 3.141592653589793; if( ff32 != 3.1415927410125732) return 5;
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index f363847a..25f00474 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -17,7 +17,7 @@ public class Chapter15Test {
"""
return 3.14;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3.14;", code.print());
assertEquals("3.14", Eval2.eval(code, 0));
}
@@ -31,7 +31,7 @@ struct _C { !_C? !l; };
c.l = c;
return c;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Test._C;", code.print());
assertEquals("Test._C{l=$cyclic}", Eval2.eval(code, 0));
}
@@ -50,7 +50,7 @@ while (i < old#) {
output[i] = 1;
return output;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return []u8;", code.print());
assertEquals("\u0001", Eval2.eval(code, 0));
}
@@ -62,7 +62,7 @@ return output;
int[] is = new int[2];
return is[1];
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -75,7 +75,7 @@ int[] is = new int[2];
int[] is2 = new int[2];
return is[1];
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -89,7 +89,7 @@ a[0] = 1;
a[1] = 2;
return a[0];
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -102,7 +102,7 @@ struct _A { int i; };
!_A?[] !a = new !_A?[2];
return a;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return []*Test._A?;", code.print());
assertEquals("*Test._A {i64 !i; }?[ null,null]", Eval2.eval(code, 0));
}
@@ -131,7 +131,7 @@ else {
}
return rez;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1.2,.y,2.3);", code.print());
assertEquals("3.14", Eval2.eval(code, 0));
assertEquals("1.2", Eval2.eval(code, 1));
@@ -156,7 +156,7 @@ if( iss[arg] )
if( iss[arg][2] )
rez = iss[arg][2].y;
return rez;""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return Phi(Region,1.2,1.2,.y);", code.print());
assertEquals("3.14", Eval2.eval(code, 0));
try { Eval2.eval(code, 10); fail(); } // Fails AIOOBE in Eval2, no range checks
@@ -174,7 +174,7 @@ root._kids = new !_Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES????
root._kids[0] = new _Tree;
return root;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return Test._Tree;", code.print());
assertEquals("Test._Tree{_kids=*Test._Tree {*[]*Test._Tree?[mutable]? !_kids; }?[ Test._Tree{_kids=null},null]}", Eval2.eval(code, 0));
}
@@ -186,7 +186,7 @@ return root;
struct _S { int a; int[] !b; new _S = { int[] ary -> b=ary; }; };
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -209,7 +209,7 @@ while( i < ary# - 1 ) {
}
return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return (.[]+(.[]*1000));", code.print());
assertEquals("1006", Eval2.eval(code, 4));
}
@@ -218,7 +218,7 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
public void sieveOEratosthenes() throws IOException {
String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/sieve.smp"));
CodeGen code = new CodeGen(src+"return sieve(arg);");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return []u32;", code.print());
assertEquals("u32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20));
}
@@ -235,7 +235,7 @@ s2.f = 2.0;
if (arg) s1 = new _S;
return s1.i + s1.f;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return ((flt).i+.f);", code.print());
assertEquals("0.0", Eval2.eval(code, 0));
}
@@ -247,7 +247,7 @@ return s1.i + s1.f;
"""
return new flt;
""");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("Cannot allocate a flt",e.getMessage()); }
}
@@ -258,7 +258,7 @@ return new flt;
flt is = new int[2];
return is;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Type *[]i64 is not of declared type flt",e.getMessage()); }
}
@@ -269,7 +269,7 @@ return is;
int[] is = new int[3.14];
return is[1];
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot '<<' 3.14",e.getMessage()); }
}
@@ -279,7 +279,7 @@ return is[1];
int[] is = new int[arg];
return is[1];
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 4));
try { Eval2.eval(code,-1); fail(); }
@@ -312,7 +312,7 @@ while(v5+(0&0)) {
}
return v1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -328,7 +328,7 @@ int v8=0;
while(0<--1>>>---(v7*0==v8)) {}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
@@ -353,7 +353,7 @@ else {
}
return 0;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
}
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 91e5cd38..2942cfee 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -15,7 +15,7 @@ public class Chapter16Test {
"""
return 3.14;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3.14;", code.print());
assertEquals("3.14", Eval2.eval(code, 0));
}
@@ -28,7 +28,7 @@ return 3.14;
int x, y;
return x+y;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -39,7 +39,7 @@ return x+y;
int x=2, y=x+1;
return x+y;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 5;", code.print());
assertEquals("5", Eval2.eval(code, 0));
}
@@ -52,7 +52,7 @@ struct _X { int x=3; };
_X z = new _X;
return z.x;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
assertEquals("3", Eval2.eval(code, 0));
}
@@ -64,7 +64,7 @@ struct _X { int x=3; new _X = { int xx -> x=xx; }; };
_X z = new _X(4);
return z.x;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
assertEquals("4", Eval2.eval(code, 0));
}
@@ -85,7 +85,7 @@ _LLI? next = head.next;
if( !next ) return 1;
return next.i;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1,.i,0);", code.print());
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
@@ -115,7 +115,7 @@ _LLI? next = head.next;
if( !next ) return 1;
return next.i;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1,.i,0);", code.print());
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
@@ -140,7 +140,7 @@ struct _Square {
};
return new _Square;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Test._Square;", code.print());
assertEquals("Test._Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3));
assertEquals("Test._Square{side=4.0,diag=2.0}", Eval2.eval(code, 4));
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java
index 52acff57..ea7afe56 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17aTest.java
@@ -15,7 +15,7 @@ int x=2, y=3;
if( arg ) { int x = y; x = x*x; y=x; } // Shadow final x
return y;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,9,3);", code.print());
assertEquals("3", Eval2.eval(code, 0));
assertEquals("9", Eval2.eval(code, 1));
@@ -28,7 +28,7 @@ struct _X { int ~x; new _X = { int xx -> x=xx; }; };
_X z = new _X(3);
return z.x;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 3;", code.print());
assertEquals("3", Eval2.eval(code, 0));
}
@@ -40,7 +40,7 @@ struct _Point { int ~x, ~y; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
_Point p = new _Point(3,4);
return p;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (const)Test._Point;", code.print());
assertEquals("Test._Point{x=3,y=4}", Eval2.eval(code, 0));
}
@@ -53,7 +53,7 @@ struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
p.x++;
return p;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@@ -65,7 +65,7 @@ struct _Point { int x=3, y=4; };
p.x++;
return p;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@@ -77,7 +77,7 @@ _Point p = new _Point;
p.x++;
return p;
""");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("'Test._Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
}
@@ -89,7 +89,7 @@ _Point p = new _Point;
p.x++;
return p;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@@ -101,7 +101,7 @@ struct _Point { var x=3; var y=4; };
p.x++;
return p;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Test._Point;", code.print());
assertEquals("Test._Point{x=4,y=4}", Eval2.eval(code, 0));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index b6768d6f..e85ce31d 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -15,7 +15,7 @@ public class Chapter17bTest {
"""
return 1;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -27,7 +27,7 @@ return 1;
CodeGen code = new CodeGen("""
return arg++;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return arg;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -37,7 +37,7 @@ return arg++;
CodeGen code = new CodeGen("""
return arg+++arg++;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return ((arg<<1)+1);", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -48,7 +48,7 @@ return arg+++arg++;
// -(arg--)-(arg--)
return -arg---arg--;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (-((arg<<1)+-1));", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -61,7 +61,7 @@ xs[0]++;
xs[1]++;
return xs[0];
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 2));
}
@@ -73,7 +73,7 @@ u8[] !xs = new u8[1];
xs[0]--;
return xs[0];
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 255;", code.print());
assertEquals("255", Eval2.eval(code, 2));
}
@@ -86,7 +86,7 @@ struct _S { u16 x; };
s.x--;
return s.x;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 65535;", code.print());
assertEquals("65535", Eval2.eval(code, 2));
}
@@ -94,7 +94,7 @@ return s.x;
@Test
public void testInc6() {
CodeGen code = new CodeGen("return --arg;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return (arg+-1);", code.print());
assertEquals("-1", Eval2.eval(code, 0));
}
@@ -102,7 +102,7 @@ return s.x;
@Test
public void testInc7() {
CodeGen code = new CodeGen("u8 x=0; return --x;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 255;", code.print());
assertEquals("255", Eval2.eval(code, 0));
}
@@ -110,7 +110,7 @@ return s.x;
@Test
public void testInc8() {
CodeGen code = new CodeGen("int x; x+=2; return x+=3;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 5;", code.print());
assertEquals("5", Eval2.eval(code, 0));
}
@@ -118,14 +118,14 @@ return s.x;
@Test
public void testInc9() {
CodeGen code = new CodeGen("flt x = 1.5; x++; return x;");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 2.5f;", code.print());
assertEquals("2.5", Eval2.eval(code, 0));
}
@Test public void testInt10() {
CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("2",Eval2.eval(code,0));
}
@@ -133,73 +133,73 @@ return s.x;
// ---------------------------------------------------------------
@Test public void testVar0() {
CodeGen code = new CodeGen("var d; return d;");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
}
@Test public void testVar1() {
CodeGen code = new CodeGen("val d; return d;");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
}
@Test public void testVar2() {
CodeGen code = new CodeGen("int x; x=3; x++; return x; // Ok, no initializer so x is mutable ");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
assertEquals("4", Eval2.eval(code, 0));
}
@Test public void testVar3() {
CodeGen code = new CodeGen("int x=3; x++; return x; // Ok, primitive so x is mutable despite initializer ");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
assertEquals("4", Eval2.eval(code, 0));
}
@Test public void testVar4() {
CodeGen code = new CodeGen("struct _S{int x;}; !_S? !s; s=new _S; s.x++; return s.x; // Ok, no initializer so x is mutable ");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@Test public void testVar5() {
CodeGen code = new CodeGen("struct _S{int x; new _S={ int xx -> x=xx; };}; !_S? !s; s=new _S(3); s.x++; return s.x; // Ok, no initializer so x is mutable ");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 4;", code.print());
assertEquals("4", Eval2.eval(code, 0));
}
@Test public void testVar6() {
CodeGen code = new CodeGen("struct S{int x;}; ~S s = new S; s.x++; return s.x; // Error initializer so x is immutable ");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@Test public void testVar7() {
CodeGen code = new CodeGen("struct S{int x; new S={ int xx -> x=xx; };}; ~S s = new S(3); s.x++; return s.x; // Error initializer so x is immutable ");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@Test public void testVar8() {
CodeGen code = new CodeGen("struct _S{int x;}; !_S !s=new _S; s.x++; return s.x; // Ok, has '!' so s.x is mutable ");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@Test public void testVar9() {
CodeGen code = new CodeGen("struct _S{int x;}; var s=new _S; s.x++; return s.x; // Ok, has var so s.x is mutable ");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@Test public void testVar10() {
CodeGen code = new CodeGen("struct S{int x;}; ~S s=new S; s.x++; return s.x; // Error, has val so x is immutable ");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@@ -218,7 +218,7 @@ foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable
xfoo.bar.x++; // Error, cannot mutate through xfoo
return xfoo.bar.x;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot modify final field 'x'",e.getMessage()); }
}
@@ -239,7 +239,7 @@ foo.bar.x++; // Bumps to 5
int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5
return x4*10+x5;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 45;", code.print());
assertEquals("45", Eval2.eval(code, 0));
}
@@ -249,7 +249,7 @@ return x4*10+x5;
CodeGen code = new CodeGen("""
int i,i++;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Redefining name 'i'",e.getMessage()); }
}
@@ -264,7 +264,7 @@ struct A { !B !b; new A = { ->
}; };
A x = new A;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Field 'b' might not be initialized",e.getMessage()); }
}
@@ -296,7 +296,7 @@ A x = new A;
"!B !b; new A = { -> if (arg) { b = new B; return 0; } b = new B; };",
} ) {
CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return new A.b != null;");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("1",Eval2.eval(code,0));
assertEquals("1",Eval2.eval(code,1));
}
@@ -314,7 +314,7 @@ val x = 1;
return x;
"""
);
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Undefined name 'x'",e.getMessage()); }
}
@@ -343,7 +343,7 @@ return me;
CodeGen code = new CodeGen("""
return arg ? 1 : 2;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1,2);", code.print());
assertEquals("2", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
@@ -354,7 +354,7 @@ return arg ? 1 : 2;
CodeGen code = new CodeGen("""
return arg ? 0 : arg;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -368,7 +368,7 @@ val get = { !S s -> s.x; };
if( !s || !arg ) return 0;
return get(s);
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
}
@Test
@@ -378,7 +378,7 @@ struct _Bar { int x; };
var b = arg ? new _Bar : null;
return b ? b.x++ + b.x++ : -1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print());
assertEquals("-1", Eval2.eval(code, 0));
@@ -392,7 +392,7 @@ struct _Bar { int x; };
var b = arg ? new _Bar;
return b ? b.x++ + b.x++ : -1;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print());
assertEquals("-1", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
@@ -406,7 +406,7 @@ struct _Bar { !_Bar? !next; int x; new _Bar = { !_Bar? !n, int xx -> next=n; x=x
var b = arg ? new _Bar((arg==2) ? new _Bar(null,2), 1);
return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0"
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,.x,0,.x);", code.print());
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
@@ -419,7 +419,7 @@ return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0
flt f=arg?1:1.2;
return f; // missing widening
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Region,1.0f,1.2);", code.print());
assertEquals("1.2", Eval2.eval(code, 0));
}
@@ -431,7 +431,7 @@ return f; // missing widening
struct S{};
return arg ? 7 : new S;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Cannot return generic scalar",e.getMessage()); }
}
@@ -444,7 +444,7 @@ for( int i=0; i<arg; i++ )
sum += i;
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
assertEquals("3", Eval2.eval(code, 3));
assertEquals("45", Eval2.eval(code, 10));
@@ -457,7 +457,7 @@ for( ; i<arg; i++ )
sum += i;
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
assertEquals("3", Eval2.eval(code, 3));
assertEquals("45", Eval2.eval(code, 10));
@@ -472,7 +472,7 @@ for( int i=0; ; i++ ) {
}
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
assertEquals("3", Eval2.eval(code, 3));
assertEquals("45", Eval2.eval(code, 10));
@@ -485,7 +485,7 @@ for( int i=0; i<arg; )
sum += i++;
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,(Phi_sum+Phi(Loop,0,(Phi_i+1))));", code.print());
assertEquals("3", Eval2.eval(code, 3));
assertEquals("45", Eval2.eval(code, 10));
@@ -498,7 +498,7 @@ for( int i=0; i<arg; i++ )
sum += i;
return i;
""");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("Undefined name 'i'",e.getMessage()); }
}
@Test
@@ -506,7 +506,7 @@ return i;
CodeGen code = new CodeGen("""
for(;;arg++;) {}
""");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("Syntax error, expected `Unexpected code after expression` but found `;`",e.getMessage()); }
}
@@ -522,7 +522,7 @@ struct _A {
};
return new _A;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Test._A;", code.print());
assertEquals("Test._A{f1=null,f2=null}", Eval2.eval(code,0));
}
@@ -537,7 +537,7 @@ struct _A {
};
return new _A(new !B?[0]).array_of_b;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return []*B?;", code.print());
assertEquals("*B {}?[]", Eval2.eval(code,0));
}
@@ -556,7 +556,7 @@ for( ; ptr; ptr = ptr.next )
sum += ptr.i;
return sum;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return Phi(Loop,0,(Phi_sum+.i));", code.print());
assertEquals("45", Eval2.eval(code, 10));
}
@@ -587,7 +587,7 @@ for( int j=0; j<nprimes; j++ )
rez[j] = primes[j];
return rez;
""");
- code.parse().opto().typeCheck().GCM();
+ code.driver(CodeGen.Phase.FinalizeIR).driver(CodeGen.Phase.Schedule);
assertEquals("return []i64;", code.print());
assertEquals("i64[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 82f99fc0..ae1dca5f 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -91,7 +91,7 @@ public class Chapter18Test {
"No common type amongst int and reference"}
} ) {
try {
- new CodeGen(test[0]).parse().opto().typeCheck();
+ new CodeGen(test[0]).driver(CodeGen.Phase.TypeCheck);
fail("Expected incompatible return types: " + test[0]);
} catch( Parser.ParseException e ) {
assertEquals("Cannot return generic scalar",e.getMessage());
@@ -140,7 +140,7 @@ while(arg--) {
}
return a;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return Phi(Loop,1,Phi(Loop,2,Phi_a));", code.print());
assertEquals("1", Eval2.eval(code, 0));
assertEquals("2", Eval2.eval(code, 1));
@@ -157,7 +157,7 @@ return a;
{int -> int}? x2 = null; // null function ptr
return x2;
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return null;", code.print());
assertEquals("null", Eval2.eval(code, 0 ) );
}
@@ -170,7 +170,7 @@ return x2;
x*x;
};
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return { sq}; return (Parm_x(sq,i64)*x); ]", code.print());
assertEquals("{ ptr i64 -> i64 #[ 3]}", Eval2.eval(code, 3));
}
@@ -197,7 +197,7 @@ return _sq(arg)+_sq(3);
int cnt=1; // Global scope; exactly one of these
return { -> cnt++; }; // Escape global-scope variable is OK
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return { ptr -> i64 #[ 3]}; return .cnt; ]", code.print());
assertEquals("{ ptr -> i64 #[ 3]}", Eval2.eval(code, 0));
}
@@ -216,7 +216,7 @@ val B = ctr(); // Make a counter
A(); // Increment counter A
return A()*10 + B(); // Incr A and B; result: 2*10+1 == 21
""");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("Variable 'cnt' is out of function scope and must be a final constant",e.getMessage()); }
}
@@ -228,7 +228,7 @@ return A()*10 + B(); // Incr A and B; result: 2*10+1 == 21
val cnt=2;
return { -> cnt; }();
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return 2;", code.print());
assertEquals("2", Eval2.eval(code, 0));
}
@@ -241,7 +241,7 @@ return { -> cnt; }();
var fcn = arg ? { int x -> x*x; } : { int x -> x+x; };
return fcn(3);
""");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("Stop[ return #2; return (Parm_x($fun3,i64,3)*x); return (Parm_x($fun4,i64,3)<<1); ]", code.print());
assertEquals("6", Eval2.eval(code, 0));
assertEquals("9", Eval2.eval(code, 1));
@@ -251,7 +251,7 @@ return fcn(3);
@Test
public void testFcn5() {
CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1 : x*_fact(x-1); }; return _fact(arg);");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
String ir = code.print();
assertTrue(ir.equals("Stop[ return #2; return Phi(Region,1,(Parm_x(_fact,arg,(x-1))*#2)); ]") ||
ir.equals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]") ||
@@ -269,7 +269,7 @@ return fcn(3);
@Test
public void testFcn5Float() {
CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1.0 : x*_fact(x-1); }; return _fact(arg);");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("1.0", Eval2.eval(code, 0));
assertEquals("1.0", Eval2.eval(code, 1));
assertEquals("2.0", Eval2.eval(code, 2));
@@ -280,7 +280,7 @@ return fcn(3);
@Test
public void testFcn5Minus() {
CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(x-1) : 1; }; return _neg(arg);");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals( "1", Eval2.eval(code, 0));
assertEquals("-1", Eval2.eval(code, 1));
assertEquals( "1", Eval2.eval(code, 2));
@@ -295,7 +295,7 @@ struct _S { int i; new _S = { int x -> i=x; }; };
val _newS = { int x -> return new _S(x); };
return _newS(1).i;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -310,7 +310,7 @@ val _g = {->2;};
if( arg ? _f : _g ) return 1;
return 2;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -388,7 +388,7 @@ for(;;) {
}
return 1;
""");
- try { code.parse().opto(); fail(); }
+ try { code.driver(CodeGen.Phase.FinalizeIR); fail(); }
catch( Exception e ) { assertEquals("No active loop for a break or continue",e.getMessage()); }
}
@@ -404,7 +404,7 @@ for(;;) {
}
return 0;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Might be null calling { ptr i64 -> i64 #[ 3]}?",e.getMessage()); }
}
@@ -416,7 +416,7 @@ return 0;
val f = { int i, int j -> return i+j; };
return f();
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Expecting 3 arguments, but found 1",e.getMessage()); }
}
@@ -432,7 +432,7 @@ val x = { -> return 1; };
S? s = new S;
return s.x;
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*Test.S'",e.getMessage()); }
}
@@ -445,7 +445,7 @@ val g = { ->
};
return g();
""");
- try { code.parse().opto().typeCheck(); fail(); }
+ try { code.driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) { assertEquals("No defined return type",e.getMessage()); }
}
@@ -459,7 +459,7 @@ val is_even = { int x -> x ? is_odd (x-1) : true ; };
val is_odd = { int x -> x ? is_even(x-1) : false; };
return is_even(arg);
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("Stop[ return #2; return Phi(Region,0,1,#2); ]", code.print());
assertEquals("1", Eval2.eval(code, 0));
assertEquals("0", Eval2.eval(code, 1));
@@ -481,7 +481,7 @@ for(;;) {
if (s) return s.f;
}
""");
- code.parse().opto().typeCheck().GCM();
+ code.driver(CodeGen.Phase.FinalizeIR).driver(CodeGen.Phase.Schedule);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 2419dc2d..6f290279 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -37,7 +37,7 @@ public class Chapter19Test {
CodeGen code = new CodeGen("""
return 0;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 2));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 5acfbc8f..0458941e 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -69,7 +69,7 @@ return f(s);
// The entry of g can disappear before its Return is folded into f.
// Exercise both worklist orders; deleting the entry does not kill 123.
for( int seed=0; seed<64; seed++ ) {
- CodeGen code = new CodeGen(src,seed,true).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.TypeCheck);
assertEquals("seed "+seed, "123", Eval2.eval(code,0));
}
}
@@ -92,7 +92,7 @@ return f(s);
@Test
public void testJig() {
CodeGen code = new CodeGen("return 0;");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 2));
}
@@ -122,7 +122,7 @@ return f(s);
@Test public void testBasic1() {
String src = "return arg | 2;";
- testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
+ testCPU(src,"x86_64_v2", "SystemV",1,"return mov((ori,arg));");
testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
testCPU(src,"arm" , "SystemV",0,"return (ori,arg);");
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 1f4913f0..07ae42eb 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -48,7 +48,7 @@ public class Chapter21Test {
else if(i==4) b.x=40;
}
return sum;
- """).parse().opto();
+ """).driver(CodeGen.Phase.FinalizeIR);
// The i==2 read still sees 10: three reads of 10, then three of 30.
assertEquals("120",Eval2.eval(code,0));
assertEquals("60",Eval2.eval(code,1));
@@ -298,7 +298,7 @@ return new _s0.v1;
@Test public void testBasic1() {
String src = "return arg | 2;";
- testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
+ testCPU(src,"x86_64_v2", "SystemV",1,"return mov((ori,arg));");
testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
testCPU(src,"arm" , "SystemV",0,"return (ori,arg);");
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 7484a6ae..00d77f65 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -88,7 +88,7 @@ public class Chapter22Test {
@Test public void testSubZeroTypeError() {
try {
- new CodeGen("return null-0;").parse().opto().typeCheck();
+ new CodeGen("return null-0;").driver(CodeGen.Phase.TypeCheck);
fail("Subtraction must reject null even when the other operand is zero");
} catch( Parser.ParseException e ) {
assertEquals("Cannot null - 0",e.getMessage());
@@ -135,6 +135,7 @@ return new s0.v1;
static int testCPUSize( String src, String cpu, String os, int spills ) {
+ // Includes the file initializer's hidden $init=true store.
return testCPU(src,cpu,os,spills, "return 0;" )._encoding._bits.size();
}
@@ -146,9 +147,9 @@ struct _Person { i32 age;};
p.age = (arg<<17)>>17;
return 0;
""";
- assertEquals(65, testCPUSize(src, "x86_64_v2","win64", 3 ));
- assertEquals(76, testCPUSize(src, "riscv", "SystemV", 4 ));
- assertEquals(76, testCPUSize(src, "arm" , "SystemV", 4 ));
+ assertEquals(76, testCPUSize(src, "x86_64_v2","win64", 3 ));
+ assertEquals(92, testCPUSize(src, "riscv", "SystemV", 4 ));
+ assertEquals(92, testCPUSize(src, "arm" , "SystemV", 4 ));
// do assertEquals here
EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away", 0, 4, false);
@@ -178,9 +179,9 @@ return 0;
int trap_arm = A5.step(100);
assertEquals(0,trap_arm);
- assertEquals(65, testCPUSize(src, "x86_64_v2","win64",3 ));
- assertEquals(76, testCPUSize(src, "riscv", "SystemV",4 ));
- assertEquals(76, testCPUSize(src, "arm", "SystemV",4 ));
+ assertEquals(76, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(92, testCPUSize(src, "riscv", "SystemV",4 ));
+ assertEquals(92, testCPUSize(src, "arm", "SystemV",4 ));
}
@@ -202,9 +203,9 @@ return 0;
int trap_arm = A5.step(100);
assertEquals(0,trap_arm);
- assertEquals(55, testCPUSize(src, "x86_64_v2","win64",3 ));
- assertEquals(72, testCPUSize(src, "riscv", "SystemV",5 ));
- assertEquals(72, testCPUSize(src, "arm", "SystemV",5));
+ assertEquals(66, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(84, testCPUSize(src, "riscv", "SystemV",5 ));
+ assertEquals(84, testCPUSize(src, "arm", "SystemV",5));
// do assertEquals here
}
@@ -282,7 +283,7 @@ aa.c = cc;
bb.c = cc;
return cc.cz;
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("return 25;", code.print());
assertEquals("25", Eval2.eval(code, 0));
}
@@ -321,7 +322,7 @@ return 0;
@Test
public void testStrLoad() {
CodeGen code = new CodeGen("return \"abc\"[1];");
- code.parse().opto();
+ code.driver(CodeGen.Phase.FinalizeIR);
assertEquals("return .[];", code.print());
}
@@ -341,7 +342,7 @@ val _sum = { i32[~] is -> // final array
};
return _sum(is);
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code.print());
assertEquals("14", Eval2.eval(code, 0));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
index 053621bd..df02d3b4 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -120,10 +120,10 @@ return a && b ? 0 : 1; // Expected answer 0
@Test
public void testLogicalValueSemantics() {
- assertEquals("42", Eval2.eval(new CodeGen("return 7 && 42;").parse().opto(), 0));
- assertEquals("0", Eval2.eval(new CodeGen("return 0 && 42;").parse().opto(), 0));
- assertEquals("7", Eval2.eval(new CodeGen("return 7 || 42;").parse().opto(), 0));
- assertEquals("42", Eval2.eval(new CodeGen("return 0 || 42;").parse().opto(), 0));
+ assertEquals("42", Eval2.eval(new CodeGen("return 7 && 42;").driver(CodeGen.Phase.FinalizeIR), 0));
+ assertEquals("0", Eval2.eval(new CodeGen("return 0 && 42;").driver(CodeGen.Phase.FinalizeIR), 0));
+ assertEquals("7", Eval2.eval(new CodeGen("return 7 || 42;").driver(CodeGen.Phase.FinalizeIR), 0));
+ assertEquals("42", Eval2.eval(new CodeGen("return 0 || 42;").driver(CodeGen.Phase.FinalizeIR), 0));
}
@Test
@@ -133,7 +133,7 @@ struct S {};
S? s = arg ? new S;
return !(!s || !arg) ? 1 : 0;
""");
- code.parse().opto().typeCheck();
+ code.driver(CodeGen.Phase.TypeCheck);
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
}
@@ -146,7 +146,7 @@ struct S { !S? !fld; new S = { !S? f -> fld=f; }; };
val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null));
return ptr && ptr.fld ? "true" : "false";
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("true", Eval2.eval(code, 0));
// Evaluate on RISC5 emulator
@@ -276,7 +276,7 @@ struct _Scan {
return _s.peek('q');
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("return Phi(Region,0,1);", code.print());
assertEquals("1", Eval2.eval(code, 0));
testCPU(src,"x86_64_v2", "win64",23,null);
@@ -301,7 +301,7 @@ struct _Scan {
return s.skip().x;
""";
- try { new CodeGen(src).parse().opto().typeCheck(); fail(); }
+ try { new CodeGen(src).driver(CodeGen.Phase.TypeCheck); fail(); }
catch( Exception e ) {
// Bad error message, but basically requires a mutable 'x' field.
org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa"));
@@ -331,7 +331,7 @@ struct _Scan {
return _s.require('[');
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("return (.[]==91);", code.print());
assertEquals("1", Eval2.eval(code, 0));
testCPU(src,"x86_64_v2", "win64",4, null);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 3e21c73e..2f125ed0 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -257,7 +257,7 @@ while (cnt) {
}
return x;
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("return 1;", code.print());
}
@@ -272,7 +272,7 @@ while( cnt-- )
x = 2-x;
return x;
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals("return 1;", code.print());
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
index 30e1ed79..275be96a 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -9,6 +9,7 @@ import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
import com.seaofnodes.simple.codegen.ElfReader;
import com.seaofnodes.simple.codegen.ParseAll;
import com.seaofnodes.simple.node.CallNode;
+import com.seaofnodes.simple.node.FunNode;
import com.seaofnodes.simple.node.LoopNode;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.Ary;
@@ -27,15 +28,71 @@ import static org.junit.Assert.*;
public class Chapter25Test {
+ @Test public void testFlagSpills() throws IOException {
+ com.seaofnodes.simple.codegen.FlagSpillTestSupport.run();
+ }
+
@Ignore
@Test
public void testJig() {
- String src = "int x = arg/17; if( arg == 0 ) return arg + x; return arg + x;";
- CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ String src = "{ i32 -> i32 } close = \"C\"; val main = { u8[] a -> if (a# > 1) return close(0); return a[0]; };";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,"riscv","SystemV");
assertEquals("return (arg+3);", code.print());
assertEquals("3",Eval2.eval(code,0));
}
+ @Test public void testConditionalClassInitX86() throws IOException {
+ Path build=Path.of("build/objs");
+ Files.createDirectories(build);
+ Path dir=Files.createTempDirectory(build,"init_flags_");
+ // Keep B separately compiled so its first-touch check survives.
+ new CodeGen(null,dir.toString(),null,"B","int value=17+arg;",123,true,TypeInteger.BOT)
+ .driver("x86_64_v2","win64",false,false);
+ // GCM can hoist the fused B.$init compare above the test of take.
+ // Its flags then cross another flag definition and need a legal split.
+ new CheckedCodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","val run={int take -> return take ? B.value : 0; };",123,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.Encoding,"x86_64_v2","win64");
+ // Remove successful fixtures; preserve the object if compilation fails.
+ Files.delete(dir.resolve("B.o"));
+ Files.delete(dir);
+ }
+
+ @Test public void testPublicInstanceMethod() {
+ String src = """
+ struct Scan {
+ val visible = { u8 c -> c==255; };
+ val _hidden = { u8 c -> c==1; };
+ };
+ struct _Private {
+ val hiddenClassMethod = { u8 c -> c==2; };
+ };
+ return new Scan.visible(44);
+ """;
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code = new CodeGen(src,seed,true).driver(CodeGen.Phase.Parse);
+ FunNode visible = null;
+ int checked = 0;
+ for( FunNode fun : code._linker ) {
+ if( fun==null || fun._name==null ) continue;
+ if( fun._name.equals("visible") ) {
+ assertTrue(code.publicFIDX(fun.sig().fidx()));
+ visible = fun;
+ checked++;
+ }
+ if( fun._name.equals("_hidden") || fun._name.equals("hiddenClassMethod") ) {
+ assertFalse(code.publicFIDX(fun.sig().fidx()));
+ checked++;
+ }
+ }
+ assertEquals(3,checked);
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertTrue(visible.unknownCallers());
+ assertEquals(TypeInteger.U8,visible.parm(3)._type);
+ assertEquals("0",Eval2.eval(code,0));
+ }
+ }
+
@Test public void testNeverReturnPointerRiscV() {
new CodeGen("""
struct _S { int x; };
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java b/chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
index 1dec3312..47e10f97 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java
@@ -1,18 +1,23 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.FunNode;
+import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.type.Field;
import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.TypeTuple;
import com.seaofnodes.simple.util.Ary;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
@@ -66,18 +71,34 @@ public class ClassInitDependenciesTest {
}
}
- // The cycle can start at any member, but must follow dependency direction.
+ // The report lists internal SCC edges, including ordinary helper calls.
+ private HashMap<String,HashSet<String>> reportEdges(String message) {
+ String[] lines=message.split("\n");
+ assertEquals(message,"Cyclic class initialization:",lines[0]);
+ HashMap<String,HashSet<String>> edges=new HashMap<>();
+ for( int i=1; i<lines.length; i++ ) {
+ if( !lines[i].startsWith(" ") || lines[i].startsWith(" ") ) continue; // Diagnostic/source/caret.
+ String[] edge=lines[i].trim().split(" -> ");
+ assertEquals(message,2,edge.length);
+ edges.computeIfAbsent(edge[0],key -> new HashSet<>()).add(edge[1]);
+ }
+ return edges;
+ }
+
+ // Follow helpers in the diagnostic, stopping at the next class initializer.
+ private boolean reportedTouch(HashMap<String,HashSet<String>> edges, String from, String to, HashSet<String> seen) {
+ if( !seen.add(from) || !edges.containsKey(from) ) return false;
+ for( String dst : edges.get(from) )
+ if( dst.equals(to) || (dst.contains(" [fidx ") && reportedTouch(edges,dst,to,seen)) ) return true;
+ return false;
+ }
+
private void assertCycle(String message, String expected) {
- String prefix="Cyclic class initialization: ";
- assertTrue(message,message.startsWith(prefix));
- String actual=message.substring(prefix.length()).trim();
+ HashMap<String,HashSet<String>> edges=reportEdges(message);
String[] path=expected.split(" -> ");
- for( int start=0; start<path.length-1; start++ ) {
- ArrayList<String> rotated=new ArrayList<>();
- for( int i=0; i<path.length; i++ ) rotated.add(path[(start+i)%(path.length-1)]);
- if( actual.equals(String.join(" -> ",rotated)) ) return;
- }
- fail("Expected cycle "+expected+" (up to rotation), got "+actual);
+ for( int i=1; i<path.length; i++ )
+ assertTrue("Missing "+path[i-1]+" -> "+path[i]+" in:\n"+message,
+ reportedTouch(edges,path[i-1],path[i],new HashSet<>()));
}
// Follow ordinary helpers, stopping at the next initializer. This checks
@@ -204,8 +225,10 @@ public class ClassInitDependenciesTest {
object(stubs,stubs,"B","int value=arg;");
// Each object is independently acyclic. Combining their real bodies
// must recheck the graph rather than trust either object's local order.
- object(dir,stubs,"A","int value=arg;\nvalue=B.value;");
- object(dir,stubs,"B","int value=arg; value=A.value;");
+ String asrc="int value=arg;\nvalue=B.value;";
+ CodeGen acode=module(dir,stubs,"A",Map.of("A",asrc));
+ String afile=acode._compunits.get("A").sourceFile();
+ module(dir,stubs,"B",Map.of("B","int value=arg; value=A.value;"));
for( String entry : new String[]{"A","B"} ) {
try {
new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
@@ -213,8 +236,21 @@ public class ClassInitDependenciesTest {
fail("Expected imported initialization cycle");
} catch( Parser.ParseException ex ) {
assertCycle(ex.getMessage(),"A -> B -> A");
+ assertTrue(ex.getMessage(),ex.getMessage().contains("value=B.value;\n -------^"));
+ assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+":2:8: error: cyclic class initialization\n"));
}
}
+ // The imported object must still report its saved path and offset
+ // when the original source is no longer available.
+ Files.delete(Path.of(afile));
+ try {
+ new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return A.value;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ fail("Expected imported initialization cycle");
+ } catch( Parser.ParseException ex ) {
+ assertCycle(ex.getMessage(),"A -> B -> A");
+ assertTrue(ex.getMessage(),ex.getMessage().contains("\n"+afile+": error: cyclic class initialization (source unavailable at byte offset "+asrc.indexOf('.')+")"));
+ }
}
@Test public void testImportedDiamondAndHelpers() throws Exception {
@@ -276,31 +312,239 @@ public class ClassInitDependenciesTest {
@Test public void testCycleReportDirection() throws Exception {
cycle("A",Map.of("A","int value=B.value;", "B","int value=C.value;",
"C","int value=A.value;"),"A -> B -> C -> A");
+ for( long seed : new long[]{1,123,987654321} ) {
+ // Stack order A,B,C is not a cycle: B has no edge to C. All four
+ // internal edges must be shown; edges to Root/D must be omitted.
+ try {
+ compile("A",Map.of("A","int value=B.value+C.value+D.value;",
+ "B","int value=A.value;", "C","int value=A.value;",
+ "D","int value=arg;"),seed);
+ fail("Expected initialization cycle");
+ } catch( Parser.ParseException ex ) {
+ assertEquals(Map.of("Root.A",Set.of("Root.B","Root.C"),
+ "Root.B",Set.of("Root.A"), "Root.C",Set.of("Root.A")),
+ reportEdges(ex.getMessage()));
+ }
+ }
}
- @Test public void testOverlappingFunctionAndClassCycles() {
- // Fixed function graph: Opto/inlining must not erase the overlap that
- // exercises SCC traversal. f -> g -> f is legal; f -> A -> g -> f isn't.
- CodeGen code=new CodeGen("return 0;").driver(CodeGen.Phase.Opto);
+ @Test public void testCycleSourceLocations() throws Exception {
+ for( String name : new String[]{"read", "read_noInline"} ) {
+ try {
+ compile("A",Map.of("A","int value=Helper."+name+"();",
+ "Helper","val "+name+"={ ->\n B.value;\n};",
+ "B","int value=A.value;"),123);
+ fail("Expected initialization cycle");
+ } catch( Parser.ParseException ex ) {
+ String message=ex.getMessage();
+ assertCycle(message,"Root.A -> Root.B -> Root.A");
+ // Inlining must keep Helper's source identity, not substitute A.
+ assertTrue(message,message.contains("Helper.smp:2:4"));
+ assertTrue(message,message.contains(" B.value;\n ---^"));
+ if( name.endsWith("noInline") ) {
+ assertTrue(message,message.contains("A.smp:1:31"));
+ assertTrue(message,message.contains("int value=Helper.read_noInline();"));
+ }
+ }
+ }
+ }
+
+ @Test public void testSourceError() {
+ String src="// π\r\n\treturn 1;\r\n";
+ CodeGen code=new CodeGen(src).driver(CodeGen.Phase.Parse);
CompUnit cu=code._compunits.get("Test");
- code._linker.clear();
- String[] names={"f", "g", "A.<clinit>"};
+ int pos=src.substring(0,src.indexOf('1')).getBytes(java.nio.charset.StandardCharsets.UTF_8).length;
+ assertEquals("Test.smp:2:9: error: example\n return 1;\n ---------------^",SourceError.format(cu,pos,"example"));
+ assertEquals("Test.smp:3:1: error: example\n \n ^",SourceError.format(cu,src.getBytes(java.nio.charset.StandardCharsets.UTF_8).length,"example"));
+ }
+
+ @Test public void testSelfInitialization() throws Exception {
+ for( String name : new String[]{"read","read_noInline"} )
+ for( long seed : new long[]{1,123,987654321} )
+ compile("A",Map.of("A","int value=arg; val "+name+"={ -> A.value; }; int result="+name+"();"),seed);
+ }
+
+ @Test public void testInitializationMemory() throws Exception {
+ String[] sources={
+ "int value=B.value+B.value;",
+ "int value; if(arg) value=B.value; else value=B.value+1; value=value+B.value;",
+ "int value; if(arg) value=B.value; value=value+B.value;"
+ };
+ int[] counts={1,2,2};
+ for( long seed : new long[]{1,123,987654321} )
+ for( int i=0; i<sources.length; i++ ) {
+ CodeGen code=compile("A",Map.of("A",sources[i],"B","int value=arg;"),seed);
+ FunNode fun=code.link(code._compunits.get("Root/A"));
+ CompUnit b=code._compunits.get("Root/B");
+ int[] touches={0};
+ code._stop.walk(n -> {
+ if( n instanceof CLInitNode init && init.fun()==fun && init.cu()==b ) {
+ touches[0]++;
+ TypeTuple state=(TypeTuple)init._type;
+ assertSame(TypeMem.BOT,state._types[1]);
+ TypeMem flag=(TypeMem)state._types[2];
+ assertEquals(b._clz.field("$init")._alias,flag._alias);
+ assertSame(TypeInteger.TRUE,flag._t);
+ assertNotNull(init.ready());
+ }
+ return null;
+ });
+ assertEquals(sources[i],counts[i],touches[0]);
+ }
+ }
+
+ @Test public void testForwardInstanceTouch() {
+ String src="""
+ struct S { int x=7; };
+ val _get={ -> _make().x; };
+ val _make={ -> new S; };
+ return _get();
+ """;
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(src,seed,true).driver(CodeGen.Phase.Parse);
+ CLInitNode init=code._stop.walk(n -> n instanceof CLInitNode c ? c : null);
+ assertNotNull("The unresolved receiver needs a speculative check",init);
+ assertNotNull("The readiness load is present from parsing",init.ready());
+ code.driver(CodeGen.Phase.TypeCheck);
+ assertNull("An instance needs neither CLInit nor its speculative $init load",
+ code._stop.walk(n -> n instanceof CLInitNode ||
+ (n instanceof LoadNode ld && ld._name.equals("$init")) ? n : null));
+ assertEquals("7",Eval2.eval(code,0));
+ }
+ }
+
+ @Test public void testInitializerFlagStore() throws Exception {
+ // A same-named struct must retain the file class's hidden flag.
+ CodeGen code=compile("A",Map.of("A","struct A { int value; }; int value=arg;"),123);
+ CompUnit a=code._compunits.get("Root/A");
+ Field flag=a._clz.field("$init");
+ assertNotNull(flag);
+ assertFalse(flag._final);
+ FunNode fun=code.link(a);
+ int[] stores={0};
+ code._stop.walk(n -> {
+ if( n instanceof StoreNode st && st._name.equals("$init") && st._alias==flag._alias ) {
+ stores[0]++;
+ assertSame(TypeInteger.TRUE,st.val()._type);
+ assertSame(fun,CFGNode.earlyCFG(st).fun());
+ }
+ return null;
+ });
+ assertEquals(1,stores[0]);
+ }
+
+ @Test public void testInitializationExecution() throws Exception {
+ Path dir=tempDir("execution-");
+ object(dir,dir,"State","int count=0;");
+ object(dir,dir,"B","State.count++; int value=17+arg;");
+ String src="int value=arg ? B.value+B.value : 0; return value+100*State.count;";
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main",src,seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertNotNull(code._stop.walk(n -> n instanceof CLInitNode ? n : null));
+ assertEquals("0",Eval2.eval(code,0));
+ assertNull(code._stop.walk(n -> n instanceof CLInitNode ? n : null));
+ assertEquals("134",Eval2.eval(code,1));
+ }
+
+ object(dir,dir,"Main","val run={int take -> "+src.replace("arg ?","take ?")+" };");
+ Path cfile=dir.resolve("check.c");
+ Files.writeString(cfile,"""
+ #include <stdint.h>
+ __attribute__((CALL_CONV)) extern int64_t run(int64_t take);
+ int main(void) {
+ return run(0)!=0 || run(1)!=134 || run(1)!=134 || run(0)!=100;
+ }
+ """);
+ String exe=dir.resolve(TestC.OS.startsWith("Windows") ? "check.exe" : "check").toString();
+ TestC.linkExe(dir.resolve("Main.o").toString(),TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
+ cfile.toString(),new Ary<>(new String[]{dir.resolve("B.o").toString(),dir.resolve("State.o").toString()}),exe);
+ assertEquals("",TestC.exec(exe));
+ for( String cpu : new String[]{"arm","riscv"} )
+ new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main",src,123,true,TypeInteger.BOT).driver(CodeGen.Phase.Encoding,cpu,"SystemV");
+ }
+
+ @Test public void testParentInitializationExecution() throws Exception {
+ Path dir=tempDir("execution-");
+ object(dir,dir,"State","int count=0;");
+ module(dir,dir,"P",Map.of("P","val C=C; State.count=State.count*10+1;",
+ "P/C","State.count=State.count*10+2; int value=arg;"));
+ for( long seed : new long[]{1,123,987654321} ) {
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return P.C.value+State.count;",seed,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("12",Eval2.eval(code,99));
+ }
+ object(dir,dir,"Main","val run={ -> P.C.value+State.count; };");
+ Path cfile=dir.resolve("check.c");
+ Files.writeString(cfile,"""
+ #include <stdint.h>
+ __attribute__((CALL_CONV)) extern int64_t run(void);
+ int main(void) { return run()!=12 || run()!=12; }
+ """);
+ String exe=dir.resolve(TestC.OS.startsWith("Windows") ? "check.exe" : "check").toString();
+ TestC.linkExe(dir.resolve("Main.o").toString(),TestC.CALL_CONVENTION.equals("win64") ? "ms_abi" : "sysv_abi",
+ cfile.toString(),new Ary<>(new String[]{dir.resolve("P.o").toString(),dir.resolve("State.o").toString()}),exe);
+ assertEquals("",TestC.exec(exe));
+ }
+
+ @Test public void testNonreturningObject() throws Exception {
+ Path dir=tempDir("nonreturning-");
+ object(dir,dir,"Spin","val spin_noInline={ -> while(1) {} return 7; };");
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","Spin.spin_noInline(); return 7;",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertNull(Eval2.eval(code,0,10));
+ }
+
+ @Test public void testImportedPrivateInitializer() throws Exception {
+ Path dir=tempDir("private-");
+ module(dir,dir,"Library",Map.of("Library","val read={ -> _Hidden.value; };",
+ "Library/_Hidden","int value=17+arg;"));
+ CodeGen code=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return Library.read();",123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("17",Eval2.eval(code,99));
+ // Even a request stopping at Select must drop imported bodies. They
+ // remain available only to the ideal evaluator above, not this object.
+ for( String cpu : new String[]{"x86_64_v2","arm","riscv"} ) {
+ CodeGen selected=new CodeGen(null,dir.toString(),new Ary<>(new String[]{dir.toString()}),
+ "Main","return Library.read();",123,true,TypeInteger.BOT).driver(CodeGen.Phase.Select,cpu,"SystemV");
+ assertNull(selected._serial);
+ for( CompUnit cu : selected._compunits.values() )
+ if( cu._src==null ) assertNull(cu._stop);
+ selected.driver(CodeGen.Phase.Encoding,cpu,"SystemV");
+ }
+ }
+
+ @Test public void testOverlappingFunctionAndClassCycles() throws Exception {
+ // Fixed function graph: Opto/inlining must not erase the overlap that
+ // exercises SCC traversal. One clinit is legal, two in the SCC are not.
+ CodeGen code=compile("A",Map.of("A","val b=B; int value=arg;", "B","int value=arg;"),123);
+ int f=code._linker._len, g=f+1, a=f+2, b=f+3;
+ String[] names={"f", "g", "A.<clinit>", "B.<clinit>"};
for( int i=0; i<names.length; i++ ) {
- TypeFunPtr sig=TypeFunPtr.make1((byte)2,false,new Type[0],TypeInteger.BOT,i+2);
+ TypeFunPtr sig=TypeFunPtr.make1((byte)2,false,new Type[0],TypeInteger.BOT,i+f);
+ CompUnit cu=code._compunits.get(i==3 ? "Root/B" : "Root/A");
FunNode fun=new FunNode(null,sig,names[i],cu);
fun._type=Type.CONTROL;
- code._linker.setX(i+2,fun);
+ code._linker.setX(i+f,fun);
}
ClassInitDependencies deps=new ClassInitDependencies(code);
- deps._edgess[2]=new int[]{3}; // f -> g
- deps._edgess[3]=new int[]{2}; // g -> f
+ deps._edgess[f]=new int[]{g}; // f -> g
+ deps._edgess[g]=new int[]{f}; // g -> f
assertNull("Ordinary mutual recursion is legal",deps.check());
- deps._edgess[4]=new int[]{3}; // A -> g
- deps._edgess[2]=new int[]{3,4};
+ deps._edgess[a]=new int[]{a}; // A -> A
+ assertNull("A singleton initializer self-edge is legal",deps.check());
+ deps._edgess[a]=new int[]{g}; // A -> g
+ deps._edgess[f]=new int[]{g,a};
+ assertNull("A cycle containing only one initializer is legal",deps.check());
+ deps._edgess[a]=new int[]{b}; // A -> B
+ deps._edgess[b]=new int[]{g}; // B -> g -> f -> A
String forward=deps.check();
- deps._edgess[2]=new int[]{4,3};
+ deps._edgess[f]=new int[]{a,g};
String reverse=deps.check();
assertTrue("Both successor orders must reject the class cycle: "+forward+", "+reverse,
forward!=null && reverse!=null);
+ assertCycle(forward,"Root.A -> Root.B -> Root.A");
+ assertCycle(reverse,"Root.A -> Root.B -> Root.A");
}
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java
index 48f739cf..e2d822d0 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java
@@ -88,7 +88,7 @@ public class CompoundAssignmentTest {
}
private static void check(String src, long arg, long expected) {
- var code = new CodeGen(src).parse().opto().typeCheck();
+ var code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals(src, Long.toString(expected), Eval2.eval(code, arg));
}
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java
index 21e677ce..366bd7f4 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java
@@ -114,6 +114,9 @@ public abstract class Eval2 {
// Current active Frame, to avoid having to pass it everywhere
public static Frame F;
+ // Class pointers and initializer self parameters share one object per run.
+ private static final HashMap<String,Object[]> CLASSES = new HashMap<>();
+
public static String eval( CodeGen code, long arg ) { return eval(code,arg,1000); }
public static String eval( CodeGen code, long arg, int timeout ) {
if( code._start.uctrl()==null ) return ""; // The empty program
@@ -122,6 +125,7 @@ public abstract class Eval2 {
code.driver(CodeGen.Phase.LocalSched);
// Set global, so don't have to pass everywhere
CODE = code;
+ CLASSES.clear();
// Timer for when we run too long. Ticks once per backedge or function call
int loopCnt = 0;
@@ -140,7 +144,7 @@ public abstract class Eval2 {
TypeStruct clz = ((TypeMemPtr)fun.sig().arg(0))._obj;
// Map from the <clinit> self parameter to the
// singleton class structure.
- F.put(fun.parm(2),alloc(clz));
+ F.put(fun.parm(2),classObject(clz));
}
@@ -203,9 +207,9 @@ public abstract class Eval2 {
// Parms read from Call args -
// - except RPC is the actual prior frame
- // - except <clinit> takes the singleton class object and external args
+ // - except entry <clinit> takes the external program argument
Object val;
- if( isClz && phi instanceof ParmNode parm && parm._idx==3 )
+ if( isClz && prior instanceof StartNode && phi instanceof ParmNode parm && parm._idx==3 )
PHICACHE[i] = arg; // Reading the initial arguments to <clinit>
else {
Node n = phi instanceof ParmNode parm
@@ -349,6 +353,8 @@ public abstract class Eval2 {
yield new Object[0];
}
+ } else if( Parser.startsClzPrefix(tmp._obj._name) ) {
+ yield classObject(tmp._obj);
} else {
// Constant object with all zeros
yield alloc(tmp._obj);
@@ -417,6 +423,12 @@ public abstract class Eval2 {
return ptr;
}
+ private static Object[] classObject(TypeStruct type) {
+ Object[] ptr = CLASSES.get(type._name);
+ if( ptr==null ) CLASSES.put(type._name,ptr=alloc(type));
+ return ptr;
+ }
+
private static Object not( NotNode not ) {
Object n = val(not.in(1));
return ( n==null || (n instanceof Double D && D==0.0) || n instanceof Long L && L==0 )
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Main.java b/chapter25/src/test/java/com/seaofnodes/simple/Main.java
index 589c043b..1248a6cf 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Main.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Main.java
@@ -11,7 +11,7 @@ public class Main {
String src = Files.readString(Path.of(args[0]));
int arg = Integer.valueOf(args[1]);
CodeGen code = new CodeGen(src);
- code.parse().opto().typeCheck().GCM().localSched();
+ code.driver(CodeGen.Phase.FinalizeIR).driver(CodeGen.Phase.Schedule).localSched();
System.out.println(code._stop);
System.out.println(Eval2.eval(code,arg,100000));
}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
index 9406f480..adeba136 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
@@ -58,7 +58,7 @@ public class MemoryEncodingTest {
// Exercise both unresolved Store aliases and nested value-Phi cleanup.
for( long seed : new long[]{0,9,126} )
for( int p=0; p<sources.length; p++ ) {
- CodeGen code=new CodeGen(sources[p],seed,true).parse().opto();
+ CodeGen code=new CodeGen(sources[p],seed,true).driver(CodeGen.Phase.FinalizeIR);
if( p==0 ) assertNull(code._stop.walk(n ->
n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr &&
ptr.in(0) instanceof NewNode nn &&
@@ -86,7 +86,7 @@ public class MemoryEncodingTest {
if (arg>1) { v=p.x; p.x=41; p.y=5; }
else { v=q.x; q.x=42; }
return v;
- """).parse().opto();
+ """).driver(CodeGen.Phase.FinalizeIR);
StopNode stop = code._stop;
assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
assertEquals("-1",Eval2.eval(code,-1));
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java b/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java
index 60e4c5aa..8bd77c06 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/MutabilityTest.java
@@ -6,12 +6,12 @@ import com.seaofnodes.simple.codegen.CodeGen;
public class MutabilityTest {
private static void ok(String src, String result) {
- var code = new CodeGen(src).parse().opto().typeCheck();
+ var code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
assertEquals(result,Eval2.eval(code,0));
}
private static void bad(String src) {
try {
- new CodeGen(src).parse().opto().typeCheck();
+ new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
} catch( RuntimeException expected ) {
String msg = expected.getMessage();
assertTrue("Expected a permission error, got: "+expected,
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/PhaseTest.java b/chapter25/src/test/java/com/seaofnodes/simple/PhaseTest.java
new file mode 100644
index 00000000..7252378a
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/PhaseTest.java
@@ -0,0 +1,64 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.graph.GraphObserver;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CodeGen.Phase;
+import com.seaofnodes.simple.node.Node;
+import java.util.ArrayList;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class PhaseTest {
+ private static final String SRC = "int sum=0; for(int i=0; i<arg; i++) sum+=i; return sum;";
+
+ @Test public void testIdealPhasesAndResume() {
+ ArrayList<String> phases = new ArrayList<>();
+ CodeGen code = new CodeGen(SRC);
+ code._obs = new GraphObserver<Node>() {
+ @Override public void before(Node n) {}
+ @Override public void after(Node n, Node repl, boolean applied) {}
+ @Override public void phase(String phase) { phases.add(phase); }
+ };
+ code.driver(Phase.TypeCheck);
+ code.driver(Phase.TypeCheck); // A repeated request does not rerun anything.
+ code.driver(Phase.LocalSched);
+ assertEquals("Parse Iter Opto IterPostOpto FinalizeIR TypeCheck Lower LoopTree Unlink Schedule LocalSched",
+ String.join(" ",phases));
+ assertNull(code._serial);
+ assertNull(code._mach);
+ assertEquals("45",Eval2.eval(code,10));
+ }
+
+ @Test public void testEncodingWithoutSerialization() {
+ for( String cpu : new String[]{"x86_64_v2","arm","riscv"} ) {
+ CodeGen code = new CodeGen(SRC).driver(Phase.TypeCheck);
+ code.driver(Phase.Unlink,cpu,"SystemV");
+ code.driver(Phase.Select,cpu,"SystemV");
+ code.driver(Phase.Encoding,cpu,"SystemV");
+ assertNull(code._serial);
+ byte[] staged = code._encoding._bits.toByteArray();
+ CodeGen direct = new CodeGen(SRC).driver(Phase.Encoding,cpu,"SystemV");
+ assertArrayEquals(staged,direct._encoding._bits.toByteArray());
+ }
+ }
+
+ @Test public void testExplicitSerializationAndMemoryExport() {
+ CodeGen code = new CodeGen(SRC).driver(Phase.Serialize);
+ assertNotNull(code._serial);
+ assertEquals(Phase.Serialize,code._phase);
+ code.driver(Phase.Encoding,"riscv","SystemV");
+ assertNotNull(code._serial);
+
+ CodeGen memory = new CodeGen(SRC).driver("riscv","SystemV",true,false);
+ assertNull(memory._serial);
+ assertEquals(Phase.Export,memory._phase);
+
+ CodeGen late = new CodeGen(SRC).driver(Phase.Encoding,"riscv","SystemV");
+ try {
+ late.exportELF(false,false);
+ fail("Cannot recover ideal IR after lowering and instruction selection");
+ } catch( IllegalStateException expected ) {
+ assertTrue(expected.getMessage().contains("Serialize before Lower"));
+ }
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Simple.java b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java
index b7e10122..2d688bbe 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/Simple.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java
@@ -45,8 +45,11 @@ Options:
--dump-time - print compilation and execution times
--dump-after-parse - dump intermediate representation after parse
--dump-after-iter - dump intermediate representation after pessimistic iter pass
- --dump-after-opto - dump intermediate representation after opto pass
+ --dump-after-opto - dump intermediate representation after SCCP
+ --dump-after-iter-post-opto - dump after SCCP-driven peepholes and inlining
+ --dump-after-finalize-ir - dump after field layout and IR finalization
--dump-after-type-check - dump intermediate representation after type check pass
+ --dump-after-lower - dump expanded class initialization before loop trees
--dump-after-select - dump intermediate representation after instruction selection pass
--dump-after-gcm - dump intermediate representation after GCM pass
--dump-after-local-sched - dump intermediate representation after local scheduling pass
@@ -114,7 +117,10 @@ Options:
case "--dump-after-parse": dump |= 1<<Phase.Parse .ordinal() ; break;
case "--dump-after-iter": dump |= 1<<Phase.Iter .ordinal() ; break;
case "--dump-after-opto": dump |= 1<<Phase.Opto .ordinal() ; break;
+ case "--dump-after-iter-post-opto": dump |= 1<<Phase.IterPostOpto.ordinal(); break;
+ case "--dump-after-finalize-ir": dump |= 1<<Phase.FinalizeIR.ordinal() ; break;
case "--dump-after-type-check": dump |= 1<<Phase.TypeCheck .ordinal() ; break;
+ case "--dump-after-lower": dump |= 1<<Phase.Lower .ordinal() ; break;
case "--dump-after-loop-tree": dump |= 1<<Phase.LoopTree .ordinal() ; break;
case "--dump-after-select": dump |= 1<<Phase.Select .ordinal() ; break;
case "--dump-after-gcm": dump |= 1<<Phase.Schedule .ordinal() ; break;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java
index 070b7a9e..03df05a7 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/StaticDataTest.java
@@ -2,6 +2,9 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.StaticData;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.StoreNode;
+import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeStruct;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.Ary;
@@ -16,14 +19,25 @@ public class StaticDataTest {
private static final String SRC="val n=42; val f={int x -> x+n;}; return 0;";
private static StaticData.ObjectData root(CodeGen code) {
- return code._encoding._data.entries().stream()
- .filter(obj -> obj.symbol.endsWith("Test.$class")).findFirst().orElseThrow();
+ for( var obj : code._encoding._data.entries() )
+ if( obj.symbol.endsWith("Test.$class") ) return obj;
+ throw new AssertionError("Missing Test class object");
}
// Read the image before <clinit>, then call the function through its data slot.
@Test public void testEmulatorImage() {
for( String cpu : new String[]{"arm","riscv"} ) {
- CodeGen code=new CodeGen(SRC).driver(cpu,"SystemV",true,false);
+ CodeGen code=new CodeGen(SRC).driver(CodeGen.Phase.TypeCheck);
+ assertNull("Constant fields fold before instruction selection",
+ code._stop.walk(n -> n instanceof StoreNode st &&
+ (st._name.equals("n") || st._name.equals("f")) ? n : null));
+ assertNotNull(code._stop.walk(n -> n instanceof ConstantNode &&
+ n._type instanceof TypeMem mem && mem.isConstant() &&
+ mem._t==TypeInteger.constant(42) ? n : null));
+ assertNotNull("The runtime initialization flag still needs its Store",
+ code._stop.walk(n -> n instanceof StoreNode st &&
+ st._name.equals("$init") ? n : null));
+ code.driver(cpu,"SystemV",true,false);
var e=code._encoding;
var obj=root(code);
TypeStruct ts=(TypeStruct)obj.type;
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/codegen/FlagSpillTestSupport.java b/chapter25/src/test/java/com/seaofnodes/simple/codegen/FlagSpillTestSupport.java
new file mode 100644
index 00000000..445acb61
--- /dev/null
+++ b/chapter25/src/test/java/com/seaofnodes/simple/codegen/FlagSpillTestSupport.java
@@ -0,0 +1,141 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.isa.X86;
+import com.seaofnodes.isa.eval.EvalArm64;
+import com.seaofnodes.simple.TestC;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.cpus.arm.arm;
+import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import static org.junit.Assert.*;
+
+// Force overlapping flags, then force every GPR unavailable across the gap.
+public class FlagSpillTestSupport {
+ private static class Op extends MachConcreteNode {
+ final RegMask _def, _use, _kill;
+ Op(RegMask def,RegMask use,RegMask kill,Node... ins) {
+ super(ins); _def=def; _use=use; _kill=kill; _type=TypeInteger.BOT;
+ }
+ @Override public String op() { return "flagsProbe"; }
+ @Override public RegMask outregmap() { return _def; }
+ @Override public RegMask regmap(int i) { return _use; }
+ @Override public RegMask killmap() { return _kill; }
+ @Override public void encoding(Encoding enc) { throw new AssertionError(); }
+ }
+
+ public static void run() throws IOException {
+ StringBuilder asm = new StringBuilder(".text\n");
+ for( boolean isArm : new boolean[]{false,true} )
+ for( boolean stack : new boolean[]{false,true} ) {
+ CodeGen code = new CodeGen("");
+ code._callingConv="SystemV";
+ code._mach=isArm ? new arm(code) : new x86_64_v2(code);
+ RegionNode block = new RegionNode(null,code._start,code._start);
+ code._cfg.add(block);
+ int flagsReg=isArm ? 64 : 32, firstStack=flagsReg+1;
+ RegMask flags = new RegMask(flagsReg);
+ Op def = new Op(flags,null,null,block);
+ RegMask killMask = stack ? (isArm ? new RegMask(0x7fffffffL,1L) : new RegMask(0x1ffffffefL)) : flags;
+ Op kill = new Op(null,null,killMask,block);
+ new Op(null,flags,null,block,def);
+ RegAlloc alloc = new RegAlloc(code);
+ alloc.regAlloc();
+
+ int splits=0, slots=0;
+ for( Node n : block._outputs ) if( n instanceof SplitNode split ) {
+ int dst=alloc.regnum(n), src=alloc.regnum(n.in(1));
+ assertTrue(split.outregmap().test(dst));
+ assertTrue(split.regmap(1).test(src));
+ for( int fp=isArm ? 32 : 16; fp<flagsReg; fp++ ) {
+ assertFalse("FP output allowed",split.outregmap().test(fp));
+ assertFalse("FP input allowed",split.regmap(1).test(fp));
+ }
+ if( dst>=firstStack || src>=firstStack ) slots++;
+ splits++;
+ }
+ assertTrue(splits>=2);
+ assertEquals(stack,slots!=0);
+
+ // Keep the allocated registers, supplying a frame for encoding stack offsets.
+ FunNode frame = new FunNode(null,null,"flags",null,(Node)null);
+ code._regAlloc=new RegAlloc(code);
+ for( Node n : block._outputs )
+ if( alloc.regnum(n)>=0 ) code._regAlloc.newLRG(n,frame)._reg=alloc.regnum(n);
+ Encoding save = new Encoding(code), restore = new Encoding(code);
+ Encoding enc = save;
+ for( Node n : block._outputs ) {
+ if( n==kill ) enc=restore;
+ if( n instanceof SplitNode split ) split.encoding(enc);
+ }
+ if( isArm ) {
+ for( int bits=0; bits<16; bits++ ) {
+ EvalArm64 cpu = new EvalArm64(new byte[1024],512);
+ System.arraycopy(save._bits.buf(),0,cpu._buf,0,save._bits.size());
+ System.arraycopy(restore._bits.buf(),0,cpu._buf,save._bits.size(),restore._bits.size());
+ cpu.N=(bits&8)!=0; cpu.Z=(bits&4)!=0; cpu.C=(bits&2)!=0; cpu.V=(bits&1)!=0;
+ assertEquals(0,cpu.step(save._bits.size()/4));
+ cpu.N=cpu.Z=cpu.C=cpu.V=false;
+ if( stack ) for( int r=0; r<31; r++ ) cpu.regs[r]=0;
+ assertEquals(0,cpu.step(restore._bits.size()/4));
+ assertEquals((bits&8)!=0,cpu.N); assertEquals((bits&4)!=0,cpu.Z);
+ assertEquals((bits&2)!=0,cpu.C); assertEquals((bits&1)!=0,cpu.V);
+ assertEquals(512,cpu.regs[31]);
+ }
+ } else {
+ Encoding body = new Encoding(code);
+ int[] preserved={3,5,6,7,12,13,14,15};
+ for( int r : preserved ) pushPop(body,r,false);
+ X86.imm(body,0x81,5,4,16); // Reserve spill storage.
+ X86.flags(body,TestC.OS.startsWith("Windows") ? 1 : 7,true);
+ bytes(body,save);
+ if( stack ) {
+ for( int r=0; r<16; r++ ) if( r!=4 ) X86.constant(body,r,0);
+ } else X86.reg(body,0x39,0,0); // CMP rax,rax clobbers only flags.
+ bytes(body,restore);
+ X86.flags(body,0,false); // Return the restored flags.
+ X86.imm(body,0x81,0,4,16);
+ for( int i=preserved.length-1; i>=0; i-- ) pushPop(body,preserved[i],true);
+ X86.ret(body);
+ String name=stack ? "flags_stack" : "flags_reg";
+ asm.append(".globl ").append(name).append('\n').append(name).append(":\n.byte ");
+ for( int i=0; i<body._bits.size(); i++ ) {
+ if( i!=0 ) asm.append(',');
+ asm.append(body._bits.buf()[i]&255);
+ }
+ asm.append('\n');
+ }
+ }
+ Path dir=Files.createTempDirectory(Path.of("build/objs"),"flag_spills_");
+ Files.writeString(dir.resolve("flags.s"),asm);
+ Files.writeString(dir.resolve("flags.c"),"""
+ typedef unsigned long long u64;
+ u64 __attribute__((CALL_CONV)) flags_reg(u64);
+ u64 __attribute__((CALL_CONV)) flags_stack(u64);
+ int main(void) {
+ const int positions[]={0,2,4,6,7,11};
+ for(int bits=0; bits<64; bits++) {
+ u64 flags=0x202;
+ for(int i=0;i<6;i++) if(bits&(1<<i)) flags|=1ULL<<positions[i];
+ if((flags_reg(flags)&0x8d5)!=(flags&0x8d5)) return 1;
+ if((flags_stack(flags)&0x8d5)!=(flags&0x8d5)) return 2;
+ }
+ return 0;
+ }
+ """);
+ String exe=dir.resolve(TestC.OS.startsWith("Windows") ? "flags.exe" : "flags").toString();
+ TestC.exec("gcc","-DCALL_CONV="+(TestC.OS.startsWith("Windows") ? "ms_abi" : "sysv_abi"),
+ "-o",exe,dir.resolve("flags.c").toString(),dir.resolve("flags.s").toString());
+ TestC.exec(exe);
+ }
+
+ private static void bytes(Encoding dst,Encoding src) {
+ for( int i=0; i<src._bits.size(); i++ ) dst.add1(src._bits.buf()[i]&255);
+ }
+ private static void pushPop(Encoding enc,int reg,boolean pop) {
+ if( reg>=8 ) enc.add1(0x41);
+ enc.add1((pop ? 0x58 : 0x50)+(reg&7));
+ }
+}
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
index 93380c87..292f1ae7 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
@@ -29,6 +29,12 @@ public class RegAllocTestSupport {
return code;
}
+ private static SplitNode testSplit(CodeGen code, MachNode def) {
+ LRG lrg = new LRG((short)0,null);
+ lrg._machDef = def;
+ return code._mach.split(lrg,"test",(byte)0);
+ }
+
public static void uncoloredFixedNeighbor() throws Exception {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
@@ -225,11 +231,11 @@ public class RegAllocTestSupport {
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(A,null,null,false,code._start);
alloc.newLRG(def,null)._reg=0;
- SplitNode hi = code._mach.split(null,"test",(byte)0);
+ SplitNode hi = testSplit(code,def);
hi.setDef(0,code._start); hi.setDef(1,def);
alloc.newLRG(hi,null)._reg=1;
new Op(null,null,killed ? A : null,false,code._start);
- SplitNode lo = code._mach.split(null,"test",(byte)0);
+ SplitNode lo = testSplit(code,hi);
lo.setDef(0,code._start); lo.setDef(1,hi);
alloc.newLRG(lo,null)._reg=0;
Op use = new Op(null,A,null,false,code._start,lo);
@@ -243,7 +249,7 @@ public class RegAllocTestSupport {
uncolored.newLRG(def0,null)._mask=A;
Op fixed = new Op(A,null,null,false,pre._start);
uncolored.newLRG(fixed,null)._mask=A;
- SplitNode copy = pre._mach.split(null,"test",(byte)0);
+ SplitNode copy = testSplit(pre,def0);
copy.setDef(0,pre._start); copy.setDef(1,def0);
assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy));
}
@@ -284,7 +290,7 @@ public class RegAllocTestSupport {
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(A,null,null,false,code._start);
alloc.newLRG(def,null)._reg=0;
- SplitNode copy = code._mach.split(null,"test",(byte)0);
+ SplitNode copy = testSplit(code,def);
copy.setDef(0,code._start); copy.setDef(1,def);
alloc.newLRG(copy,null)._reg=1;
if( kind==3 ) new Op(null,null,A,false,code._start);
@@ -307,7 +313,7 @@ public class RegAllocTestSupport {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(new RegMask(3L),null,null,false,code._start);
- SplitNode copy = code._mach.split(null,"test",(byte)0);
+ SplitNode copy = testSplit(code,def);
copy.setDef(0,code._start); copy.setDef(1,def);
Op use = new Op(null,new RegMask(3L),null,false,code._start,copy);
LRG from = alloc.newLRG(def,null), to = alloc.newLRG(copy,null);
diff --git a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
index ec9e3910..55c2c040 100644
--- a/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
+++ b/chapter25/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
@@ -133,7 +133,7 @@ public class Fuzzer {
double parse_nop_ratio = (double)parse_nops/parse_peeps;
double parse_peeps_per_node = (double)parse_peeps/nids;
- code.opto();
+ code.iter().opto().iterPostOpto().finalizeIR();
int iter_peeps= code._iter_cnt;
int iter_nops = code._iter_nop_cnt;
double iter_nop_ratio = (double)iter_nops/iter_peeps;