diff options
| author | Cliff Click <cliffc@acm.org> | 2026-10-18 22:09:58 -0700 |
|---|---|---|
| committer | Cliff Click <cliffc@acm.org> | 2026-10-18 22:09:58 -0700 |
| commit | 300d2a9c3672acacc8bdd8d5370a424651a00571 (patch) | |
| tree | f33d895f6ec4e08692efa4c090c681257553097d /chapter25 | |
| parent | 0be52f8dd82f70d4c557fe916d47dc425401f848 (diff) | |
| download | Simple-300d2a9c3672acacc8bdd8d5370a424651a00571.tar.gz Simple-300d2a9c3672acacc8bdd8d5370a424651a00571.zip | |
Cleanup & backportclinit
The phase rework, made new phases explicit and add them to the CodeGen driver. Fix the flag-spilling on X86 and ARM. Fix the GCM conservative anti-deps and backport.
Diffstat (limited to 'chapter25')
73 files changed, 1534 insertions, 1778 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 9996a3a3..00000000 --- a/chapter25/initialization.md +++ /dev/null @@ -1,842 +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. -- After Opto, build the combined function-call and class-initialization graph, - including module boundaries. Reject an SCC only when it contains more than - one `<clinit>`. Ordinary recursion, initializer self-edges, and helper cycles - involving only one initializer are legal. Unresolved conditional branches - contribute edges; eliminated branches and call targets do not. -- 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. -Same-class helper calls are allowed by the cycle checker, whether or not they -inline. Ordinary field-initialization and type checks remain separate. - -The cycle diagnostic lists every internal edge of the first rejected SCC, -including helper calls. Every listed edge participates in a cycle; this reports -all cyclic connections in that component without enumerating individual paths. -On failure, rescan the component's functions to choose one surviving touch or -call site for each edge. `SourceError.format(CompUnit, byteOffset, message)` prints -`file:line:character: error: message` starting in column one, followed by the -source line and a separate caret line. Lexer snapshots retain their -original CompUnit through inlining; Call and CLInit serialization retain the -offset and source identity/path. Imported source is read only on error. Parent -requirements are labeled explicitly rather than assigned a fabricated position. -This is a focused diagnostic utility, not a migration of all parser errors. - -## 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 - -### Compiler phase boundaries - -The Chapter 25 driver runs `Parse -> Iter -> Opto -> IterPostOpto -> FinalizeIR --> TypeCheck -> LoopTree`, followed by the existing serialization and backend -phases. `Opto` performs SCCP, resolves numeric modes, and seeds the worklist; -it does not invoke either Iter pass. `IterPostOpto` performs peepholes and -inlining using the SCCP results. `FinalizeIR` resolves field layouts and offsets, -runs the resulting cleanup, roots surviving functions, and releases temporary -CU memory keeps. Numeric resolution remains in Opto because it can require -another SCCP propagation before the types are ready for Iter. - -Direct callers explicitly run `parse().iter().opto().iterPostOpto().finalizeIR()` -before `typeCheck()`. The driver handles that sequence when a later phase is -requested. CLInit expansion after successful TypeCheck remains unimplemented; -its phase and the serialization boundary are separate decisions. - -### Memory IR checkpoint (2026-10-17) - -This checkpoint supersedes the older representation details below. Keep CLInit -opaque through Iter/Opto and the class-cycle check; introduce neither its -conditional call CFG nor frequency estimates in those phases. - -Use the existing mutable `$init` field and alias. The implemented node shape is: - -```text -ready = Load(C.$init, incoming memory) -CLInit(ctrl, wholeMemory, classPtr, ready) - -> (ctrl, wholeMemory, initAliasMemory) -``` - -The third result is an alias slice containing true on normal continuation. -Merge it over the whole-memory result using MemMerge. The whole-memory result -conservatively accounts for arbitrary initializer effects; changing just the -flag slice would be incorrect. The flag result is a fact about the operation's -result, not an extra runtime store at each touch. Keep the class pointer and -source location on the operation; resolve the flag alias from the class type, -including after forward-reference resolution and object loading. - -The parser creates the ready load up front, using the existing receiver node. -For an unresolved receiver, ordinary Load/FldOff machinery resolves the alias -and offset later; no additional named FRef is needed. If the receiver becomes -an ordinary instance, the speculative CLInit and flag load disappear together. -If the parser cannot yet identify the flag alias, precise Loads and MemPhis -recover that slice from the whole-memory projection once the type resolves. - -Known-true input bypasses the operation, returning incoming control/memory; -false or unknown retains it. Parser memory uses the result, so subsequent flag -loads, MemPhis, and ordinary peepholes can establish redundant checks without -a separate parser set of initialized classes. A merge establishes true only -when every live incoming path does. Initially an ordinary unknown call may -lose a known-true fact; that is conservative, and preserving monotone flag -facts across arbitrary calls can follow later. - -The static flag starts false. Its true store belongs inside `<clinit>`, not at -each CLInit site. It is installed at body entry so the body knows its own flag -is true. The parent requirement remains structural; runtime lowering must -execute it before entering the body. This implements the accepted same-class -reentry rule; it does not exempt cross-class cycles from validation. Retain -initializer boundaries and their dependency edges until the post-Opto check; -do not propagate an in-progress caller's flag into another initializer to -erase that check. Ordinary field-initialization checking remains separate. - -Memory/projection wiring, parser updates, the initializer-owned flag store, -and copying/serialization are implemented. Rebuild serialized objects for the -new four-input node shape. Runtime lowering, entry adaptation, and executable -run-once behavior remain a subsequent step. - -Public function fields on instances of public classes must retain their Start -caller, just like public class-object fields. `CodeGen.publicFIDX` now checks -both, preserving private-field/class and hidden-constructor exclusions. This -fixes the `sys.scan.match` widening exposed by conservative CLInit memory -effects; neither imported-type resets nor SCCP assertions needed changes. - -Validation: 395 raw0 tests, 37 raw1 tests, 9 standalone tests, the fuzzer -regression wrapper, 25 Chapter 25 tests, and 38 additional tests all pass. -The additional group includes 16 class-init tests -covering cycles, serialization, repeated touches, branch merges, flag stores, -and removal of a speculative flag load when a forward call returns an instance. - -### Deferred constant instance fields - -A final instance field proven to have the same constant value in every instance -can eventually be stored on the class object, or folded directly to a -ConstantNode, and removed from instance layout. Instance-field access semantics -remain unchanged. Public function reachability must not depend on that storage -choice: an externally accessible function field is callable by anyone whether -stored in the instance or class. This optimization is not needed for the current -initialization checkpoint; instance layout is unchanged. - -### Deferred CFG frequencies - -After Opto and cycle validation, lower surviving CLInit operations to flag -tests, conditional calls, and control/memory merges. Mark the needs-init edge -rare. Once the resulting CFG and loop tree are stable, estimate frequencies -before GCM and reuse them in Encoding block layout. Frequencies are derived -backend data, not ideal-IR types or inputs to Iter/Opto. - -Start each function at 1.0; use 50/50 for unknown tests, 90/10 for non-null/null, -and a small positive minimum probability for initialization and known-failure -edges. Use an estimated loop trip count of 10 unless a proven count is known; -distinguish body iterations from header tests and account for early exits. -Compute cyclic flow using loop-aware propagation, not an unbounded walk around -backedges. Regions sum incoming edge flows, and branch outputs sum to their -input. Bound probabilities and loop amplification and normalize outgoing -probabilities; do not independently clamp successor counts and break those -relationships. Unreachable blocks remain zero. Bound the resulting estimates -against overflow. Later profiling can supply probabilities/trip estimates -through the same interface; switch distributions are deferred. - -GCM chooses the lowest-frequency legal placement, with deterministic tie breaks. -Encoding prefers common fall-through paths, forward cold branches, and backward -loop edges. Recompute/invalidate estimates if later CFG changes require it. - -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, IterPostOpto, FinalizeIR, 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 eca2b9ce..5ceda6a9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -291,8 +291,7 @@ public class Parser { } // The initializer owns its run-once flag store. Parent initialization - // is a structural requirement, to be executed before this body when - // CLInit runtime lowering is added. + // 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"); 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 3909f881..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.*; @@ -32,8 +29,9 @@ public class CodeGen { 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 @@ -185,7 +183,7 @@ public class CodeGen { public final XCtrlNode XCTRL; public Node constant( Type t ) { - assert _phase==null || _phase.ordinal() <= Phase.FinalizeIR.ordinal(); + assert _phase==null || _phase.ordinal() <= Phase.Lower.ordinal(); return ConstantNode.make(t).peephole(); } public ConstantNode con( Type t ) { return (ConstantNode)constant(t); } @@ -239,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); } @@ -256,25 +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.IterPostOpto.ordinal() ) { iterPostOpto(); p2 = dump(dump); } - if( p2 < p1 && p2 < Phase.FinalizeIR.ordinal() ) { finalizeIR(); 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; } @@ -283,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; } @@ -430,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)); } // --------------------------- @@ -464,126 +482,14 @@ 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(); - - // 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); - - _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0; - if( _obs != null ) _obs.phase(_phase.name()); - return 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; - } - - 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; - } - + public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); } // SCCP and numeric-mode resolution; leave cleanup work queued for Iter. - public CodeGen opto() { - assert _phase == Phase.Iter; - _phase = Phase.Opto; - long t0 = System.currentTimeMillis(); + public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); } - Opto.opto(this); - - _times[Phase.Opto.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)); } - public CodeGen iterPostOpto() { - assert _phase == Phase.Opto; - _phase = Phase.IterPostOpto; - long t0 = System.currentTimeMillis(); - // Use SCCP types and the sharp call graph for peepholes and inlining. - _iter.iterate(this); - _times[Phase.IterPostOpto.ordinal()] = System.currentTimeMillis() - t0; - if( _obs != null ) _obs.phase(_phase.name()); - return this; - } - - public CodeGen finalizeIR() { - assert _phase == Phase.IterPostOpto; - _phase = Phase.FinalizeIR; - long t0 = System.currentTimeMillis(); - - // Freeze field sizes and replace symbolic field offsets by constants. - for( int i=0; i<_start.nOuts(); i++ ) { - Node use = _start.out(i); - if( use instanceof ConFldOffNode off ) { - Node con = off.asOffset(); - assert con != null : off._ts._name+"."+off._fname; - off.subsume(con); - i--; - } - } - _iter.iterate(this); // Propagate field offsets and clean up. - - // Retain surviving functions for emission without adding unknown callers. - for( FunNode fun : _linker ) - if( fun != null && !fun.isDead() && !fun._type.isHigh() ) - keepInCompUnit(fun); - for( CompUnit cu : _compunits.values() ) { - ProjNode mem = cu._start.proj(1); - if( mem != null ) mem.unkeep(); - } - // Stable print order: initializers first, then other Returns by NID. - for( Node stop : _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) )); - - _times[Phase.FinalizeIR.ordinal()] = System.currentTimeMillis() - t0; - if( _obs != null ) _obs.phase(_phase.name()); - return this; - } + public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); } // Root a surviving function without claiming that it has unknown callers. void keepInCompUnit(FunNode fun) { @@ -597,58 +503,7 @@ public class CodeGen { // --------------------------- // Last check for bad programs - public CodeGen typeCheck() { - assert _phase == Phase.FinalizeIR; - _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? @@ -659,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)); } // --------------------------- @@ -740,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)); } // --------------------------- @@ -950,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 @@ -998,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/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 33d971e9..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,38 +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 CLInitNode init: - assert late[init._nid]!=null; - lca = anti_dep(load,late[init._nid],init.cfg0(),lca,init,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; @@ -297,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 c41a29ac..ef222f60 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java @@ -73,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); 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/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/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java index 850a5711..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,7 +154,7 @@ 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); @@ -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/CLInitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java index 9a41cc18..7bc17c75 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java @@ -99,7 +99,65 @@ public class CLInitNode extends CFGNode implements MultiNode { return CodeGen.CODE.con(((TypeTuple)_type)._types[2]); } - @Override public Node idealize() { return null; } + @Override public Node idealize() { + 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. 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 555d0c65..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); 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 b4bb5e72..71f6d146 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java @@ -101,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 4357570e..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 FinalizeIR. 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.FinalizeIR.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/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/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java index b31e14d5..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. @@ -140,27 +136,24 @@ public abstract class MemOpNode extends TypeNode { // 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); + 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/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/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 f2bd5841..d3a356ed 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java @@ -41,7 +41,7 @@ public class StopCUNode extends StopNode { // below must not forget private bits already discovered precisely from // that Return on an earlier SCCP step. if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() && - CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.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)); @@ -102,7 +102,7 @@ public class StopCUNode extends StopNode { // input refinement add bits, reversing Iter's monotonic direction. // This normalization is needed only for Opto's optimistic grand cycle. if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() || - CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() ) return mem; + 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 418b1e92..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); } 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 8c9bed95..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() { 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 7b351533..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 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 e9231d9a..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().iter().opto().iterPostOpto().finalizeIR().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/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 5b57e701..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 c503b56c..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 1;", code.print()); } @@ -44,7 +44,7 @@ else a=3; return a; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); +""", 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().iter().opto().iterPostOpto().finalizeIR(); +""", TypeInteger.constant(1)).driver(CodeGen.Phase.FinalizeIR); assertEquals("return 3;", code.print()); } @@ -144,7 +144,7 @@ else a=5; return a; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 3;", code.print()); } @@ -186,7 +186,7 @@ else a=5; return a; """, TypeInteger.constant(3)); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 4;", code.print()); } @@ -202,7 +202,7 @@ if( arg ) { } return a+b; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 4;", code.print()); } @@ -239,7 +239,7 @@ if( arg ) { } return a+b; """, TypeInteger.constant(0)); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 1;", code.print()); } @@ -257,7 +257,7 @@ if( arg ) { } return a+b+c; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 498038c1..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 61815840..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -167,7 +167,7 @@ while(arg < 10) { } return arg; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return arg;", code.print()); } @@ -184,7 +184,7 @@ while(arg < 10) { } return a; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 839b834d..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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); } @Test @@ -40,7 +40,7 @@ else { } return x; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -128,14 +128,14 @@ while(v1+arg) { } return 0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return arg;", code.print()); } @@ -148,7 +148,7 @@ if(0) while(0) { } return 0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 4;", code.print()); } @Test public void testSwap2() { CodeGen code = new CodeGen("return 1+(1+1);"); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 3;", code.print()); } @@ -181,7 +181,7 @@ while(arg) { } return a; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return Top;", code.print()); } @@ -244,7 +244,7 @@ while(1) { } return 0!=0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -273,14 +273,14 @@ while(1) v1=1; return v1+v0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 1b542d63..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 2;", code.print()); } @@ -138,7 +138,7 @@ else v.y = 3; return v; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return Phi(Region,4,3);", code.print()); } @@ -261,7 +261,7 @@ while(arg) { } return ret; """); - try { code.parse().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return .v0;", code.print()); } @@ -351,7 +351,7 @@ while(arg < 10) { } return ret.v0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); } @Test @@ -441,7 +441,7 @@ while(v0.f0) {} s0 v1 = v0; return v1; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 19c531f8..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().typeCheck(); fail(); } + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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 9786006c..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return Phi(Region,.f,0);", code.print()); } @@ -115,7 +115,7 @@ if (v1) { } return v0.f; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return arg;", code.print()); } @@ -150,7 +150,7 @@ while(1) { } return v.f; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return Top;", code.print()); } @@ -185,7 +185,7 @@ while (arg) { } return i; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -205,7 +205,7 @@ while(arg) { } return arg; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -223,7 +223,7 @@ if (arg) { } return v.f; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -242,7 +242,7 @@ while (arg > 0) { } return v; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); //assertEquals("return new _S;", code.print()); } @@ -259,7 +259,7 @@ while(0>=0) { } return 0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return Top;", code.print()); } @@ -277,7 +277,7 @@ u32 v0=0; return v1; } """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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 ccca2e2b..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 3ba03b48..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 3c1254d2..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return 0;", code.print()); } @@ -353,7 +353,7 @@ else { } return 0; """); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + 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 ad9b99bc..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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 95a0fc37..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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 6137c166..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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 d88d8e9d..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR(); 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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 2763e53d..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().iter().opto().iterPostOpto().finalizeIR().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 9f537fe7..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 83342ab8..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().iter().opto().iterPostOpto().finalizeIR(); + """).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)); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter22Test.java index cbb08438..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().iter().opto().iterPostOpto().finalizeIR().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()); @@ -283,7 +283,7 @@ aa.c = cc; bb.c = cc; return cc.cz; """; - CodeGen code = new CodeGen(src).parse().iter().opto().iterPostOpto().finalizeIR().typeCheck(); + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); assertEquals("return 25;", code.print()); assertEquals("25", Eval2.eval(code, 0)); } @@ -322,7 +322,7 @@ return 0; @Test public void testStrLoad() { CodeGen code = new CodeGen("return \"abc\"[1];"); - code.parse().iter().opto().iterPostOpto().finalizeIR(); + code.driver(CodeGen.Phase.FinalizeIR); assertEquals("return .[];", code.print()); } @@ -342,7 +342,7 @@ val _sum = { i32[~] is -> // final array }; return _sum(is); """; - CodeGen code = new CodeGen(src).parse().iter().opto().iterPostOpto().finalizeIR().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 b5da4d91..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().iter().opto().iterPostOpto().finalizeIR(), 0)); - assertEquals("0", Eval2.eval(new CodeGen("return 0 && 42;").parse().iter().opto().iterPostOpto().finalizeIR(), 0)); - assertEquals("7", Eval2.eval(new CodeGen("return 7 || 42;").parse().iter().opto().iterPostOpto().finalizeIR(), 0)); - assertEquals("42", Eval2.eval(new CodeGen("return 0 || 42;").parse().iter().opto().iterPostOpto().finalizeIR(), 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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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 4bfb0d30..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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 4e8effbe..275be96a 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter25Test.java @@ -28,6 +28,36 @@ 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 = "{ 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 { @@ -63,15 +93,6 @@ public class Chapter25Test { } } - @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); - assertEquals("return (arg+3);", code.print()); - assertEquals("3",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 cc010668..47e10f97 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/ClassInitDependenciesTest.java @@ -433,6 +433,88 @@ public class ClassInitDependenciesTest { 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. diff --git a/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java b/chapter25/src/test/java/com/seaofnodes/simple/CompoundAssignmentTest.java index db41363a..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().iter().opto().iterPostOpto().finalizeIR().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 97129f5b..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().iter().opto().iterPostOpto().finalizeIR().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 4934c91f..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().iter().opto().iterPostOpto().finalizeIR(); + 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().iter().opto().iterPostOpto().finalizeIR(); + """).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 9727857e..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().iter().opto().iterPostOpto().finalizeIR().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().iter().opto().iterPostOpto().finalizeIR().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 9078a33a..2d688bbe 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Simple.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Simple.java @@ -49,6 +49,7 @@ Options: --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 @@ -119,6 +120,7 @@ Options: 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); |
