summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/seaofnodes/simple/IterPeeps.java46
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java1914
-rw-r--r--src/main/java/com/seaofnodes/simple/Var.java14
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/CodeGen.java826
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/CompUnit.java138
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfFile.java381
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfReader.java234
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java387
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Encoding.java285
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java171
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java120
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/IFG.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/LRG.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/LinkMem.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Machine.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Module.java98
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Opto.java342
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ParseAll.java379
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java33
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RegMask.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Serialize.java617
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddFNode.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddNode.java42
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AndNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArithFNode.java32
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArithNode.java171
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BoolNode.java115
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java333
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CFGNode.java151
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CProjNode.java28
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndMach.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallEndNode.java272
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CastNode.java74
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CheckCastMach.java (renamed from src/main/java/com/seaofnodes/simple/node/CastMach.java)7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CheckCastNode.java82
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java44
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConstantNode.java54
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConvertNode.java96
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CtrlNode.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/DivFNode.java26
-rw-r--r--src/main/java/com/seaofnodes/simple/node/DivNode.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/EscapeNode.java114
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ExternNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FRefNode.java22
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FldOffNode.java50
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunNode.java295
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunPtrNode.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/node/GuardMach.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/GuardNode.java61
-rw-r--r--src/main/java/com/seaofnodes/simple/node/IfNode.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java209
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoopNode.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MachNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java141
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java100
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemPhiNode.java72
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusFNode.java37
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusNode.java61
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ModeNode.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MulFNode.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MulNode.java14
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NeverNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NewNode.java134
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java180
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NotNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/OrNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ParmNode.java49
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PhiNode.java169
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Proj.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ProjNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java40
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RegionNode.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReturnNode.java104
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RoundF32Node.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SarNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java304
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShlNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShrNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SplitNode.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StartCUNode.java33
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StartNode.java51
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StopCUNode.java122
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StopNode.java79
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StoreNode.java231
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StructNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubFNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubNode.java10
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ToFloatNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/TypeNode.java33
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XCtrlNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/XorNode.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java (renamed from src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java)9
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java135
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java35
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java37
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java32
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java113
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java145
-rw-r--r--src/main/java/com/seaofnodes/simple/print/ASMPrinter.java192
-rw-r--r--src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java15
-rw-r--r--src/main/java/com/seaofnodes/simple/print/IRPrinter.java377
-rw-r--r--src/main/java/com/seaofnodes/simple/print/JSViewer.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Field.java82
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Type.java233
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAry.java61
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAryB.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAryI.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFloat.java30
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java184
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeInteger.java63
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java198
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java88
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeNil.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypePtr.java11
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeRPC.java113
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeScalar.java58
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeStruct.java384
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeTuple.java77
-rw-r--r--src/main/java/com/seaofnodes/simple/type/XInt.java266
-rw-r--r--src/main/java/com/seaofnodes/simple/util/BAOS.java162
-rw-r--r--src/main/java/com/seaofnodes/simple/util/IntHashMap.java65
-rw-r--r--src/main/java/com/seaofnodes/simple/util/SB.java2
-rw-r--r--src/main/smp/sys.smp202
-rw-r--r--src/main/smp/sys/Ary.smp10
-rw-r--r--src/main/smp/sys/Scan.smp36
-rw-r--r--src/main/smp/sys/aryi64.smp70
-rw-r--r--src/main/smp/sys/aryu8.smp66
-rw-r--r--src/main/smp/sys/io.smp32
-rw-r--r--src/main/smp/sys/libc.smp20
-rw-r--r--src/test/java/com/seaofnodes/simple/BrainFuckTest.java24
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter01Test.java10
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter03Test.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter04Test.java2
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter05Test.java7
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter06Test.java16
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter07Test.java8
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter09Test.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter10Test.java87
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter11Test.java89
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter12Test.java19
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter13Test.java124
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter14Test.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter15Test.java74
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter16Test.java93
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter17Test.java102
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter18Test.java165
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter19Test.java124
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter20Test.java62
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter21Test.java94
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter22Test.java141
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter23Test.java288
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter24Test.java548
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter25Test.java286
-rw-r--r--src/test/java/com/seaofnodes/simple/Compile.java65
-rw-r--r--src/test/java/com/seaofnodes/simple/Eval2.java123
-rw-r--r--src/test/java/com/seaofnodes/simple/EvalArm64.java40
-rw-r--r--src/test/java/com/seaofnodes/simple/MergeSortTest.java80
-rw-r--r--src/test/java/com/seaofnodes/simple/SchedulerTest.java74
-rw-r--r--src/test/java/com/seaofnodes/simple/Simple.java310
-rw-r--r--src/test/java/com/seaofnodes/simple/TestArm64.java44
-rw-r--r--src/test/java/com/seaofnodes/simple/TestC.java182
-rw-r--r--src/test/java/com/seaofnodes/simple/TestRisc5.java44
-rw-r--r--src/test/java/com/seaofnodes/simple/TypeTest.java62
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java382
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java605
-rw-r--r--src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java6
-rw-r--r--src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java4
-rw-r--r--src/test/java/com/seaofnodes/simple/test0/A.smp6
-rw-r--r--src/test/java/com/seaofnodes/simple/test0/A/B.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/test5/par.smp20
-rw-r--r--src/test/java/com/seaofnodes/simple/test5/par/child1.smp12
-rw-r--r--src/test/java/com/seaofnodes/simple/test5/par/child2.smp13
-rw-r--r--src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp3
-rw-r--r--src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp6
-rw-r--r--src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp29
-rw-r--r--src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp50
218 files changed, 12428 insertions, 6865 deletions
diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java
index 18ff470a..9fac7a99 100644
--- a/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Opto;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.Ary;
@@ -39,7 +40,7 @@ import java.util.Random;
* <li>Our strong invariant is that for all Nodes, either they are on the worklist
* OR no peephole applies. This invariant is easy to check, although expensive.
* Basically the normal "iterate peepholes to a fixed point" is linear, and this
- * check is linear at each peephole step... so quadratic overall. Its a useful
+ * check is linear at each peephole step... so quadratic overall. It's a useful
* assert, but one we can disable once the overall algorithm is stable - and
* then turn it back on again when some new set of peepholes is misbehaving.
* The code for this is turned on in `IterPeeps.iterate` as `assert
@@ -80,6 +81,10 @@ public class IterPeeps {
// Everybody gets a free "go again" in case they didn't get
// made in their final form.
_work.push(x);
+ // A self-returning peephole can have rewritten its input
+ // edges. The new defs gained a user and may have
+ // backwards, user-sensitive peepholes of their own.
+ for( Node z : x._inputs ) _work.push(z);
// If the result is not self, revisit all inputs (because
// there's a new user), and replace in the graph.
if( x != n ) {
@@ -117,22 +122,35 @@ public class IterPeeps {
Node changed = code._stop.walk( n -> {
Node m = n;
Type nval = n.compute();
- if( (!n.iskeep() || n._nid<=8) && // Types must be forwards, even if on worklist
- ( dir
- ? nval.isa(n._type) // Pesi: new value lifts over old
- : n._type.isa(nval) // Opto: new value falls over old
- ) ) {
- if( list.on(n) ) return null;
- m = n.peepholeOpt();
- if( m==null ) return null;
- }
- System.err.println("BREAK HERE FOR BUG");
+
+ // Types must be forwards, even if on the worklist.
+ boolean checkType = !n.iskeep() ||
+ n._nid <= 8 ||
+ (n instanceof ProjNode && n.in(0) instanceof StartNode);
+
+ boolean monotonic = dir
+ ? nval.isa(n._type) // Pesi: new value lifts over old
+ : n._type.isa(nval); // Opto: new value falls over old
+ assert !checkType || monotonic : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+m;
+ if( list.on(n) )
+ return null;
+
+ assert n.nOuts() > 0 : "Unused live node: "+n;
+ m = n.peepholeOpt();
+ assert m==null : "Peep fired and not on worklist, "+n.getClass().getSimpleName()+"#"+n._nid+" -> "+m;
return m;
});
code._midAssert = false;
return changed==null;
}
+ private static String inputTypes(Node n) {
+ StringBuilder sb = new StringBuilder("[");
+ for( Node in : n._inputs )
+ sb.append(in==null ? "null" : in.getClass().getSimpleName()+"#"+in._nid+"="+in+":"+in._type+":keep="+in.iskeep()).append(',');
+ return sb.append(']').toString();
+ }
+
/**
* Classic WorkList, with a fast add/remove, dup removal, random pull.
* The Node's nid is used to check membership in the worklist.
@@ -180,12 +198,18 @@ public class IterPeeps {
for( E n : ary )
push(n);
}
+ public void addAll( E[] es ) {
+ for( E n : es )
+ push(n);
+ }
+
/**
* True if Node is on the WorkList
*/
public boolean on( E x ) { return _on.get(x._nid); }
boolean isEmpty() { return _len==0; }
+ Node[] asAry() { return Arrays.copyOf(_es,_len); }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java
index f80e8f2c..4c2dfc84 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -1,12 +1,14 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.print.GraphVisualizer;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.node.ScopeNode.Kind;
+
+import static com.seaofnodes.simple.util.Utils.TODO;
+
import java.util.*;
/**
@@ -18,16 +20,32 @@ import java.util.*;
*/
public class Parser {
- public static ConstantNode ZERO; // Very common node, cached here
- public static ConstantNode NIL; // Very common node, cached here
- public static XCtrlNode XCTRL; // Very common node, cached here
-
// Compile driver
public final CodeGen _code;
// The Lexer. Thin wrapper over a byte[] buffer with a cursor.
private Lexer _lexer;
+ // Class prefix string; TODO: use something short: "%"
+ public static final String CLZ = "class:";
+ private static final String clzPrefix = CLZ;
+ public static String addClzPrefix( String x ) {
+ assert !x.startsWith(clzPrefix);
+ return (clzPrefix+x).intern();
+ }
+ public static boolean startsClzPrefix( String s ) {
+ return s.startsWith(clzPrefix);
+ }
+ public static boolean isPublicClassName( String name ) {
+ if( startsClzPrefix(name) ) name = name.substring(clzPrefix.length());
+ return !name.isEmpty() && name.charAt(0)!='_' && name.indexOf("._")==-1;
+ }
+
+ // Source file for compilation
+ public CompUnit _ref;
+
+ // Current classname being parsed
+ private String _nestedType;
/**
* Current ScopeNode - ScopeNodes change as we parse code, but at any point of time
* there is one current ScopeNode. The reason the current ScopeNode can change is to do with how
@@ -79,34 +97,16 @@ public class Parser {
*/
public final Stack<ScopeNode> _xScopes = new Stack<>();
- ScopeNode _continueScope;
- ScopeNode _breakScope; // Merge all the while-breaks here
- FunNode _fun; // Current function being parsed
+ ScopeNode _breakScope; // Merge all the while-breaks here
+ ScopeNode _continueScope; // Merge all the while-continues here
+ ScopeNode _returnScope; // Merge all the function exits here
+ TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
// Mapping from a type name to a Type. The string name matches
// `type.str()` call.
- public static HashMap<String, Type> TYPES = defaultTypes();
-
- // Mapping from a type name to the constructor for a Type.
- public final HashMap<String, StructNode> INITS;
-
-
- public Parser(CodeGen code, TypeInteger arg) {
- _code = code;
- _scope = new ScopeNode();
- _continueScope = _breakScope = null;
- ZERO = con(TypeInteger.ZERO).keep();
- NIL = con(Type.NIL).keep();
- XCTRL= new XCtrlNode().peephole().keep();
- TYPES = defaultTypes();
- INITS = new HashMap<>();
- }
-
- @Override
- public String toString() { return _lexer.toString(); }
-
- public static HashMap<String, Type> defaultTypes() {
- return new HashMap<>() {{
+ public static final HashMap<String, Type> TYPES = new HashMap<>();
+ public static final HashMap<String, Type> INIT_TYPES =
+ new HashMap<>() {{
put("bool",TypeInteger.U1 );
put("byte",TypeInteger.U8 );
put("f32" ,TypeFloat .F32);
@@ -124,170 +124,363 @@ public class Parser {
put("val" ,Type.TOP); // Marker type, indicates type inference
put("var" ,Type.BOTTOM); // Marker type, indicates type inference
}};
+
+
+ public Parser(CodeGen code ) {
+ _code = code;
}
+ @Override
+ public String toString() { return _lexer==null ? "" : _lexer.toString(); }
+
// Debugging utility to find a Node by index
public Node f(int nid) { return _code.f(nid); }
+ // Read and set CTRL
private Node ctrl() { return _scope.ctrl(); }
private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
+ // Read and set Memory
+ private Node mem() { return _scope.mem(1); }
+ private void mem(Node mem) { _scope.mem(mem); }
+ // Structural label for precise memory Phis. The '$' prefix cannot be
+ // referenced in Simple source.
+ public static String memName(int alias) { return ("$"+alias).intern(); }
- public void parse() {
+ // Ordinary parsing carries one whole-memory value. Alias partitioning is
+ // graph optimization, not parser state.
+ private void storeMem( Node st, int alias, Node prior ) {
+ mem(alias==1 ? st : mergeAlias(prior,alias,st));
+ }
+ // A semantic graph partition representing the complete memory after a
+ // precise alias update. ScopeNode treats this as one opaque value.
+ private Node mergeAlias(Node bulk, int alias, Node precise) {
+ assert bulk != null : "Missing bulk memory for alias #"+alias;
+ MemMergeNode merge = new MemMergeNode(false,null,bulk);
+ merge.alias(alias,precise);
+ return peep(merge);
+ }
+
+
+ public Ary<FRefNode> parse( CompUnit ref ) {
+ assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
+ _lexer = new Lexer(ref._src);
+ // Starting Scope has control, memory, initial arguments
+ _scope = new ScopeNode();
_scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer);
_scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer);
- _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer);
-
- ctrl(XCTRL);
- _scope.mem(new MemMergeNode(false));
+ // Track active scopes for Graph display
+ _xScopes.push(_scope);
+ ctrl(_code.XCTRL);
+ mem(con(TypeMem.BOT));
- // Parse the sys import
- _lexer = new Lexer(sys.SYS);
- while( !_lexer.isEOF() ) {
- parseStatement();
- _lexer.skipWhiteSpace();
- }
+ // File-level struct declaration
+ _ref = ref;
+ _nestedType = ref._cname;
+ String typeName = addClzPrefix(ref._cname);
- // Reset lexer for program text
- _lexer = new Lexer(_code._src);
- _xScopes.push(_scope);
+ ReturnNode clzret = parseStruct( true, typeName );
- // Parse whole program, as-if function header "{ int arg -> body }"
- parseFunctionBody( _code._main,loc(),"arg");
-
- // Kill an empty default main. Keep only if it was explicitly defined
- // (programmer asked for a "main") or it has stuff (i.e. beginner
- // default main).
- FunNode main = _code.link(_code._main);
- StopNode stop = _code._stop;
- // Gather an explicit main
- FunNode xmain = null;
- for( Node n : stop._inputs )
- if( n instanceof ReturnNode ret && "main".equals(ret.fun()._name) )
- if( xmain != null ) throw Utils.TODO();
- else xmain = ret.fun();
-
- if( main.ret().expr()._type==Type.TOP && main.uctrl()==null ) {
- // Kill an empty default main; so it does not attempt to put a
- // "main" in any final ELF file
- main.setDef(1,XCTRL); // Delete default start input
- stop.delDef(stop._inputs.find(main.ret()));
- if( xmain != null )
- _code.setMain(xmain);
+ if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
- } else {
- // We have a non-empty default main.
- if( xmain != null )
- // Found an explicit "main" AND we have a default "main"
- throw error("Cannot define both an explicit main and a default main");
- main._name = "main";
+ // At the top scope and every new var should be a FRef, and these
+ // should immediately resolve to a file-based name which is a Simple
+ // class type.
+ Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
+ for( int i=2; i<_scope._vars._len; i++ ) {
+ FRefNode fref = (FRefNode)_scope._inputs.at(i);
+ CompUnit cu = ParseAll.findCompUnitOrThrow(_code,_ref,fref._name);
+ fref._con = TypeMemPtr.make((byte)2,TypeStruct.make(addClzPrefix(cu._cname),true),true);
+ frefs.push(fref);
}
- if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
+ // The class being published
+ ref._clz = (TypeStruct)TYPES.get(typeName);
// Clean up and reset
_xScopes.pop();
_scope.kill();
- for( StructNode init : INITS.values() )
- init.unkeep().kill();
- INITS.clear();
- stop.peephole();
+ _scope = null;
+
+ return frefs;
}
+
+
/**
* Parses a function body, assuming the header is parsed.
*/
- private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String... ids) {
- // If this is a method, record & restore the existing struct fields,
- // which are also the struct initializers. The method might update
- // mutable fields, so when the function parse is done reset back to
- // pre-function-parse values.
- Node[] preParse = new Node[_scope.nIns()];
- for( int i=0; i<_scope.nIns(); i++ )
- preParse[i] = _scope.in(i)==null ? null : _scope.in(i).keep();
+ private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) {
+ // Record & restore the existing scope vars. The function parse might
+ // update fields, so when the parse is done reset back to the pre-parse
+ // state.
+ Node[] preParse = _scope.save();
+
+ // Parse function body normally
+ ReturnNode ret = _parseFunctionBody(null, sig,loc,ids);
+
+ // Reset all fields to pre-parse days
+ _scope.restore(preParse);
+ return ret;
+ }
+
+
+ // Parse a function body
+
+ // Stack the {break,continue,return} scopes and a new lexical scope.
+ // Build a FunNode and parameters, extending scope by parm names.
+ // Parse the function body
+ // - while( not end-of-function )
+ // - - parseStatement
+ // - - If isInit, also incrementally extend the self-type.
+ // If isInit
+ // - upgrade and close the self-type
+ // - Gather fields and store values
+ // - <init> returns self, not last
+ // Unstack the {break,continue,return} scopes.
+
+ private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, String[] ids) {
+ boolean isInit = FunNode.isInit(funName);
+ boolean isCtor = isInit || isExplicitConstructorName(funName);
+ String typeName = isCtor ? ((TypeMemPtr)sig._sig[0])._obj._name : null;
// Stack parser state on the local Java stack, and unstack it later
- FunNode oldfun = _fun;
- ScopeNode breakScope = _breakScope; _breakScope = null;
+ _scope.push(new Kind.Func(funName));
+ ScopeNode breakScope = _breakScope; _breakScope = null;
ScopeNode continueScope = _continueScope; _continueScope = null;
+ ScopeNode returnScope = _returnScope; _returnScope = null;
- FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._start));
+ int oldUID = _code.UID(); // Used to approximate function size
+ FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._start));
// Once the function header is available, install in linker table -
// allowing recursive functions. Linker matches on declared args and
// exact fidx, and ignores the return (because the fidx will only match
// the exact single function).
_code.link(fun);
- Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,con(TypeRPC.BOT)).peephole();
-
- // Build a multi-exit return point for all function returns
- RegionNode r = new RegionNode( null,null,null).init();
- assert r.inProgress();
- PhiNode rmem = new PhiNode(ScopeNode.MEM0,TypeMem.BOT,r,null).init();
- PhiNode rrez = new PhiNode(ScopeNode.ARG0,Type.BOTTOM,r,null).init();
- ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init();
- fun.setRet(ret);
- assert ret.inProgress();
- _code._stop.addDef(ret);
-
+ Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
// Pre-call the function from Start, with worse-case arguments. This
// represents all the future, yet-to-be-parsed functions calls and
// external calls.
- _scope.push(new Kind.Func());
- ctrl(fun); // Scope control from function
+ _scope.ctrl(fun); // Scope control from function
// Private mem alias tracking per function
- MemMergeNode mem = new MemMergeNode(true);
- mem.addDef(null); // Alias#0
- mem.addDef(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole()); // All aliases
- _scope.mem(mem);
+ Node defaultMem = new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole();
+ Node privMem = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,defaultMem).peephole();
+ mem(privMem);
// All args, "as-if" called externally
for( int i=0; i<ids.length; i++ ) {
Type t = sig.arg(i);
- _scope.define(ids[i], t, false, new ParmNode(ids[i],i+2,t,fun,con(t)).peephole(), loc);
+ // Args take a default input if called from Start/external-world
+ _scope.define(ids[i], t, i==0 && ids[0]=="self", new ParmNode(ids[i],i+2,t,fun,ConstantNode.seed(t).peephole()).peephole(), loc);
}
- // Parse the body
- Node last=ZERO;
- while (!peek('}') && !_lexer.isEOF())
- last = parseStatement();
+ // Parse the function body.
+ Node last=_code.ZERO; // Last statement as the default result
- // Last expression is the return except for the top-level main
- if( ctrl()._type==Type.CONTROL )
- if( fun.sig() != _code._main )
- fun.addReturn(ctrl(), _scope.mem().merge(), last);
- else {
- fun.setDef(1,XCTRL); // Kill default main
- _code.addAll(fun._outputs);
+ int nvar = 0;
+ while (!peek('}') && !_lexer.isEOF()) {
+ // Parse a statement; record last statement as default return
+ last = _code.add(parseStatement());
+ // For <init> and <clinit>, local vars are really struct fields and
+ // get exposed as discovered. This allows late local functions to
+ // use early declared fields.
+ if( isInit )
+ nvar = updateSelfAsFieldsDiscovered( typeName, nvar );
+ }
+
+ ParmNode self = fun.parm(2);
+ // Last expression is the return
+ if( ctrl()._type==Type.CONTROL ) {
+ // Constructors get special return types
+ if( fun.isClz() ) {
+ // <clinit> only returns an integer, which becomes an OS exit code
+ // if the <clinit> is run directly
+ //if( !(last._type instanceof TypeInteger) )
+ // last = _code.ZERO;
+ assert last!=null;
+ } else if( isCtor ) {
+ last = fun.parm(3);
}
-
- // Pop off the inProgress node on the multi-exit Region merge
- assert r.inProgress();
- r ._inputs.pop();
- rmem._inputs.pop();
- rrez._inputs.pop();
- r._loc = loc(); // Final position
- assert !r.inProgress();
-
- // Force peeps, which have been avoided due to inProgress
- ret.setDef(1,rmem.peephole());
- ret.setDef(2,rrez.peephole());
- ret.setDef(0,r.peephole());
- ret = (ReturnNode)ret.peephole();
-
- // Function scope ends
- _scope.pop();
- _fun = oldfun;
- _breakScope = breakScope;
+ addReturn(last);
+ // exit path is now dead (nothing live after a "return")
+ }
+ if( isCtor )
+ upgradeSelfTypeAndStoreFields( typeName, fun.isClz(), self, fun.parm(3), isInit );
+
+ // Build a return from the _returnScope.
+ // Can be no returns for never-exit functions
+ Node rctl = _returnScope==null ? _code.XCTRL : _returnScope.ctrl().peephole();
+ Node rmem = rctl==_code.XCTRL ? con(TypeMem.TOP) : _returnScope.mem();
+ Node expr = rctl==_code.XCTRL ? con(Type.TOP) : _returnScope._inputs.last().peephole();
+ ReturnNode ret = (ReturnNode)peep(new ReturnNode(rctl, rmem, expr, rpc, fun));
+ fun.setRet(ret);
+ // Approximate function size, for inlining heuristics
+ fun._approxUIDs = _code.UID() - oldUID;
+ for( Node cend : ret.outs() )
+ if( cend instanceof CallEndNode )
+ throw TODO(); //_code.add(cend); // have call ends ALREADY? just barely made the Return
+
+ // Export (and keep-alive) functions until prove dead
+ _ref.addFun(_code,fun);
+
+ // Unstack parser state
+ if( _returnScope != null ) _returnScope.kill();
+ _returnScope = returnScope;
_continueScope = continueScope;
-
- // Reset all fields to pre-parse days
- for( int i=0; i<preParse.length; i++ )
- _scope.setDef(i,preParse[i]==null ? null : preParse[i].unkeep());
+ _breakScope = breakScope;
+ _scope.pop();
return ret;
}
+ private int updateSelfAsFieldsDiscovered( String typeName, int nvar ) {
+ int lex = _scope.klast()._lexSize + 2/*Skip self and selfMem*/;
+ // Load self type in and out of the TYPES hashtable.
+ TypeStruct tself = (TypeStruct)TYPES.get(typeName);
+ // Start looking for newly declared vars at the lexical scope start,
+ // skipping two ("self" and "selfmem") and fields seen already.
+ while( lex + nvar < _scope.nIns() ) {
+ Var v = _scope.var(lex + nvar++);
+ // Field with sharper type
+ Field old = tself.field(v._name);
+ Field fld = old == null
+ ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final)
+ : Field.make(v._name,v.type(), old._alias, old._final);
+ tself = tself.addOrUpdate(fld);
+ }
+ TYPES.put(typeName,tself);
+ return nvar;
+ }
+
+
+ void upgradeSelfTypeAndStoreFields( String typeName, boolean isClz, ParmNode self, ParmNode smem, boolean upgrade ) {
+ int lex = _scope.klast()._lexSize;
+ assert _scope.var(lex)._name=="self";
+ TypeStruct tself = (TypeStruct)TYPES.get(typeName);
+ int base = lex+2; // Skip ctrl, mem
+ if( upgrade ) {
+ // Default <init>/<clinit> defines the struct and can sharpen fields.
+ // A named constructor only initializes the already-closed struct.
+ for( int i=0; i<tself.nkids(); i++ ) {
+ Field tfld = tself._fields[i];
+ int vidx = fieldVarIdx(base,tfld._fname);
+ if( vidx == -1 )
+ continue;
+ Var v = _scope.var(vidx);
+ // Forward-ref fields where placed in tself but will get promoted
+ // to an outer scope - and should not be in tself.
+ if( v._fref )
+ tself = tself.remove(i--);
+ else if( v.type() != tfld._t ) {
+ assert v.type().isa(tfld._t);
+ tself = tself.replace( tfld.makeFrom( v.type() ) );
+ }
+ }
+
+ // Close Struct type after parsing struct body.
+ TYPES.put( tself._name, tself = tself.close() );
+ // Improve self, selfMem types in scope.
+ // Class self-type is a singleton pointer. Instance <init> receives the
+ // broad allocation pointer, while private memory tracks initialized fields.
+ self._declaredType = self._type = isClz ? TypeMemPtr.make((byte)2,tself,true) : TypeMemPtr.make(constructorRecv(tself));
+ FunNode fun = self.fun();
+ fun.setSig(fun.sig().makeFrom(self._type,0));
+ if( !isClz ) // <init> returns upgraded private memory
+ smem._declaredType = smem._type = TypeMem.makePrivate(tself);
+ }
+
+ // When can _returnScope be null here? A never-exit constructor will
+ // not have any returns, and thus no need to gather values and store
+ // them into the (never) constructed object
+ if( _returnScope==null )
+ return;
+
+ // A MemMerge to gather field updates as stores. Classes update the
+ // normal public memory. <init> updates the private memory that got
+ // passed in - is treated like a normal argument and not like memory.
+ Node mmm = isClz ? _returnScope.mem(1) : smem;
+
+
+ // Store constructor results into fields
+ for( int i=0; i<tself.nkids(); i++ ) {
+ Field fld = tself._fields[i];
+ int vidx = upgrade
+ ? fieldVarIdx(base,fld._fname)
+ : fieldVarIdxBefore(lex,fld._fname);
+ if( vidx == -1 )
+ continue;
+ Var v = _scope.var(vidx);
+ if( v._fref ) continue; // Forward refs not declared here
+ // NewNode's TOP field is the zero-code poison for a required
+ // field. The parser Var retains the typed nullable state for
+ // early-read diagnostics; only a user constructor writes memory.
+ if( upgrade && v._uninit ) continue;
+ Node val = _returnScope.in(vidx);
+ if( !upgrade && v._uninit && val._type instanceof TypeNil tn && tn.nullable() )
+ throw error("'"+tself._name+"' is not fully initialized, field '"+
+ fld._fname+"' is only partially set in the constructor",v._loc);
+ // Store value into extended struct
+ Node prior = mmm.keep();
+ Node st = peep(new StoreNode(null, fld._fname, fld._alias, fld._t, null, prior, self, off(tself,fld._fname), val, fld._final));
+ mmm = mergeAlias(prior,fld._alias,st);
+ prior.unkeep();
+ }
+ mmm = mmm.peephole();
+
+ if( !isClz )
+ // Stuff private mem into normal expression return value.
+ _returnScope.setDef(_returnScope.nIns()-1,mmm);
+ else
+ // Parser memory is one opaque whole-memory edge. Publish the
+ // completed class-field aggregate as the <clinit> return memory.
+ _returnScope.mem(mmm);
+ //_code.add(mmm);
+ }
+
+ private int fieldVarIdx( int base, String name ) {
+ for( int i=base; i<_scope.nIns(); i++ )
+ if( _scope.var(i)._name == name )
+ return i;
+ return -1;
+ }
+
+ // Named constructors update field Vars from the enclosing initializer
+ // scope. Constructor arguments and locals begin at limit and must not
+ // shadow the field state gathered into private memory.
+ private int fieldVarIdxBefore( int limit, String name ) {
+ for( int i=limit-1; i>=0; i-- )
+ if( _scope.var(i)._name == name )
+ return i;
+ return -1;
+ }
+
+ private boolean isConstructorDecl(String name) {
+ TypeStruct self = _scope.constructorSelf();
+ return self != null && name == initFieldName(self._name);
+ }
+
+ private boolean inExplicitConstructor() {
+ if( !(_scope.klast() instanceof Kind.Func func) )
+ return false;
+ return isExplicitConstructorName(func._name);
+ }
+
+ private boolean isExplicitConstructorName(String funName) {
+ TypeStruct self = _scope.constructorSelf();
+ return self != null && funName == (self._name+"."+initFieldName(self._name)).intern();
+ }
+
+ private static TypeStruct constructorRecv(TypeStruct self) {
+ Field[] fs = new Field[self._fields.length];
+ for( int i=0; i<fs.length; i++ ) {
+ Field fld = self._fields[i];
+ fs[i] = fld._t instanceof TypeFunPtr ? fld : fld.makeFrom(fld._t.glb(true));
+ }
+ return TypeStruct.make(self._name,self._open,fs);
+ }
+
+
/**
* Parses a block
*
@@ -300,9 +493,11 @@ public class Parser {
private Node parseBlock(Kind kind) {
// Enter a new scope
_scope.push(kind);
- Node last = ZERO;
- while (!peek('}') && !_lexer.isEOF())
+ Node last = _code.ZERO;
+ while (!peek('}') && !_lexer.isEOF()) {
+ _code.add(last);
last = parseStatement();
+ }
// Exit scope
last.keep();
_scope.pop();
@@ -325,8 +520,9 @@ public class Parser {
else if (matchx("break") ) return parseBreak();
else if (matchx("continue")) return parseContinue();
else if (matchx("struct") ) return parseStruct();
+ else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration();
else if (matchx("#showGraph")) return require(showGraph(),";");
- else if (matchx(";") ) return null; // Empty statement
+ else if (matchx(";") ) return _code.ZERO; // Empty statement
// Break ambiguity around leading function types and starting a block
else if (peek('{') && !isTypeFun() ) {
match("{");
@@ -336,6 +532,31 @@ public class Parser {
else return parseDeclarationStatement();
}
+ private boolean inStructDeclaration() {
+ return _scope.klast() instanceof Kind.Func fun && FunNode.isInstance(fun._name);
+ }
+
+ // Constructor declaration: new TypeName = { args -> body };
+ private Node parseConstructorDeclaration() {
+ TypeStruct self = _scope.constructorSelf();
+ assert self != null;
+ String name = requireId();
+ String expect = initFieldName(self._name);
+ if( name != expect )
+ throw error("Constructor name '" + name + "' must match type name '" + expect + "'");
+ require("=");
+ Node ctor = constructorFunc(name);
+ TypeFunPtr sig = (TypeFunPtr)ctor._type;
+ String clzName = addClzPrefix(self._name);
+ TypeStruct clz = (TypeStruct)TYPES.get(clzName);
+ Field old = clz==null ? null : clz.field("<ctor>");
+ if( old != null )
+ throw error("Redefining constructor '" + name + "'");
+ Field fld = Field.make("<ctor>",sig,_code.alias(_ref._cname),true);
+ TYPES.put(clzName,clz==null ? TypeStruct.make(clzName,true,fld) : clz.add(fld));
+ return require(ctor,";");
+ }
+
/**
* Parses a while statement
*
@@ -386,7 +607,7 @@ public class Parser {
// used as an indicator to switch off peepholes of the region and
// associated phis; see {@code inProgress()}.
- ctrl(new LoopNode(loc(),ctrl()).peephole()); // Note we set back edge to null here
+ ctrl(new LoopNode(loc(),null,ctrl()).peephole()); // Note we set back edge to null here
// At loop head, we clone the current Scope (this includes all
// names in every nesting level within the Scope).
@@ -476,12 +697,12 @@ public class Parser {
_xScopes.push( exit );
}
_scope = exit;
- return ZERO;
+ return _code.ZERO;
}
private ScopeNode jumpTo(ScopeNode toScope) {
ScopeNode cur = _scope.dup();
- ctrl(XCTRL); // Kill current scope
+ ctrl(_code.XCTRL); // Kill current scope
// Prune nested lexical scopes that have depth > than the loop head
// We use _breakScope as a proxy for the loop head scope to obtain the depth
while( cur.depth() > _breakScope.depth() )
@@ -499,7 +720,7 @@ public class Parser {
private void checkLoopActive() { if (_breakScope == null) throw error("No active loop for a break or continue"); }
- private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; }
+ private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return _code.ZERO; }
private Node parseBreak () {
checkLoopActive();
// At the time of the break, and loop-exit conditions are only valid if
@@ -508,7 +729,7 @@ public class Parser {
_breakScope.removeGuards(_breakScope.ctrl());
_breakScope = require(jumpTo(_breakScope ),";");
_breakScope.addGuards(_breakScope.ctrl(), null, false);
- return ZERO;
+ return _code.ZERO;
}
// Look for an unbalanced `)`, skipping balanced
@@ -518,7 +739,7 @@ public class Parser {
// Next X char handles skipping complex comments
switch( _lexer.nextXChar() ) {
case Character.MAX_VALUE:
- throw Utils.TODO();
+ throw TODO();
case ')':
if( --paren<0 ) {
posT( pos() - 1 );
@@ -568,7 +789,7 @@ public class Parser {
case ":" -> parseAsgn(); // pred ? asgn;
case "&&" -> parseLogical(); // pred && expr
case "||" -> pred; // pred || expr_is_ignored
- default -> throw Utils.TODO();
+ default -> throw TODO();
};
lhs.keep();
_scope.removeGuards(ifT);
@@ -593,7 +814,7 @@ public class Parser {
case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero());
case "&&" -> rhs = pred;
case "||" -> rhs = parseLogical();
- default -> throw Utils.TODO();
+ default -> throw TODO();
};
rhs.keep();
_scope.removeGuards(ifF);
@@ -616,10 +837,10 @@ public class Parser {
// Merge results
RegionNode r = ctrl(tScope.mergeScopes(fScope,loc()));
- Node ret = peep(new PhiNode("",lhs._type.meet(rhs._type).glb(false),r,lhs.unkeep(),rhs.unkeep()));
+ Node ret = peep(new PhiNode("",r,lhs.unkeep(),rhs.unkeep()));
// Immediately fail e.g. `arg ? 7 : ptr`
ParseException err;
- if( !fside.equals("else") && (err=ret.err()) !=null ) throw err;
+ if( !fside.equals("else") && (err=ret.err()) !=null ) throw err;
r.peephole();
return ret;
}
@@ -635,11 +856,56 @@ public class Parser {
*/
private Node parseReturn() {
var expr = require(parseAsgn(), ";");
+ return addReturn(expr);
+ }
+ private Node addReturn(Node expr) {
+ expr.keep();
// Need default memory, since it can be lazy, need to force
// a non-lazy Phi
- _fun.addReturn(ctrl(), _scope.mem().merge(), expr);
- ctrl(XCTRL); // Kill control
- return expr;
+ mem(); // Force a pending lazy bulk-memory Phi
+ // For <init> and <clinit> - all fields in scope will be live-on-exit
+ // and stored into `self` and need to have lazy-phi's inserted.
+ int lexN = _scope.enclosingFuncOrDecl();
+ Kind.Func k = (Kind.Func)_scope._kinds.at(lexN);
+ if( FunNode.isInit(k._name) ) {
+ int nestedLexSize = lexN+1 < _scope.depth() ? _scope._kinds.at(lexN+1)._lexSize : _scope.nIns();
+ for( int i=k._lexSize; i<nestedLexSize; i++ )
+ _scope.update(_scope.var(i),null);
+ if( _returnScope != null ) {
+ Var vexpr = _returnScope._vars.pop();
+ Node oldX = _returnScope.removeLast();
+ int rlen = _returnScope.nIns();
+ while( rlen < nestedLexSize ) {
+ _returnScope._vars.add(_scope.var(rlen));
+ _returnScope.addDef(con(_scope.var(rlen++).type().makeZero()));
+ }
+ _returnScope._vars.add(vexpr);
+ _returnScope.addDef(oldX);
+ }
+ }
+
+ // No prior merge point? Just clone and hang on to it
+ if( _returnScope == null ) {
+ _returnScope = _scope.dup();
+ while( lexN+1 < _returnScope.depth() )
+ _returnScope._pop(); // Pop a nested block scope until we hit the function scope
+ _returnScope.define("$expr", expr._type.glb(false), true, expr, null);
+
+ } else {
+ // For <init> and <clinit> - ALL FIELDS IN LAST SCOPE are live
+ // and need to have lazy-phi's inserted.
+
+ // And fields might not match.... will need matching Var/define with default values
+ int rlen = _returnScope.nIns()-1;
+ RegionNode r = ctrl(new RegionNode(null, null,_returnScope.ctrl(), _scope.ctrl()).init().keep());
+ _returnScope._merge(_scope, r, rlen);
+ Node oldExpr = _returnScope.in(rlen);
+ _returnScope.setDef(rlen,new PhiNode("$expr", r, oldExpr, expr).peephole());
+ _code.add(r);
+ _returnScope.ctrl(r.unkeep());
+ }
+ ctrl(_code.XCTRL); // Kill control
+ return expr.unkeep();
}
/**
@@ -647,7 +913,7 @@ public class Parser {
* @return {@code null}
*/
Node showGraph() {
- System.out.println(new GraphVisualizer().generateDotOutput(_code._stop,_scope,_xScopes));
+ System.out.println(new GraphVisualizer().generateDotOutput(_ref,_scope,_xScopes));
return null;
}
@@ -656,7 +922,6 @@ public class Parser {
private Node parseAsgn() {
int old = pos();
String name = _lexer.matchId();
- int old_pre_equals = pos();
// Just a plain expression, no assignment.
// Distinguish `var==expr` from `var=expr`
if( name==null || KEYWORDS.contains(name) || !matchOpx('=','=') )
@@ -665,62 +930,72 @@ public class Parser {
// Find variable to update
Var def = _scope.lookup(name);
if( def==null )
+ def = defineOpenConstructorField(name);
+ if( def==null )
throw error("Undefined name '" + name + "'");
- // If a method var, allow nested blocks and methods, but not fcns or other structs.
- // Else safety check for accessing fields out of function scope.
- if( instance(def) ) {
- pos(old_pre_equals); // setup for `self.name = expr`
- Node self = _scope.in(_scope.lookup("self")); // Insert self reference
- return parsePostfixName(self,name);
- }
-
// TOP fields are for late-initialized fields; these have never
// been written to, and this must be the final write. Other writes
// outside the constructor need to check the final bit.
if( _scope.in(def._idx)._type!=Type.TOP && def._final &&
// Inside an allocation, final assign is OK, outside nope.
// The alloc() call added the allocation scope
- !(_scope.inAllocation() && def._idx >= _scope._kinds.last(-1)._lexSize) )
+ !(_scope.inConstructor() && def._idx >= _scope.klast()._lexSize) &&
+ !inExplicitConstructor() )
throw error("Cannot reassign final '"+name+"'");
// Parse assignment expression
Node expr = parseAsgn();
// Lift expression, based on type
- Node lift = liftExpr(expr.keep(), def.type(), def._final, true);
+ Type decl = def.type(); // Declared type
+ Node lift = convertExpr(expr.keep(), decl).keep();
// Update
_scope.update(name,lift);
+
+ // Check for storing into an instance field instead of a local var
+ int kx = _scope.kindx(def); // Declaration scope
+ int fx = _scope.enclosingFunction(); // Enclosing function scope
+ Kind kk = _scope._kinds.at(kx);
+ Kind fk = _scope._kinds.at(fx);
+ // Access instance field from 'self'
+ if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) &&
+ fk instanceof Kind.Func method && !inExplicitConstructor() ) {
+ // Named field offset on an unknown struct type
+ TypeMemPtr self = (TypeMemPtr)_scope.var(fk._lexSize).type();
+ TypeStruct sobj = (TypeStruct)TYPES.get(self._obj._name);
+ Field fld = sobj.field(name);
+ int alias = fld._alias;
+ Node ptr = _scope.in(method._lexSize);
+ Node off = fldoff(ptr,name);
+ Node prior = mem().keep();
+ Node st = new StoreNode(loc(), name, alias, decl, ctrl(), prior, ptr, off, lift, inExplicitConstructor()).peephole().keep();
+ storeMem(st,alias,prior);
+ st.unkeep();
+ prior.unkeep();
+ }
+ lift.unkill();
+
+
// Return un-lifted expr
return expr.unkeep();
}
- // Make finals deep; widen ints to floats; narrow wide int types.
+ // Widen ints to floats; narrow wide int types.
// Early error if types do not match variable.
- private Node liftExpr( Node expr, Type t, boolean xfinal, boolean isLoad ) {
- if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() )
- throw error("Must define forward ref "+tmp._obj._name);
- // Final is deep on ptrs
- if( xfinal && t instanceof TypeMemPtr tmp ) {
- t = tmp.makeRO();
- expr = peep(new ReadOnlyNode(expr));
- }
+ private Node convertExpr( Node expr, Type t ) {
+ // A FunPtr carries the semantic edge to its function's Return. Do not
+ // hide that edge behind a Convert which can fold to a plain constant.
+ if( expr instanceof FunPtrNode && t instanceof TypeFunPtr )
+ return expr;
// Auto-widen array to i64 (cast ptr to raw int bits)
if( t == TypeInteger.BOT && expr._type instanceof TypeMemPtr tmp && tmp._obj.isAry() )
- expr = peep(new AddNode(peep(new CastNode(t,ctrl(),expr)),off(tmp._obj,"[]")));
- // Auto-widen int to float
- expr = widenInt( expr, t );
- // Auto-narrow wide ints to narrow ints. For loads, emit code to force
- // the loaded value to match the declared sign/zero bits. For stores,
- // just force the type, acting "as if" the store silently truncates.
- Type et = expr._type;
- if( isLoad ) { expr = zsMask(expr,t); et = expr._type; }
- else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t;
-
- // Type is sane
- if( et!=Type.BOTTOM && !et.shallowISA(t) )
- expr = peep(new CastNode(t.isConstant() ? t : t.widen(),null,expr));
- return expr;
+ return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]")));
+ // Null check has to eventually pass
+ if( t == Type.NIL && expr._type instanceof TypeMemPtr tmp && tmp.notNull() )
+ return peep(new CheckCastNode(t,null,expr));
+ // Widen ints to floats, narrow wide types
+ return peep(new ConvertNode(t,expr));
}
private Node widenInt( Node expr, Type t ) {
@@ -761,11 +1036,17 @@ public class Parser {
// Optional initializing expression follows
boolean xfinal = false;
boolean fld_final = false; // Field is final, but not deeply final
+ boolean uninit = false; // Required non-null constructor field
Node expr;
if( match("=") ) {
- expr = isExternDecl()
- ? externDecl(name,t)
- : parseAsgn();
+ if( isExternDecl() ) {
+ expr = externDecl(name,t);
+ t = expr._type; // Upgrade declared type to the exact extern decl type
+ } else if( isConstructorDecl(name) ) {
+ expr = constructorFunc(name);
+ } else {
+ expr = parseAsgn();
+ }
// TOP means val and val is always final
xfinal = (t==Type.TOP) ||
expr instanceof ExternNode ||
@@ -773,8 +1054,6 @@ public class Parser {
(t!=Type.BOTTOM &&
// no Bang AND
!hasBang &&
- // Locals are not-final by default
- !_scope.inFunction() &&
// not-null (expecting null to be set to not-null)
expr._type != Type.NIL &&
// Pointers are final by default; int/flt are not-final by default.
@@ -787,13 +1066,23 @@ public class Parser {
t = expr._type;
if( !xfinal ) t = t.glb(false); // Widen if not final
}
+
+ // Final is deep on ptrs
+ if( xfinal && t instanceof TypeMemPtr tmp ) {
+ t = tmp.makeRO();
+ expr = peep(new ReadOnlyNode(expr));
+ }
+
// expr is a constant function
if( t instanceof TypeFunPtr && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- if( expr instanceof ExternNode ) t = expr._type; // Upgrade declared type to exact function
- else _code.link(tfp).setName(name); // Assign debug name to Simple function
+ FunNode fun = _code.link(tfp);
+ if( fun != null )
+ fun.setName(name); // Assign debug name to Simple function
}
} else {
+ // Since no initializer, not-final but might need an initializer
+
// Need an expression to infer the type.
// Also, if not-null then need an initializing expression.
// Allowed in a class def, because type/init will happen in the constructor.
@@ -801,34 +1090,33 @@ public class Parser {
throw errorSyntax("=expression");
// Initial value for uninitialized struct fields.
expr = switch( t ) {
- // Nullable pointers get a NIL; not-null get a TOP which
- // signals that they *must* be initialized in the constructor.
- case TypeNil tn -> tn.nullable() ? NIL : con(Type.TOP);
- case TypeInteger ti -> ZERO;
+ // Required pointers retain their declared shape, but include
+ // null until every constructor path assigns them.
+ case TypeNil tn -> tn.nullable() ? con(Type.NIL) : con(t.meet(Type.NIL));
+ case TypeInteger ti -> _code.ZERO;
case TypeFloat tf -> con(TypeFloat.FZERO);
// Bottom signals type inference: they must be initialized in
// the constructor and that's when we'll discover the type.
case Type tt -> { assert tt==Type.BOTTOM; yield con(tt); }
};
- // Nullable fields are set in the constructor, but remain shallow final.
- // e.g. final pointer to a r/w array
- if( t instanceof TypeNil tn && !tn.nullable() && !hasBang )
- fld_final = true;
+ // Every non-null pointer without a default is constructor-required.
+ // Without `!` it is also shallow-final; mutability and definite
+ // initialization are independent properties.
+ if( t instanceof TypeNil tn && !tn.nullable() ) {
+ uninit = true;
+ if( !hasBang ) fld_final = true;
+ }
}
// Lift expression, based on type
- Node lift = liftExpr(expr, t, xfinal, true);
- // Rebuild the deep RO 't', not returned from liftExpr
- if( xfinal && t instanceof TypeMemPtr tmp )
- t = tmp.makeRO();
-
- // Lift type to the declaration. This will report as an error later if
- // we cannot lift the type.
- if( !lift._type.isa(t) )
- lift = peep(new CastNode(t.widen(),null,lift));
+ // Do not let an authoritative Convert hide the nullable
+ // not-yet-initialized state from constructor-local type flow.
+ Node lift = uninit ? expr : convertExpr(expr, t);
+
// Define a new name
if( !_scope.define(name,t,xfinal || fld_final,lift, loc) )
throw error("Redefining name '" + name + "'", loc);
+ _scope.lookup(name)._uninit = uninit;
return lift;
}
@@ -840,59 +1128,132 @@ public class Parser {
*
* @return zero
*/
- private String _nestedType;
- private Node parseStruct() {
- // "struct" already parsed, so expect the struct name next.
- String old = _nestedType;
+ private Node parseStruct( ) {
+ // keyword "struct" already parsed, so expect the struct name next.
String typeName = requireId();
- if( old!=null ) typeName = old+"."+typeName;
- _nestedType = typeName;
- // New declared or forward-ref type
- TypeStruct ts = TYPES.get(typeName) instanceof TypeMemPtr tmp ? tmp._obj : TypeStruct.open(typeName);
- TYPES.put(typeName,TypeMemPtr.make(ts));
-
- // A Block scope parse, and inspect the scope afterward for fields.
- _scope.push(new Kind.Define(TypeMemPtr.make(ts)));
require("{");
- int lexlen = _scope._kinds.last()._lexSize, nvar=lexlen;
- while (!peek('}') && !_lexer.isEOF()) {
- parseStatement().isKill();
- while( nvar < _scope._vars._len ) {
- Var v = _scope.var(nvar++);
- if( !v.isFRef() ) {
- ts = ts.add( Field.make( v._name, v.type(), _code.getALIAS(), v._final, v._final && _scope.in( v._idx )._type != Type.TOP ) );
- TYPES.put( typeName, TypeMemPtr.make( ts ) );
- }
- }
- }
- // Count struct declarations
- int varlen = _scope._vars._len;
- ts = ts.close();
- TYPES.put(typeName,TypeMemPtr.make(ts));
+ // create type class:NESTED.typeName; no fields.
- // Generate an initializing struct
- StructNode s = new StructNode(ts);
- for( int i=lexlen; i<varlen; i++ )
- if( !_scope.var(i).isFRef() ) // Promote to outer scope, not defined here
- s.addDef(_scope.in(i)); // Keep the program code to generate instance
+ String old = _nestedType;
+ String fullName = _scope.depth()==1 && _nestedType.endsWith(typeName) ? old : (old+"."+typeName).intern();
+ _nestedType = fullName; // Recursive structs start with this as their basename
- // Save instance default constructor
- s.setType(s.compute()); // Do not call peephole, in case the entire struct is a big plain constant
- INITS.put(typeName,s.keep());
+ ReturnNode ret = parseStruct( false, fullName );
- // Having finalized the struct, upgrade all the 'self' pointers
- for( Node n : s._inputs )
- if( n._type instanceof TypeFunPtr tfp && _code.link(tfp) instanceof FunNode fun )
- _code.addAll( fun._outputs );
+ _nestedType = old;
+ // Insert a field into class:NESTED.typeName of "val typeName = fcn-ptr-to-init"
+ // Function does malloc internally.
+ // Future self: means subtyping has to deal with "who does the malloc"
+ String clzName = addClzPrefix(fullName);
+ TypeFunPtr sig = ret.fun().sig();
+ TypeStruct ts = (TypeStruct)TYPES.get(clzName);
+ String initName = initFieldName(fullName);
+ Field oldFld = ts==null ? null : ts.field(initName);
+ Field fld = oldFld==null
+ ? Field.make(initName,sig,_code.alias(_ref._cname),true)
+ : oldFld.makeFrom(sig);
+ Field ctorFld = ts==null ? null : ts.field("<ctor>");
+ ts = ctorFld==null
+ ? TypeStruct.make(clzName,false,fld)
+ : TypeStruct.make(clzName,false,fld,ctorFld);
+ TYPES.put(clzName,ts);
+
+ // Insert a field in the containing class with the nested class type.
+ // This is basically sugar for "val CLZNAME = <init>"
+ // and is a nicer version of updateSelfAsFieldsDiscovered.
+ _scope.define(typeName, sig, true, new FunPtrNode(sig,_code._start,ret).peephole(), loc());
- // Done with struct/block scope
- _nestedType = old;
require("}");
- require(";");
- _scope.pop();
- return ZERO;
+ return require(_code.ZERO,";");
+ }
+
+ // Parse a struct declaration (not an allocation); file-level is a class-init, and scope structs are normal
+ private ReturnNode parseStruct( boolean isClz, String typeName ) {
+ // Record & restore global state set during parsing the <init> code
+ Node oldCtrl= _scope.ctrl().keep();
+ Node oldMem = _scope.mem ().keep();
+
+ // Make the future clazz/instance struct.
+ TypeStruct tself = (TypeStruct)TYPES.get(typeName);
+ if( tself==null )
+ TYPES.put(typeName, tself = TypeStruct.make(typeName,true));
+ else {
+ assert tself._open;
+ TYPES.put(typeName,tself = tself.defined());
+ }
+
+ // Signature of the default constructor
+ TypeFunPtr sig = structInitSig(isClz,tself);
+ // Function and argument names of default constructor
+ String fname = (typeName + (isClz ? ".<clinit>" : ".<init>")).intern();
+ String[] ids = new String[]{"self", isClz ? "arg" : "#selfMem"};
+
+ // Struct decls look like function bodies. Parse function body normally.
+ ReturnNode ret = _parseFunctionBody(fname,sig,loc(),ids);
+ // Unwind global state.
+ ctrl(oldCtrl.unkeep());
+ mem (oldMem .unkeep());
+ return ret;
+ }
+
+ // <cl/init> signature: { self arg/selfMem -> BOT/selfMem }
+ // Self-memory is the rare *private* (never aliased) memory for the new object.
+ private TypeFunPtr structInitSig( boolean isClz, TypeStruct tself ) {
+ // Private uninitialized memory from new object, no escaped values.
+ TypeMem privMem = isClz ? null : TypeMem.make(1,tself,true,isClz,false,null,null);
+ Type targ = isClz ? TypeInteger.BOT : privMem;
+ // Return for private memory has to name all escaped fields, which are not known yet.
+ // Escape ALL fields, and sharpen later.
+ Type tret = isClz ? Type.BOTTOM : TypeMem.makePrivate(tself);
+ Type[] targs = new Type[]{ TypeMemPtr.make((byte)2,tself,isClz), targ };
+ // FIDX for FREFs is in the global namespace, needs the correct clz name.
+ String clzBare = tself._name;
+ int order = 3; // 3 is always the <init> order, 2nd in the file just behind the <clinit>
+ if( isClz ) {
+ assert startsClzPrefix( clzBare );
+ clzBare = clzBare.substring(clzPrefix.length()).intern();
+ order = 2; // 2 is always the <clinit> order, first in file and skipping reserved #1
+ }
+ return TypeFunPtr.make1((byte)2, true, targs, tret, _code.fidx(clzBare,order));
+ }
+
+ // A class type carries the constructor function as a required final field.
+ private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) {
+ return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true));
+ }
+
+ private static String initFieldName( String typeName ) {
+ if( startsClzPrefix(typeName) )
+ typeName = typeName.substring(clzPrefix.length());
+ int idx = typeName.lastIndexOf('.');
+ return (idx == -1 ? typeName : typeName.substring(idx+1)).intern();
+ }
+
+ private TypeStruct latestStruct( TypeStruct ts ) {
+ Type t = TYPES.get(ts._name);
+ return t instanceof TypeStruct ts2 ? ts2 : ts;
+ }
+
+ private Field fieldOrOpen( TypeStruct ts, String name, boolean xfinal ) {
+ ts = latestStruct(ts);
+ Field fld = ts.field(name);
+ if( fld != null ) return fld;
+ if( !ts._open || !ts._fref ) return null;
+ fld = Field.make(name,Type.BOTTOM,_code.alias(_ref._cname),xfinal);
+ TYPES.put(ts._name,ts = ts.add(fld));
+ if( _ctorOpenStruct != null && _ctorOpenStruct._name == ts._name )
+ _ctorOpenStruct = ts;
+ return fld;
+ }
+
+ private Var defineOpenConstructorField( String name ) {
+ if( _ctorOpenStruct==null ) return null;
+ Field fld = fieldOrOpen(_ctorOpenStruct,name,true);
+ if( fld == null ) return null;
+ _scope.define(name,fld._t,fld._final,con(Type.TOP),loc());
+ return _scope.lookup(name);
}
@@ -902,48 +1263,146 @@ public class Parser {
// ref type position.
// t = int|i8|i16|i32|i64|u8|u16|u32|u64|byte|bool | flt|f32|f64 | val | var | struct[?]
+
+ // Structs have nested names, and can be discovered with a partial name
+ // match. Example, searching for a type name "C.D" in the namespace "A.B".
+ // This will match "A.B.C.D", then "A.C.D", then "C.D".
+
+ // Will also match external names like "sys.io" or "externModule.subtype.subtype"
+
private Type type() {
- int old1 = pos();
// Only type with a leading `{` is a function pointer...
if( peek('{') ) return typeFunPtr();
// Otherwise you get a type name
+ int old1 = pos();
String tname = _lexer.matchId();
if( tname==null ) return null;
// Convert the type name to a type.
- Type t0 = TYPES.get(tname);
+ Type t0 = TYPES.get(tname), t1 = t0;
// No new types as keywords
- if( t0 == null && KEYWORDS.contains(tname) )
- return posT(old1);
- if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
+ if( KEYWORDS.contains(tname) ) {
+ if( t0 == null ) return posT(old1);
+ // Something like "int" or "f64" or "var".
+ // Can be "int[]" so still need to check array-ness
+ } else {
+ if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
+
+ if( t0 != null ) {
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ sname = tname+"."+sname;
+ Type t = TYPES.get(sname);
+ if( t==null ) { pos(old2); break; }
+ tname = sname;
+ t0 = t;
+ }
+ } else {
+ // Type A.B.C starts with just A. Search our nested type path,
+ // e.g. path "X.Y.Z", in order:
+
+ // If A==X its now a local type.
+ // If another dot, repeat for B==Y then C==Z
+ // Else if AX.B exists, done
+ // Else AX.B is a local forward ref.
+ //
+ // Else its a short cut form, one of:
+ // X.Y.Z.A
+ // X.Y.A
+ // X.A
+ // Else this all failed, assume a forward ref with A as an external symbol:
+ // A
+
+ String root = _nestedType;
+ int ridx = root.indexOf('.');
+ if( ridx != -1 ) root = root.substring(0,ridx);
+ if( tname.equals(root) ) {
+ boolean localForward = false;
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ sname = tname+"."+sname;
+ Type t = TYPES.get(sname);
+ if( t==null && !(_nestedType.equals(tname) || _nestedType.startsWith(tname+".")) && !localForward ) {
+ pos(old2);
+ break;
+ }
+ tname = sname;
+ t0 = t;
+ localForward |= t==null;
+ }
+ } else {
+ String nest = _nestedType, fullq = tname;
+ while( true ) {
+ fullq = nest+"."+tname; // Full qualified name
+ t0 = TYPES.get(fullq);
+ if( t0 != null )
+ { tname = fullq; break; } // Search succeeded
+ // Subtract a layer from nested typenames
+ int idx = nest.lastIndexOf('.');
+ if( idx== -1 ) break; // Search failed; leave as an external symbol
+ nest = nest.substring(0,idx);
+ }
+ if( t0==null ) {
+ CompUnit cu = ParseAll.findCompUnit(_code,_ref,tname);
+ if( cu != null ) {
+ tname = cu._cname;
+ t0 = TYPES.get(tname);
+ }
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ tname = tname+"."+sname;
+ t0 = TYPES.get(tname);
+ }
+ } else {
+ while( true ) {
+ int old2 = pos();
+ if( !match(".") ) break;
+ String sname = matchId();
+ if( sname==null ) { pos(old2); break; }
+ sname = tname+"."+sname;
+ Type t = TYPES.get(sname);
+ if( t==null ) { pos(old2); break; }
+ tname = sname;
+ t0 = t;
+ }
+ }
+ }
+ }
+
+ tname = tname.intern();
+ // Still no type found? Assume forward reference
+ t1 = t0 == null
+ ? TypeStruct.forward(tname) // Null: assume a forward ref type
+ : t0;
+ }
+
+ // Structs are always actually references
+ if( t1 instanceof TypeStruct ts1 )
+ t1 = TypeMemPtr.make(ts1);
- // Check for subtype.
- while( true ) {
- int old2 = pos();
- if( !match(".") ) break;
- String sname = _lexer.matchId();
- if( sname==null ) { pos(old2); break; }
- String tsname = tname+"."+sname;
- t0 = TYPES.get(tsname);
- if( t0==null ) { pos(old2); t0 = TYPES.get(tname); break; }
- tname = tsname;
- }
-
- // Still no type found? Assume forward reference
- Type t1 = t0 == null ? TypeMemPtr.make(TypeStruct.open(tname)) :t0; // Null: assume a forward ref type
// Nest arrays and '?' as needed
Type t2 = t1;
while( true ) {
if( match("?") ) {
- if( !(t2 instanceof TypeMemPtr tmp) )
+ if( t2 instanceof TypeMemPtr tmp ) {
+ if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
+ } else
throw error("Type "+t0+" cannot be null");
- if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
- t2 = tmp.makeNullable();
} else if( match("[~]") ) {
- t2 = typeAry(t2,true);
+ t2 = TypeMemPtr.make(typeAry(t2,true));
} else if( match("[]") ) {
- t2 = typeAry(t2,false);
+ t2 = TypeMemPtr.make(typeAry(t2,false));
} else
break;
}
@@ -955,28 +1414,63 @@ public class Parser {
int old2 = pos();
match("!");
String id = _lexer.matchId();
- if( !(peek(',') || peek(';') || match("->")) || id==null )
+ if( !(peek(',') || peek(';') || peek('=') || match("->")) || id==null )
return posT(old1); // Reset lexer to reparse
pos(old2); // Reset lexer to reparse
// Yes a forward ref, so declare it
- TYPES.put(tname,t1);
+ forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj);
// Return the (array, final) type
return t2;
}
- // Make an array type of t
- private TypeMemPtr typeAry( Type t, boolean efinal ) {
+ private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) {
+ TYPES.put(tname,tself);
+ String clzName = addClzPrefix( tname );
+ TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true));
+ return TypeMemPtr.make(tself);
+ }
+
+ private TypeMemPtr forwardRefType( String tname ) {
+ tname = fullTypeName(tname);
+ return forwardRefType(tname,TypeStruct.forward(tname));
+ }
+
+ private String fullTypeName( String tname ) {
+ String nest = _nestedType, fullq;
+ while( true ) {
+ fullq = nest+"."+tname;
+ if( TYPES.get(fullq) != null )
+ return fullq.intern();
+ int idx = nest.lastIndexOf('.');
+ if( idx== -1 ) return fullq.intern();
+ nest = nest.substring(0,idx);
+ }
+ }
+
+ // Make an array type of t. Always record a mutable version,
+ // but return the requested version.
+ private TypeStruct typeAry( Type t, boolean efinal ) {
if( t instanceof TypeMemPtr tmp && tmp.notNull() )
throw error("Arrays of reference types must always be nullable");
- String tname = "[]"+t.str();
- TypeMemPtr ta = (TypeMemPtr)TYPES.get(tname);
- if( ta==null )
- // Remember final version
- TYPES.put(tname, ta = TypeMemPtr.make( TypeStruct.makeAry(tname,TypeInteger.U32,_code.getALIAS(),t,_code.getALIAS(),true) ));
- Field elem = ta._obj.field("[]");
- if( elem._final == efinal )
- return ta;
- return TypeMemPtr.make( ta._obj.replace(elem.makeFrom(efinal)));
+ String tname = ("[]"+t.str()).intern();
+ TypeStruct ta = (TypeStruct)TYPES.get(tname);
+ if( ta==null ) {
+ // Primitive arrays use the null compunit
+ String rcname = TYPES.get(t) instanceof TypeStruct ? _ref._cname : ("_."+t).intern();
+ ta = TypeStruct.makeAry(tname,TypeInteger.U32,_code.alias(rcname),t,_code.alias(rcname), false );
+ TYPES.put(tname,ta);
+ }
+ if( !efinal ) return ta;
+ // Already have the aliases, just efinal is wrong
+ return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,true );
+ }
+
+ private Type implicitSelfType() {
+ TypeStruct self = _scope.constructorSelf();
+ // Ordinary instance methods accept the declared object shape. The
+ // constructor-only receiver starts at the low/default field values,
+ // and is built explicitly by constructorFunc().
+ return self == null ? TypePtr.PTR : TypeMemPtr.make(self);
}
// A function type is `{ type... -> type }` or `{ type }`.
@@ -985,16 +1479,19 @@ public class Parser {
match("{"); // Skip already-peeked '{'
Type t0 = type(); // Either return or first arg
if( t0==null ) return posT(old); // Not a function
- if( match("}") ) // No-arg function { -> type }
- return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0);
Ary<Type> ts = new Ary<>(Type.class);
+ // Always reserve a receiver slot. Outside constructor scopes it is
+ // broad and unused, so call sites can uniformly pass their self.
+ ts.push(implicitSelfType());
+ if( match("}") ) // No-arg function { -> type }
+ return TypeFunPtr.make(match("?"),ts.asAry(),t0);
ts.push(t0); // First argument
while( true ) {
if( match("->") ) { // End of arguments, parse return
Type ret = type();
if( ret==null || !match("}") )
return posT(old); // Not a function
- return TypeFunPtr.make(match("?"),false,ts.asAry(),ret);
+ return TypeFunPtr.make(match("?"),ts.asAry(),ret);
}
Type t1 = type();
if( t1==null ) return posT(old); // Not a function
@@ -1070,7 +1567,7 @@ public class Parser {
else if( !match("!=") ) break;
lhs.keep();
Node rhs = parseComparison();
- lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs).widen());
+ lhs = peep(new BoolNode.EQ(lhs.unkeep(),rhs));
if( !eq ) lhs = peep(new NotNode(lhs));
}
return lhs;
@@ -1087,46 +1584,64 @@ public class Parser {
*/
private Node parseComparison() {
Node lhs = parseShift();
- int dir = 0;
-
- while( true ) {
- int dir0;
- if( false ) ;
- else if( match("<=" ) ) dir0 = -1;
- else if( match(">=" ) ) dir0 = -2;
- else if( match("<" ) ) dir0 = 1;
- else if( match(">" ) ) dir0 = 2;
- else break;
- lhs.keep();
- // Check and record direction
- if( dir==0 ) {
- dir = Math.abs(dir0);
- Node rhs = parseShift();
- lhs = makeCompBool(dir0,lhs.unkeep(),rhs); // Convert to a bool
- } else if( dir == Math.abs(dir0) ) {
- // e0 < lhs < ???
- Node ifNode = new IfNode(ctrl(), lhs).peephole();
- Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole();
- Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
- // False side does nothing
- ScopeNode fScope = _scope.dup();
- fScope.ctrl(ifF);
- // True side executes next part of range
- ctrl(ifT);
- _scope.addGuards(ifT,lhs,false);
- Node rhs = parseShift();
- _scope.removeGuards(ifT); // TODO: I think should remain true as long as possible
- Node next = makeCompBool(dir0, lhs, rhs).keep();
- // Merge result
- RegionNode r = ctrl(_scope.mergeScopes(fScope, loc()));
- assert next._type.meet(lhs._type).isa( TypeInteger.BOOL );
- lhs = peep(new PhiNode("",TypeInteger.BOOL,r,next.unkeep(),lhs.unkeep()));
- r.peephole();
- } else {
+ int dir = parseCompDir();
+ // No compare
+ if( dir==0 ) return lhs;
+
+ // Compare
+ Node rhs = parseShift().keep();
+ Node cmp = makeCompBool(dir,lhs,rhs); // Convert to a bool
+
+ // Stacked compares?
+ int dir0 = parseCompDir();
+ if( dir0 == 0 ) {
+ rhs.unkeep();
+ if( rhs != cmp ) rhs.isKill();
+ return cmp;
+ }
+
+ // rhs is keeped() and becomes lhs
+ // cmp is NOT keeped() and is the last test
+ Node ifNode = new IfNode(ctrl(), cmp).peephole();
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ // False side does nothing but capture memory & side-effects
+ ScopeNode fail = _scope.dup();
+ fail.ctrl(ifF);
+
+ // Loop over stacked compares. Each loop moves the old RHS to become
+ // the new LHS, so e.g. (a < b < c < d) compares (LHS:a < RHS:b) then
+ // compares (LHS:b < RHS:c) then (LHS:c < RHS:d), etc.
+ while( dir0 != 0 ) {
+ if( Math.abs(dir) != Math.abs(dir0) )
throw error("Mixing relational directions in a chained relational test");
- }
- }
- return lhs;
+ // True side parses next arm of test
+ ctrl(ifT);
+ lhs = rhs; // lhs is keeped() and is old RHS
+ rhs = parseShift().keep();
+ cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool
+ ifNode = new IfNode(ctrl(), cmp).peephole();
+ ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole();
+ ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ // Merge result into the fail case
+ ctrl(ifF);
+ fail.mergeScopes(_scope.dup(), loc()).peephole();
+ // Check next stacked compare
+ dir0 = parseCompDir();
+ }
+ rhs.unkill();
+ ctrl(ifT);
+ RegionNode r = fail.mergeScopes(_scope, loc()).init();
+ _scope = fail;
+ return new PhiNode("", r,_code.ZERO,con(1)).peephole();
+ }
+
+ private int parseCompDir() {
+ if( match("<=") ) return -1; // Negative for "equals-or-less"
+ if( match(">=") ) return -2; // 1 for <, 2 for >
+ if( match("<" ) ) return 1; // Positive for just "less"
+ if( match(">" ) ) return 2;
+ return 0;
}
private Node makeCompBool( int dir0, Node lhs, Node rhs ) {
@@ -1134,10 +1649,9 @@ public class Parser {
if( Math.abs(dir0)==2 ) // Swap direction
{ Node tmp = lhs; lhs = rhs; rhs = tmp; }
lhs = peep(dir0 < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs));
- return peep(lhs.widen());
+ return peep(lhs);
}
-
/**
* Parse an additive expression
*
@@ -1158,7 +1672,7 @@ public class Parser {
ParseException err;
if( (err=lhs.err()) != null )
throw err;
- lhs = peep(lhs.widen());
+ lhs = peep(lhs);
}
return lhs;
}
@@ -1179,7 +1693,7 @@ public class Parser {
else if( match("-") ) lhs = new SubNode(lhs,null);
else break;
lhs.setDef(2,parseMultiplication());
- lhs = peep(lhs.widen());
+ lhs = peep(lhs);
}
return lhs;
}
@@ -1200,7 +1714,7 @@ public class Parser {
else if( match("/") ) lhs = new DivNode(lhs,null);
else break;
lhs.setDef(2,parseUnary());
- lhs = peep(lhs.widen());
+ lhs = peep(lhs);
}
return lhs;
}
@@ -1217,16 +1731,14 @@ public class Parser {
// Pre-dec/pre-inc
int old = pos();
if( match("--") || match("++") ) {
- int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post
+ int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
String name = _lexer.matchId();
if( name!=null ) {
Var n = _scope.lookup(name);
if( n != null && !(n.type() instanceof TypeMemPtr) ) {
if( n._final )
throw error("Cannot reassign final '"+n._name+"'");
- Node expr = n.type() instanceof TypeFloat
- ? peep(new AddFNode(_scope.in(n),con(TypeFloat.constant(delta))))
- : zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
+ Node expr = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
_scope.update(n,expr);
return expr;
}
@@ -1234,7 +1746,7 @@ public class Parser {
// Reset, try again
pos(old);
}
- if (match("-")) return peep(new MinusNode(parseUnary()).widen());
+ if (match("-")) return peep(new MinusNode(parseUnary()));
if (match("!")) return peep(new NotNode(parseUnary()));
// Else be a primary expression
return parsePrimary();
@@ -1246,7 +1758,7 @@ public class Parser {
* <pre>
* primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
* new Type | '(' expression ')' | Id['++','--'] |
- * [static.]* Id
+ * type['.' Id]*
* </pre>
* @return a primary {@link Node}, never {@code null}
*/
@@ -1254,95 +1766,113 @@ public class Parser {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
if( _lexer.peek('"') ) return newString(parseString());
if( matchx("true" ) ) return con(1);
- if( matchx("false") ) return ZERO;
- if( matchx("null" ) ) return NIL;
+ if( matchx("false") ) return _code.ZERO;
+ if( matchx("null" ) ) return con(Type.NIL);
if( match ("'" ) ) return parseChar();
if( match ("(" ) ) return parsePostfix(require(parseAsgn(), ")"));
if( matchx("new" ) ) return parsePostfix(alloc());
if( match ("{" ) ) return parsePostfix(require(func(),"}"));
- // Parse static variable lookup: `type.fld` in the type namespace
- // CNC: not a full type parse, just a typename parse
int pos = pos();
- Type t = type();
- if( t!=null ) {
- if( peek('.') )
- return parsePostfix(con(t));
- //pos(pos); // TODO: not a `type.fld`
- //return null;
- throw Utils.TODO();
- }
-
// Expect an identifier now
-
- // - if null, make fref, normal var handling
- // - if final constant, normal var handling
- // - if method var, scan skipping blocks & methods, must hit correct struct no fcns
- // - if normal var, scan skipping blocks, allowing last fcn
- // - NO fcns nested in methods, NO methods nested in fcns
-
String id = _lexer.matchId();
if( id == null || KEYWORDS.contains(id) )
throw errorSyntax("an identifier or expression");
+ int pos2 = pos();
+ // Attempt a local var lookup first
Var var = _scope.lookup(id);
-
if( var==null ) {
- // If missing, assume a forward reference
- _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc());
- var = _scope.lookup(id);
- }
+ // Not a local var, try again as a type name
+ pos(pos);
+ Type t = type();
+ if( t!=null ) {
+ // Static <clinit> typename lookup
+ if( peek('.') ) // type.fld, which is really a field lookup in the class object
+ return parsePostfix(con(t));
+ // Direct reference to a <clinit> type. Convert the instance
+ // type to a class type name
+ if( !(t instanceof TypeMemPtr tmp) ) {
+ //return new FRefNode(addClzPrefix(tmp._obj._name), loc()).peephole();
+ // Not a local var, but yes a bare type... disallow a local
+ // FREF and return null, not-a-primary parse.
+ pos(pos);
+ return null;
+ }
+ }
- // If a method var, allow nested blocks and methods, but not fcns or other structs.
- // Else safety check for accessing fields out of function scope.
- if( instance(var) ) {
- Node self = _scope.in(_scope.lookup("self")); // Insert self reference
- return parsePostfixName(self,id);
+ // Insert FREF outside enclosing Kind.Func scope.
+ pos(pos2);
+ t = FRefNode.FREF_TYPE;
+ // Check if this is a class.
+ CompUnit cu = ParseAll.findCompUnit(_code,_ref,id);
+ if( cu!=null ) {
+ // Must be a final class-type
+ String clzName = addClzPrefix(cu._cname);
+ TypeStruct clz = (TypeStruct)TYPES.get(clzName);
+ if( clz == null )
+ TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
+ t = TypeMemPtr.make((byte)2,clz,true);
+ }
+ // Define the FRef
+ var = _scope.defineFRef(id,t,cu!=null,loc());
}
// Load local value
Node rvalue = _scope.in(var);
- if( rvalue._type == Type.BOTTOM )
- if( rvalue instanceof FRefNode )
- return parsePostfix(rvalue);
- else throw error("Cannot read uninitialized field '"+id+"'");
+ if( var._fref )
+ return parsePostfix(rvalue);
+ // Required constructor fields begin as their declared pointer type
+ // widened to include null. Inside the explicit constructor, any read
+ // before all paths replace that value observes a partially initialized
+ // object. Ordinary methods run only after construction is complete;
+ // their enclosing declaration scope still carries the same Var marker.
+ if( inExplicitConstructor() && var._uninit && rvalue._type instanceof TypeNil tn && tn.nullable() )
+ throw error("Field '"+id+"' might not be initialized",var._loc);
+
+ // Check for an instance field load
+ int kx = _scope.kindx(var); // Declaration scope
+ int fx = _scope.enclosingFunction(); // Enclosing function scope
+ Kind kk = _scope._kinds.at(kx);
+ Kind fk = _scope._kinds.at(fx);
+ // Access instance field from 'self'
+ if( kx+1 == fx && kk instanceof Kind.Func inst && FunNode.isInstance(inst._name) && fk instanceof Kind.Func method )
+ return parsePostfixName(_scope.in(method._lexSize),id);
+
+ // Check for a function-escaping variable; these require true
+ // closures. Final constants are OK; final vars require a hidden var
+ // argument; not-final fields can just use an explicit struct arg.
+ if( kx < fx && // Out of scope
+ !(var._final && rvalue._type.isConstant()) ) { // Not a final constant
+ if( kx > 0 ) // Global scope is OK, only ever one of these (not one per function invoke)
+ throw error("Variable '"+var._name+"' is out of function scope and must be a final constant");
+
+ // var.idx==2 is self argument, 3 is args, 4 or more are now static globals
+ if( var._idx >= 4 ) {
+ // Read from global scope.
+ // Surely a nicer way to get the top-level scope compile name?
+ Var clzptr = _scope.var( 2 );
+ // Get the most recent sharpen global
+ TypeStruct clz = (TypeStruct) TYPES.get( ((TypeMemPtr) clzptr.type())._obj._name );
+ TypeMemPtr clztmp = TypeMemPtr.make( (byte) 2, clz, true );
+ return parsePostfixName( con( clztmp ), id );
+ //return parsePostfixName(_scope.in(2),id);
+ }
+ }
// Check for assign-update, x += e0;
char ch = _lexer.matchOperAssign();
- if( ch==0 ) // Normal primary, check for postfix updates
- return parsePostfix(rvalue);
+ if( ch==0 ) { // Normal primary, check for postfix updates
+ int selfx = _scope.kindFcn(var)._lexSize;
+ // Might be a method call from inside a method, so no explicit "self".
+ // Pass the in-scope "self"
+ return parsePostfixMethod(rvalue,_scope.in(selfx));
+ }
// Assign-update direct into Scope
Node op = opAssign(ch,rvalue, var.type() );
_scope.update(var,op);
return postfix(ch) ? rvalue.unkeep() : op;
}
- // true if instance variable lookup (var is in any number of nested blocks,
- // then an instance function then inside a struct declaration).
- private boolean instance(Var var ) {
- // If a method var, allow nested blocks and methods, but not fcns or other structs
- Kind kind = _scope.kind(var);
- if( kind instanceof Kind.Define ) {
- for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- ) {
- Kind xkind = _scope._kinds.at(i);
- if( xkind instanceof Kind.Block ) continue;
- if( xkind instanceof Kind.Func ) {
- if( _scope._kinds.at(i-1)==kind )
- return true; // Instance variable: lookup in a method in a decl
- }
- throw Utils.TODO();
- }
- } else {
- // If a normal var, allow any nested blocks, and a last fcn -
- // but not functions further out (which involves capture).
- Node def = _scope.in(var._idx);
- for( int i = _scope.depth()-1; i>=0 && var._idx < _scope._kinds.at(i)._lexSize; i-- )
- if( _scope._kinds.at(i) instanceof Kind.Func &&
- !(def instanceof FRefNode) && !(var._final && def._type.isConstant()) )
- throw error("Variable '"+var._name+"' is out of function scope and must be a final constant");
- }
- return false;
- }
-
// Check for assign-update, "x += e0".
// Returns "x + e0"; the caller assigns value.
// if ch is postfix, then lhs is kept and caller will unkeep and return it.
@@ -1365,11 +1895,11 @@ public class Parser {
case '-' -> new SubNode(lhs,rhs);
case '*' -> new MulNode(lhs,rhs);
case '/' -> new DivNode(lhs,rhs);
- default -> throw Utils.TODO();
+ default -> throw TODO();
};
// Convert to float ops, or narrow int types; error if not declared type.
// Also, if postfix LHS is still keep()
- return liftExpr(peep(op.widen()),t,false,true);
+ return convertExpr(peep(op),t.glb(false));
}
@@ -1378,124 +1908,163 @@ public class Parser {
*/
private Node alloc() {
Type t = type();
- if( t==null ) throw error("Expected a type");
+ if( t==null ) {
+ String tname = _lexer.matchId();
+ if( tname==null || KEYWORDS.contains(tname) )
+ throw error("Expected a type");
+ t = forwardRefType(tname);
+ }
// Parse ary[ length_expr ]
if( match("[") ) {
if( !t.makeZero().isa(t) )
- throw error("Cannot allocate a non-nullable, since arrays always zero/null fill");
- Node len = parseAsgn();
- if( !(len._type instanceof TypeInteger) )
- throw error("Cannot allocate an array with length "+len._type);
+ throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled");
+ Node len = parseAsgn().keep();
require("]");
- TypeMemPtr tmp = typeAry(t,false);
- return newArray(tmp._obj,len);
- }
-
- if( !(t instanceof TypeMemPtr tmp) )
- throw error("Cannot allocate a "+t.str());
-
- // Parse new struct { default_initialization }
- StructNode s = INITS.get(tmp._obj._name);
- if( s==null ) throw error("Unknown struct type '" + tmp._obj._name + "'");
-
- Field[] fs = s._ts._fields;
- // if the object is fully initialized, we can skip a block here.
- // Check for constructor block:
- boolean hasConstructor = match("{");
- Ary<Node> init=s._inputs; int idx=0;
- if( hasConstructor ) {
- idx = _scope.nIns();
- // Push a scope, and pre-assign all struct fields.
- _scope.push(new Kind.Block());
- Lexer loc = loc();
- for( int i=0; i<fs.length; i++ )
- // An initial TOP means the field needs to be initialized. We
- // store a BOT initially; any partial init will fall to BOT
- // (merge BOT and the partial) and be obviously only a partial
- // init. To be initialized the field needs a full clobber.
- _scope.define(fs[i]._fname, fs[i]._t, fs[i]._final, s.in(i)._type==Type.TOP ? con(Type.BOTTOM) : s.in(i), loc);
- // Parse the constructor body
- require(parseBlock(new Kind.Alloc(tmp)),"}");
- init = _scope._inputs;
- }
- // Check that all fields are initialized
- for( int i=idx; i<init.size(); i++ )
- if( init.at(i)._type == Type.TOP || init.at(i)._type == Type.BOTTOM )
- throw error("'"+tmp._obj._name+"' is not fully initialized, field '" + fs[i-idx]._fname + "' needs to be set in a constructor");
- Node ptr = newStruct(tmp._obj, off(tmp._obj, " len"), idx, init );
- if( hasConstructor )
- _scope.pop();
- return ptr;
+ return allocArray(typeAry(t,false),len);
+ } else {
+ if( !(t instanceof TypeMemPtr tmp) )
+ throw error("Cannot allocate a "+t.str());
+ return allocStruct(tmp._obj);
+ }
}
+ private Node allocStruct(TypeStruct ts) {
+ Ary<Node> ctorArgs = constructorArgs(); // CNC TODO- BAD THREADING SELF-MEM
+ Node size = off(ts, " len");
+
+ // Build a NewNode; takes in ctrl and size.
+ // Produces a ptr and a private mem.
+ NewNode nnn = new NewNode(ts, ctrl(), size ).init();
+ ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
+ ProjNode smem = new ProjNode(nnn,1,"#selfMem").init();
+
+ // Find a "class:XXX" struct, with field "XXX" function ptr as the <init>
+ TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(ts._name));
+ if( clz==null )
+ throw error("Unknown struct type '" + ts._name + "'");
+ TypeFunPtr init = (TypeFunPtr)clz.field(initFieldName(ts._name))._t;
+
+ // Call construct <init>($ctrl,$mem,NewNode.self,NewNode.#selfMem) and
+ // encourage inlining
+ Node initSelf = self._type.isa(init.arg(0)) ? self : peep(new CheckCastNode(init.arg(0),ctrl(),self));
+ Ary<Node> args = new Ary<>(Node.class){{add(ctrl()); add(mem()); add(initSelf); add(smem); add(ConstantNode.seed(init)); }};
+ Node selfMem = functionCall( args );
+ // The returned value is a merge of private *Memory* and NOT some Scalar
+
+ // Optional user constructor; same as the default constructor; private
+ // memory going in and out.
+ Field ctor = clz.field("<ctor>");
+ if( ctor != null )
+ selfMem = constructorCall((TypeFunPtr)ctor._t,self,selfMem,ctorArgs);
+ else if( !ctorArgs.isEmpty() )
+ throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
+ if( match("{") )
+ throw error("Inline constructor blocks are no longer supported; define a constructor in '" + ts._name + "'");
+
+ // Might be TOP if parsing in dead/unreachable code
+ if( selfMem._type == Type.TOP ) {
+ _code.add(selfMem);
+ return self.unkeep();
+ }
+
+ // Escape all new aliases. EscapeNode inputs are the self pointer, the
+ // merged private memory, then all the named public aliases. The
+ // output is all the newly merged public aliases - but not actually
+ // bulk memory.
+ selfMem.keep();
+ for( Field fld : ts._fields ) {
+ Field fld2 = fld._final ? fld : (Field)fld.glb(true);
+ Node prior = mem();
+ Node esc = peep(new EscapeNode(fld2,self,selfMem,prior));
+ mem(mergeAlias(prior,fld._alias,esc));
+ }
+
+ if( selfMem.unkeep().isUnused() ) selfMem.kill();
+ else _code.add(selfMem);
+ return self.unkeep();
+ }
- /**
- * Return a NewNode initialized memory.
- * @param obj is the declared type, with GLB fields
- * @param init is a collection of initialized fields
- */
- private Node newStruct( TypeStruct obj, Node size, int idx, Ary<Node> init ) {
- Field[] fs = obj._fields;
- if( fs==null )
- throw error("Unknown struct type '" + obj._name + "'");
- int len = fs.length;
- Node[] ns = new Node[2+len];
- ns[0] = ctrl(); // Control in slot 0
- // Total allocated length in bytes
- ns[1] = size;
- // Memory aliases for every field
- for( int i = 0; i < len; i++ )
- if( !fs[i]._one )
- ns[2+i] = memAlias(fs[i]._alias);
- Node nnn = new NewNode(TypeMemPtr.make(obj), ns).peephole().keep();
- for( int i = 0; i < len; i++ )
- if( !fs[i]._one )
- memAlias(fs[i]._alias, new ProjNode(nnn,i+2,memName(fs[i]._alias)).peephole());
- Node ptr = new ProjNode(nnn.unkeep(),1,obj._name).peephole().keep();
-
- // Initial nonzero values for every field
- for( int i = 0; i < len; i++ ) {
- Node val = init.get( i + idx );
- if( !fs[i]._one && val._type != val._type.makeZero() ) {
- Node mem = memAlias(fs[i]._alias);
- Node st = new StoreNode(loc(),fs[i]._fname,fs[i]._alias,fs[i]._t,mem,ptr,off(obj,fs[i]._fname),val,true).peephole();
- memAlias(fs[i]._alias,st);
- }
+ private Ary<Node> constructorArgs() {
+ Ary<Node> args = new Ary<>(Node.class);
+ if( !match("(") )
+ return args;
+ while( !peek(')') ) {
+ Node arg = parseAsgn();
+ if( arg==null ) break;
+ args.push(arg.keep());
+ if( !match(",") ) break;
}
+ require(")");
+ return args;
+ }
+
+ // Constructors are called with two extra arguments: self and selfMem.
+ private Node constructorCall(TypeFunPtr ctor, Node self, Node selfMem, Ary<Node> ctorArgs) {
+ Ary<Node> args = new Ary<>(Node.class);
+ Node ctorSelf = self._type.isa(ctor.arg(0))
+ ? self
+ : peep(new CheckCastNode(ctor.arg(0),ctrl(),self));
+ args.add(ctrl());
+ args.add(mem());
+ args.add(ctorSelf);
+ args.add(selfMem);
+ for( Node arg : ctorArgs )
+ args.add(arg.unkeep());
+ args.add(ConstantNode.seed(ctor));
- return ptr.unkeep();
+ CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
+ CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
+ call.peephole();
+ cend = (CallEndNode)cend.keep().peephole();
+ ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
+ mem (new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ return new ProjNode(cend.unkeep(),2,"#selfMem").peephole();
}
- private static final Ary<Node> ALTMP = new Ary<>(Node.class);
- private Node newArray(TypeStruct ary, Node len) {
- ConFldOffNode base = off(ary, "[]");
- int scale= ary.aryScale();
- Node size = peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(scale)))));
- ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._t.makeZero()));
- return newStruct(ary,size,0,ALTMP);
+ private Node allocArray(TypeStruct ts, Node len) {
+ Field lenFld = ts.field("#" );
+ Field bodyFld = ts.field("[]");
+
+ ConFldOffNode base = off(ts, "[]");
+ int scale = ts.aryScale();
+ Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale)))));
+
+ // Build a NewNode; takes in and puts out all aliases.
+ NewNode nnn = new NewNode(ts, ctrl(), size ).init();
+ ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
+ ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep();
+
+ // Store length. Rest of array is zero'd via CALLOC during CodeGen.
+ // Length is casted to sanity.
+ // TODO: Needs runtime check.
+ Node st = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true));
+
+ // TODO: Allow add-on "constructor" to init the array elements
+
+ Node prior = mem();
+ Node esc = peep(new EscapeNode(lenFld,self,st,prior));
+ mem(mergeAlias(prior,lenFld._alias,esc));
+ prior = mem();
+ esc = peep(new EscapeNode(bodyFld,self,smem,prior));
+ mem(mergeAlias(prior,bodyFld._alias,esc));
+ smem.unkeep();
+ return self.unkeep();
}
+
private Node newString(String s) {
- TypeMemPtr tmp = typeAry(TypeInteger.U8,true);
- int lenAlias = tmp._obj.field("#" )._alias;
- int elemAlias = tmp._obj.field("[]")._alias;
- TypeConAryB con = TypeConAryB.make(s);
- Type elem = con.elem();
+ TypeStruct ts = typeAry(TypeInteger.U8,true);
+ int lenAlias = ts.field("#" )._alias;
+ int elemAlias = ts.field("[]")._alias;
+ TypeConAryB body = TypeConAryB.make(s);
+ TypeInteger slen = TypeInteger.constant(s.length());
// Make a TMP, not-null (byte)2, singleton (true) (requires text
// strings are hash-interned), with a TypeStruct having a constant
// array body.
- TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, elemAlias, true),true);
+ TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", slen, lenAlias, body, elemAlias, true),true);
assert str.isConstant();
return con(str);
}
- // We set up memory aliases by inserting special vars in the scope these
- // variables are prefixed by $ so they cannot be referenced in Simple code.
- // Using vars has the benefit that all the existing machinery of scoping
- // and phis work as expected
- private Node memAlias(int alias ) { return _scope.mem(alias ); }
- private void memAlias(int alias, Node st) { _scope.mem(alias, st); }
- public static String memName(int alias) { return ("$"+alias).intern(); }
-
/**
* Parse postfix expression; this can be a field expression, an array
* lookup or a postfix operator like '#'
@@ -1508,17 +2077,34 @@ public class Parser {
* expr '(' [args,]* ')' // Function call
* </pre>
*/
+ private Node parsePostfixMethod(Node expr, Node self) {
+ // TODO: This is too broad because the parser lacks strong types here.
+ // Some normal functions can end up with a "self" argument.
+
+ // Self method call? Self is a TMP-not-CLZ, then assume and pass self.
+ if( match("(") ) {
+ expr = parsePostfix(require(functionCall(expr,self),")"));
+ } else
+ self.isKill(); // Not a method call, no need for self
+ return parsePostfix(expr);
+ }
private Node parsePostfix(Node expr) {
String name;
if( match(".") ) name = requireId();
else if( match("#") ) name = "#";
else if( match("[") ) name = "[]";
- else if( match("(") ) return parsePostfix(require(functionCall(expr,null),")"));
+ // Can get here without an explicit receiver: "f()" or
+ // "self.method()(next)(next)(next)". Pass the current function self.
+ else if( match("(") ) return parsePostfix(require(functionCall(expr,defaultSelf()),")"));
else return expr; // No postfix
return parsePostfixName(expr,name);
}
+ private Node defaultSelf() {
+ return _scope.in(_scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize);
+ }
+
/**
* Parse postfix expression; this can be a field expression, an array
* lookup or a postfix operator like '#'
@@ -1529,97 +2115,96 @@ public class Parser {
* expr ('[' expr ']')* [ [op]= expr ] // Array reference
* </pre>
*/
+
private Node parsePostfixName(Node expr, String name) {
+ // Keep expr across possible updates
+ expr.keep();
- if( expr._type==Type.NIL )
- throw error("Accessing unknown field '" + name + "' from 'null'");
-
- // Sanity check expr for being a reference
- if( !(expr._type instanceof TypeMemPtr ptr) )
- throw error( "Expected "+(name=="#" || name=="[]" ? "array" : "reference")+" but found " + expr._type.str() );
-
- // Find the field from the Type. Lookup in the base object field names.
- TypeMemPtr ptr2 = (TypeMemPtr)TYPES.get(ptr._obj._name);
- ptr = (TypeMemPtr)ptr.join(ptr2);// Upgrade to latest TYPES
- TypeStruct base = ptr._obj;
- int fidx = base.find(name);
- if( fidx == -1 )
- throw error("Accessing unknown field '" + name + "' from '" + ptr.str() + "'");
-
- // Get field type and layout offset from base type and field index fidx
- Field f = base._fields[fidx]; // Field from field index
- Type tf = f._t;
- if( tf instanceof TypeMemPtr ftmp && ftmp.isFRef() )
- tf = ftmp.makeFrom(((TypeMemPtr)(TYPES.get(ftmp._obj._name)))._obj);
- if( base.isAry() && tf instanceof TypeConAry con )
- tf = con.elem();
+ // Get the best known TypeStruct from a pointer type.
+ TypeStruct ts = null, ts2;
+ if( expr._type instanceof TypeMemPtr tmp && (ts2=((TypeStruct)TYPES.get(tmp._obj._name))) != null )
+ ts = (TypeStruct)tmp._obj.join(ts2);
+ // A Load's flow type is intentionally not promised during parsing,
+ // but its declaring field type is still known. Recover that static
+ // type for chained accesses such as `_grow(1).buf[len++]`; otherwise
+ // the array Store is parsed as alias #1 and can hide the independent
+ // side effect of the postfix `len++`.
+ if( ts==null && expr instanceof LoadNode load ) {
+ TypeStruct ots = load.off() instanceof ConFldOffNode coff ? coff._ts : null;
+ if( ots==null && load.ptr()._type instanceof TypeMemPtr owner ) {
+ Type owner0 = TYPES.get(owner._obj._name);
+ ots = owner0 instanceof TypeStruct obj ? obj : owner._obj;
+ }
+ if( ots != null ) {
+ Field loaded = ots.field(load._name);
+ if( loaded != null && loaded._t instanceof TypeMemPtr ltmp ) {
+ Type loaded0 = TYPES.get(ltmp._obj._name);
+ ts = loaded0 instanceof TypeStruct obj ? obj : ltmp._obj;
+ }
+ }
+ }
+
+ Field fld = ts==null ? null : ts.field(name);
+ if( fld==null && ts!=null )
+ fld = fieldOrOpen(ts,name,false);
+ // With no field, we will update the bulk memory
+ int alias = fld==null ? 1 : fld._alias;
+ Type decl = fld==null ? Type.BOTTOM : fld._t;
// Field offset; fixed for structs, computed for arrays
- Node off = (name.equals("[]") // If field is an array body
- // Array index math
- ? peep(new AddNode(off(base,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),con(base.aryScale())))))
- // Struct field offsets are hardwired
- : off(base,name)).keep();
+ Node off = (name=="[]"
+ // Array element math, with unknown base offset and shift amount.
+ ? peep(new AddNode(fldoff(expr,"[]"),peep(new ShlNode(null,require(parseAsgn(),"]"),fldoff(expr,"<<")))))
+ // Named field offset on an unknown struct type
+ : fldoff(expr,name)).keep();
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
if( matchOpx('=','=') ) {
+ // Field assignment
Node val = parseAsgn().keep();
- Node lift = liftExpr( val, tf, f._final, false );
-
- Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr, off.unkeep(), lift, false);
- // Arrays include control, as a proxy for a safety range check.
- // Structs don't need this; they only need a NPE check which is
- // done via the type system.
- if( base.isAry() ) st.setDef(0,ctrl());
- memAlias(f._alias, st.peephole());
+ // Lift value for store
+ Node lift = fld==null ? val : new ConvertNode(fld._t,val).peephole();
+ // Memory for store, post assignment expression
+ Node mem = mem();
+ // Store to field
+ mem.keep();
+ Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), lift, false).peephole().keep();
+ storeMem(st,alias,mem);
+ st.unkeep();
+ mem.unkeep();
+
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
- // Keep expr for possible store update
- expr.keep();
+ // Memory for load
+ Node mem = mem();
+ // Loading from a constant array, the declared type is the
+ // meet-over-elements and not the array itself.
+ if( name=="[]" && decl instanceof TypeConAry conary )
+ decl = conary.elem();
+
// Load field
- Node load = f._one && INITS.containsKey( base._name )
- ? INITS.get(base._name).in(base.find(name))
- : new LoadNode(loc(),name, f._alias, tf, memAlias(f._alias), expr, off);
- // Arrays include control, as a proxy for a safety range check
- // Structs don't need this; they only need a NPE check which is
- // done via the type system.
- if( base.isAry() && !name.equals("#") ) load.setDef(0,ctrl());
- load = peep(load);
+ Node load = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off));
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
char ch = _lexer.matchOperAssign();
if( ch!=0 ) {
- Node op = opAssign(ch,load, tf );
- Node st = new StoreNode(loc(), name, f._alias, tf, memAlias(f._alias), expr.unkeep(), off.unkeep(), op, false);
- // Arrays include control, as a proxy for a safety range check.
- // Structs don't need this; they only need a NPE check which is
- // done via the type system.
- if( base.isAry() ) st.setDef(0,ctrl());
- memAlias(f._alias, peep(st));
+ if( fld!=null && decl==Type.BOTTOM )
+ throw error( "'" + ts._name + "' is not fully initialized, field '" + fld._fname + "' needs to be set in a constructor" );
+ Node op = opAssign(ch,load, decl );
+ mem.keep();
+ Node st = new StoreNode(loc(), name, alias, decl.glb(true), ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
+ storeMem(st,alias,mem);
+ st.unkeep();
+ mem.unkeep();
+
load = postfix(ch) ? load.unkeep() : op;
// And use the original loaded value as the result
return load;
- //throw Utils.TODO(); // NO POSTFIX AFTER STORE, JUST RETURN
- } else {
- off.unkill();
- // Method call: Loaded a function from a named field, AND the base
- // field type is final.
- if( load._type instanceof TypeFunPtr && name!="[]" &&
- !(load instanceof ExternNode) &&
- ((TypeMemPtr)TYPES.get(base._name))._obj.field(name)._final &&
- // And calling a function
- match("(") ) {
- return parsePostfix(require(functionCall(load,expr),")"));
- }
-
- // Drop expr, keeping load alive
- load.keep();
- expr.unkill();
- load.unkeep();
}
-
- return parsePostfix(load);
+ off.unkill();
+ // Might be a method call, so pass expr as 'self'
+ return parsePostfixMethod(load,expr.unkeep());
}
@@ -1653,15 +2238,9 @@ public class Parser {
private Node func() {
Ary<Type> ts = new Ary<>(Type.class);
Ary<String> ids = new Ary<>(String.class);
-
- // Defined in constructor? Add `self` argument. "static" calls still
- // add a `self` they just ignore it.
- if( _scope._kinds.last() instanceof Kind.Define define ) {
- // Upgrade defined struct to latest fields
- TypeMemPtr tmp = (TypeMemPtr)TYPES.get(define._tmp._obj._name);
- ts .push(tmp);
- ids.push("self");
- }
+ Type self = implicitSelfType();
+ ts.push(self);
+ ids.push(self == TypePtr.PTR ? "#self" : "self");
// Parse other arguments
_lexer.skipWhiteSpace();
@@ -1676,9 +2255,42 @@ public class Parser {
}
require("->");
// Make a concrete function type, with a fidx
- TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM));
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname));
ReturnNode ret = parseFunctionBody(tfp,loc,ids.asAry());
- return con(ret._fun.sig());
+ return new FunPtrNode(tfp,_code._start,ret).peephole();
+ }
+
+ private Node constructorFunc(String name) {
+ require("{");
+ Ary<Type> ts = new Ary<>(Type.class);
+ Ary<String> ids = new Ary<>(String.class);
+ TypeStruct self = _scope.constructorSelf();
+ assert self != null;
+ ts.push(TypeMemPtr.make(constructorRecv(self)));
+ ids.push("self");
+ // Explicit constructors use the same hidden allocation arguments as
+ // <init>: public memory is the call memory edge (index 1), followed by
+ // self (index 2) and the private, unescaped object memory (index 3).
+ ts.push(TypeMem.makePrivate(self));
+ ids.push("#selfMem");
+
+ _lexer.skipWhiteSpace();
+ Lexer loc = loc();
+ while( true ) {
+ Type t = type();
+ if( t==null ) break;
+ String id = requireId();
+ ts .push(t );
+ ids.push(id);
+ match(",");
+ }
+ require("->");
+ TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),TypeMem.makePrivate(self),_code.fidx(_ref._cname));
+ Node[] preParse = _scope.save();
+ ReturnNode ret = _parseFunctionBody((self._name+"."+name).intern(),tfp,loc,ids.asAry());
+ _scope.restore(preParse);
+ require("}");
+ return new FunPtrNode(tfp,_code._start,ret).peephole();
}
/**
@@ -1690,15 +2302,13 @@ public class Parser {
private Node functionCall(Node fcn, Node self) {
if( fcn._type == Type.NIL )
throw error("Calling a null function pointer");
- if( !(fcn instanceof FRefNode) && !fcn._type.isa(TypeFunPtr.BOT) )
- throw error("Expected a function but got "+fcn._type.glb(false).str());
fcn.keep(); // Keep while parsing args
Ary<Node> args = new Ary<>( Node.class );
args.push(null); // Space for ctrl,mem
args.push(null);
if( self != null ) // Method call has a self
- args.push(self);
+ args.push(self.keep());
while( !peek(')') ) {
Node arg = parseAsgn();
if( arg==null ) break;
@@ -1707,11 +2317,15 @@ public class Parser {
}
// Control & memory after parsing args
args.set(0,ctrl().keep());
- args.set(1,_scope.mem().merge().keep());
+ args.set(1,mem().keep());
args.push(fcn); // Function pointer
// Unkeep them all
for( Node arg : args )
arg.unkeep();
+ return functionCall(args);
+ }
+
+ private Node functionCall( Ary<Node> args ) {
// Dead into the call? Skip all the node gen
if( ctrl()._type == Type.XCONTROL ) {
for( Node arg : args )
@@ -1723,17 +2337,16 @@ public class Parser {
// Into the call
CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
// Post-call setup
- CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole();
+ CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
call.peephole(); // Rerun peeps after CallEnd, allows early inlining
+ cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline
// Control from CallEnd
ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
// Memory from CallEnd
- MemMergeNode mem = new MemMergeNode(true);
- mem.addDef(null); // Alias#0
- mem.addDef(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
- _scope.mem(mem);
+ Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ mem(mem);
// Call result
- return new ProjNode(cend,2,"#2").peephole();
+ return new ProjNode(cend.unkeep(),2,"#2").peephole();
}
// Just after parsing "type foo = " can parse `"C"`
@@ -1745,10 +2358,14 @@ public class Parser {
return false;
}
// External linked constant
- private Node externDecl( String ex, Type t ) {
- if( t instanceof TypeFunPtr tfp ) // Generic TFP from type parse
- t = _code.makeFun(tfp); // Get a FIDX, becomes a constant
- return new ExternNode(t,ex).peephole();
+ private ConstantNode externDecl( String ex, Type t ) {
+ if( t instanceof TypeFunPtr tfp ) { // Generic TFP from type parse
+ int fidx = _code.fidx(_ref._cname); // Get a FIDX
+ tfp = tfp.makeFrom(fidx); // Become a constant
+ _code.externFunc(fidx,ex); // Map fidx to extern name
+ t = tfp;
+ }
+ return (ExternNode)(new ExternNode(t,ex).peephole());
}
/**
@@ -1756,13 +2373,20 @@ public class Parser {
*
* <pre>
* integerLiteral: [1-9][0-9]* | [0]
- * floatLiteral: [digits].[digits]?[e [digits]]?
+ * floatLiteral: [digits].[digits]?[e [digits]]?[f]?
* </pre>
*/
private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
- public static Node con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); }
- public static ConstantNode con( Type t ) { return (ConstantNode)new ConstantNode(t).peephole(); }
- public static ConFldOffNode off( TypeStruct base, String fname ) { return (ConFldOffNode)(new ConFldOffNode(base._name,fname).peephole()); }
+ ConstantNode con( long con ) { return _code.con(con); }
+ ConstantNode con( Type t ) { return _code.con(t ); }
+ // Field offset for a known type (but unknown offset)
+ public static ConFldOffNode off( TypeStruct ts, String fname ) {
+ return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
+ }
+ // Field offset for an unknown type
+ public Node fldoff( Node n, String name ) {
+ return new FldOffNode(n,name,loc()).peephole();
+ }
public Node peep( Node n ) {
// Peephole, then improve with lexically scoped guards
return _scope.upcastGuard(n.peephole());
@@ -1772,11 +2396,27 @@ public class Parser {
private String parseString() {
if( !peek('"') ) return null;
_lexer.inc();
- int start = pos();
- while( !_lexer.isEOF() && _lexer.nextChar()!= '"' ) ;
- if( _lexer.isEOF() )
- throw error("Unclosed string");
- return new String(_lexer._input,start,pos()-start-1);
+ StringBuilder sb = new StringBuilder();
+ while( !_lexer.isEOF() ) {
+ char c = _lexer.nextChar();
+ if( c=='"' ) return sb.toString();
+ if( c!='\\' ) {
+ sb.append(c);
+ continue;
+ }
+ if( _lexer.isEOF() )
+ throw error("Unclosed string");
+ char esc = _lexer.nextChar();
+ sb.append(switch( esc ) {
+ case 'n' -> '\n';
+ case 't' -> '\t';
+ case 'r' -> '\r';
+ case '\\' -> '\\';
+ case '"' -> '"';
+ default -> throw error("Unknown string escape \\\\"+esc);
+ });
+ }
+ throw error("Unclosed string");
}
// Already parsed "'"
@@ -1784,6 +2424,14 @@ public class Parser {
return require(con(TypeInteger.constant(_lexer.nextChar())),"'");
}
+ private boolean noLowerCase(String s) {
+ for( int i=0; i<s.length(); i++ )
+ if( Character.isLowerCase(s.charAt(i)) )
+ return false;
+ return true;
+ }
+
+
//////////////////////////////////
// Utilities for lexical analysis
@@ -1856,6 +2504,11 @@ public class Parser {
// Start of current line
private int _line_start = 0;
+ /** True when this location precedes {@code that} in the same source. */
+ public boolean before(Lexer that) {
+ return that != null && _input == that._input && _position < that._position;
+ }
+
/**
* Record the source text for lexing
*/
@@ -2002,7 +2655,7 @@ public class Parser {
// Used for errors
String getAnyNextToken() {
- if (isEOF()) return "";
+ if (isEOF()) return "EOF";
if (isIdStart(peek())) return parseId();
if (isNumber(peek())) return parseNumberString();
if (isPunctuation(peek())) return parsePunctuation();
@@ -2016,17 +2669,26 @@ public class Parser {
private Type parseNumber() {
int old = _position;
int len = isLongOrDouble();
+ boolean f32 = peek()=='f';
+ if( f32 ) nextChar();
+ int n = Math.abs(len);
+ if( f32 )
+ return TypeFloat.constant(Float.parseFloat(new String(_input,old,n)));
if( len > 0 ) {
if( len > 1 && _input[old]=='0' )
throw error("Syntax error: integer values cannot start with '0'",this);
long i = Long.parseLong(new String(_input,old,len));
return TypeInteger.constant(i);
}
- return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len)));
+ return TypeFloat.constant(Double.parseDouble(new String(_input,old,n)));
}
private String parseNumberString() {
int old = _position;
int len = Math.abs(isLongOrDouble());
+ if( peek()=='f' ) {
+ nextChar();
+ len++;
+ }
return new String(_input,old,len);
}
@@ -2056,7 +2718,7 @@ public class Parser {
private String parseId() {
int start = _position;
while (isIdLetter(nextChar())) ;
- return new String(_input, start, --_position - start);
+ return new String(_input, start, --_position - start).intern();
}
//
@@ -2085,9 +2747,9 @@ public class Parser {
}
}
- ParseException errorSyntax(String syntax) { return _errorSyntax("expected "+syntax); }
+ ParseException errorSyntax(String syntax) { return _errorSyntax("expected `"+syntax+"`"); }
private ParseException _errorSyntax(String msg) {
- return error("Syntax error, "+msg+": " + _lexer.getAnyNextToken());
+ return error("Syntax error, "+msg+" but found `" + _lexer.getAnyNextToken()+"`");
}
ParseException error(String msg) { return error(msg,_lexer); }
public static ParseException error(String msg, Lexer loc) { return new ParseException(msg,loc); }
diff --git a/src/main/java/com/seaofnodes/simple/Var.java b/src/main/java/com/seaofnodes/simple/Var.java
index cf549cd6..1906ce15 100644
--- a/src/main/java/com/seaofnodes/simple/Var.java
+++ b/src/main/java/com/seaofnodes/simple/Var.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
/**
@@ -15,6 +16,9 @@ public class Var {
private Type _type; // Declared type
public boolean _final; // Final field
public boolean _fref; // Forward ref
+ // Parser-only definite-initialization marker. The current binding's
+ // nullable type carries the path-sensitive state through SSA merges.
+ public boolean _uninit;
public Parser.Lexer _loc; // Source location
public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) {
@@ -28,12 +32,8 @@ public class Var {
_fref = fref;
_loc = loc;
}
- public Type type() {
- if( !_type.isFRef() ) return _type;
- // Update self to no longer use the forward ref type
- Type def = Parser.TYPES.get(((TypeMemPtr)_type)._obj._name);
- return (_type=_type.meet(def));
- }
+ public Type type() { return _type; }
+ public Type _type() { return _type; } // ScopeNode printer needs to have no side effects
// Forward reference variables (not types) must be BOTTOM and
// distinct from inferred variables
@@ -48,6 +48,6 @@ public class Var {
}
@Override public String toString() {
- return _type.toString()+(_final ? " ": " !")+_name;
+ return (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
index e9b5eb2e..911e69f6 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -14,9 +14,14 @@ abstract public class BuildLRG {
// since it will not color, just split the conflicted ranges now). Returns
// true if no hard-conflicts, although we still might not color.
public static boolean run(int round, RegAlloc alloc) {
- for( Node bb : alloc._code._cfg )
+ FunNode fun = null;
+ for( Node bb : alloc._code._cfg ) {
+ if( bb instanceof FunNode fun2 )
+ fun = fun2;
for( Node n : bb.outs() ) {
if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) {
+ if( phi instanceof MachNode mach && mach.outregmap()==null )
+ continue;
// All Phi inputs end up with the same LRG.
// Pass 1: find any pre-existing LRG, to avoid make-then-Union a LRG
LRG lrg = alloc.lrg(phi);
@@ -25,8 +30,8 @@ abstract public class BuildLRG {
if( (lrg = alloc.lrg(phi.in(i))) != null )
break;
// If none, make one.
- if( lrg==null ) lrg = alloc.newLRG(n);
- if( phi instanceof MachNode ) defLRG(alloc,n);
+ if( lrg==null ) lrg = alloc.newLRG(n,fun);
+ if( phi instanceof MachNode ) defLRG(alloc,n,fun);
// Pass 2: everybody uses the same LRG
lrg=alloc.union(lrg,phi);
for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
@@ -40,13 +45,13 @@ abstract public class BuildLRG {
int uidx = n.out(0)._inputs.find(n);
RegMask mask1 = n.in (1) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(1))._mask;
RegMask mask2 = n.in (2) instanceof MachNode machx ? machx.outregmap() : alloc.lrg(n.in(2))._mask;
- RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): alloc.lrg(n )._mask;
+ RegMask masko = n.out(0) instanceof MachNode machx ? machx.regmap(uidx): mach.outregmap();
if( !mask1.overlap(masko) && mask2.overlap(masko) )
n.swap12();
}
// Define live range
- defLRG(alloc,n);
+ defLRG(alloc,n,fun);
// Now, look in the opposite direction. How are incoming
// LRGs affected by this node: For all uses, make live lrgs
@@ -66,8 +71,9 @@ abstract public class BuildLRG {
if( n instanceof MultiNode && !(n instanceof CFGNode) )
for( Node proj : n.outs() )
if( proj instanceof MachNode )
- defLRG(alloc,proj);
+ defLRG(alloc,proj,fun);
}
+ }
// Collect live ranges
alloc.unify();
@@ -75,12 +81,12 @@ abstract public class BuildLRG {
return alloc.success();
}
- private static void defLRG( RegAlloc alloc, Node n ) {
+ private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) {
MachNode mach = (MachNode)n;
RegMask def_mask = mach.outregmap();
if( def_mask == null ) return;
LRG lrg = mach.twoAddress() == 0
- ? alloc.newLRG(n) // Define a new LRG for N
+ ? alloc.newLRG(n,fun) // Define a new LRG for N
: alloc.lrg2(n,mach.twoAddress()); // Use the matching 2-adr input
// Record mask and mach
if( !lrg.machDef(mach,def_mask.size1()).and(def_mask) )
diff --git a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index 3715d015..85d8143b 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -5,78 +5,276 @@ import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.print.*;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
+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.*;
+
+// Compile Driver for a Compilation Unit
@SuppressWarnings("unchecked")
public class CodeGen {
- public static final String PORTS = "com.seaofnodes.simple.node.cpus";
- // Last created CodeGen as a global; used all over to avoid passing about a
- // "context".
+
+ // Last created CodeGen as a global; used all over to avoid passing about a "context".
public static CodeGen CODE;
+ // Location of built-in CPU ports
+ public static final String PORTS = "com.seaofnodes.simple.node.cpus";
+
+ // Compile phases
public enum Phase {
Parse, // Parse ASCII text into Sea-of-Nodes IR
+ Iter, // Pessimistic peepholes after parse
Opto, // Run ideal optimizations
TypeCheck, // Last check for bad programs
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
LocalSched, // Local schedule
RegAlloc, // Register allocation
Encoding, // Encoding
Export, // Export
+ LastPhase // After last phase
}
public Phase _phase;
+
+ /** True when tests deliberately randomize Iter worklist order. */
+ public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; }
+
+ // ---------------------------
+ // Module Source Root
+ public final String _modDir;
+ // Module Build Root
+ public final String _buildDir;
+ // Search path from CWD for `.o` files containing external symbols and types.
+ // This can contain archives, zips and other .o file containers.
+ public final Ary<String> _externPaths;
+
+ // Dotted path from module root to file containing the source, sans ".smp".
+ // e.g. module root: "sys", nested source name "sys.io".
+ public final String _srcName;
+
+ // Only available for tests, otherwise source comes from the CompUnit
+ private final String _src;
+
+ // Current Working Directory; default module base
+ private final String _cwd;
+
+ // Compilation Units in this compile; one per source/object file
+ public HashMap<String,CompUnit> _compunits;
+ // Test shortcut for only one compilation unit
+ public CompUnit compunit() {
+ assert _compunits.size()==1;
+ for( CompUnit cu : _compunits.values() )
+ return cu;
+ throw Utils.TODO();
+ }
+
+ // True when fidx is published through a public field of a public class.
+ // TypeStruct.fidxs() already excludes private (underscore) fields.
+ public boolean publicFIDX(int fidx) {
+ // A public class initializer is itself an external root; unlike
+ // constructors and methods, it is not reached through a class field.
+ FunNode fun = _linker.atX(fidx);
+ if( fun != null && fun.isClz() && fun.isPublic() )
+ return true;
+ // Constructors and methods live on their class objects, including
+ // nested classes which do not have their own CompUnit.
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct clz &&
+ Parser.startsClzPrefix(clz._name) &&
+ Parser.isPublicClassName(clz._name) &&
+ resolvedClass(clz) &&
+ publicClassFIDX(clz,fidx) )
+ return true;
+ return false;
+ }
+
+ private static boolean publicClassFIDX(TypeStruct clz, int fidx) {
+ boolean hasCtor = clz.field("<ctor>") != null;
+ for( Field fld : clz._fields )
+ if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp &&
+ // With a user constructor, class:N.N is the private <init>.
+ !(hasCtor && clz.hiddenInit(fld)) &&
+ XInt.bit(tfp._fidxs,fidx) )
+ return true;
+ return false;
+ }
+
+ public static boolean hasUserConstructor(TypeStruct self) {
+ Type t = Parser.TYPES.get(Parser.addClzPrefix(self._name));
+ return t instanceof TypeStruct clz && clz.field("<ctor>") != null;
+ }
+
+ // A forward reference gets a class object and constructor signature so
+ // parsing can proceed, but it has no callable definition yet.
+ private static boolean resolvedClass(TypeStruct clz) {
+ Type t = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
+ return !(t instanceof TypeStruct ts) || !ts._fref;
+ }
+
+ private int[] _publicFIDXs, _publicAliases;
+
+ /**
+ * Freeze the externally visible portion of the program after ParseAll has
+ * loaded every compilation unit and closed/upgraded all types. Later
+ * optimization can remove linker entries and create inline-only fidxs;
+ * neither operation changes this interface.
+ */
+ void freezePublicInterface() {
+ assert _publicFIDXs==null && _publicAliases==null;
+ int[] fidxs = XInt.EMPTY;
+ for( int fidx=1; fidx<_linker._len; fidx++ )
+ if( publicFIDX(fidx) )
+ fidxs = XInt.make(fidxs,fidx);
+ int[] aliases = XInt.EMPTY;
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct clz &&
+ Parser.startsClzPrefix(clz._name) &&
+ Parser.isPublicClassName(clz._name) &&
+ resolvedClass(clz) ) {
+ boolean hasCtor = clz.field("<ctor>") != null;
+ for( Field fld : clz._fields )
+ if( fld._fname.charAt(0)!='_' && fld._t!=Type.TOP &&
+ !(hasCtor && clz.hiddenInit(fld)) )
+ aliases = XInt.make(aliases,fld._alias);
+ }
+ _publicFIDXs = fidxs;
+ _publicAliases = aliases;
+ }
+
+ public int[] publicFIDXs() { assert _publicFIDXs !=null; return _publicFIDXs; }
+ public int[] publicAliases(){ assert _publicAliases!=null; return _publicAliases; }
+ public boolean publicInterfaceFrozen() { return _publicFIDXs != null; }
+
// ---------------------------
- // Compilation source code
- public final String _src;
- // Compile-time known initial argument type
- public final TypeInteger _arg;
+
+ // Very common nodes, cached here
+ public final ConstantNode ZERO;
+ public final XCtrlNode XCTRL;
+
+ public Node constant( Type t ) {
+ assert _phase==null || _phase.ordinal() <= Phase.Opto.ordinal();
+ return ConstantNode.make(t).peephole();
+ }
+ public ConstantNode con( Type t ) { return (ConstantNode)constant(t); }
+ public ConstantNode con( long con ) { return con==0 ? ZERO : con(TypeInteger.constant(con)); }
+
// ---------------------------
- public CodeGen( String src ) { this(src, TypeInteger.BOT, 123L, true ); }
- public CodeGen( String src, TypeInteger arg) { this(src, arg==null ? TypeInteger.BOT : arg, 123L, true ); }
+ // Test setup; no module nor file with specific argument
+ public CodeGen( String src ) { this(src, 126L ); }
+
+ // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
+ public CodeGen( String src, Type arg ) {
+ this(null,null,null,null, src, 126L, arg);
+ }
+ // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
+ public CodeGen( String src, long workListSeed ) {
+ this(null,null,null,null, src, workListSeed, TypeInteger.BOT);
+ }
- public CodeGen( String src, TypeInteger arg, long workListSeed, boolean reset ) {
+ // Generic CodeGen, including full module setup
+ public CodeGen( String modDir, String buildDir, Ary<String> externPaths,
+ String srcName, String src, long workListSeed, Type arg ) {
+ // Public singleton to avoid passing about this state to a huge count
+ // of places. Probably becomes a TLS at some point.
CODE = this;
- if( reset ) Type.reset();
- _main = makeFun(TypeFunPtr.MAIN);
- _phase = null;
- _callingConv = null;
- _start = new StartNode(arg);
- _stop = new StopNode(src);
+ Type.reset(); // Reset to recover from failed test
+
+ _cwd = System.getProperty("user.dir")+"/";
+ _modDir = modDir == null ? _cwd : modDir;
+ _buildDir = buildDir == null ? _cwd : buildDir;
+ _externPaths = externPaths;
+ _srcName = srcName;
+ _callingConv = null; // Calling convention
+ // Source code from test strings, not files
_src = src;
- _arg = arg;
- _iter = new IterPeeps(workListSeed);
- P = new Parser(this,arg);
+ // All the compilation units
+ _compunits = new HashMap<>();
+ _phase = null;
+ // Start GVN table
+ _gvn = new HashMap<>();
+ // Allow whole-suite worklist-order sweeps without rewriting the many
+ // explicit seeds used by tests and command-line compilation.
+ String iterSeed = System.getProperty("simple.iter.seed");
+ _iter = new IterPeeps(iterSeed == null ? workListSeed : Long.parseLong(iterSeed));
+ // End points of graph
+ _stop = new StopNode().init();
+ _start = new StartNode(null,_stop,arg).init();
+ ZERO = con(TypeInteger.ZERO).keep();
+ XCTRL = new XCtrlNode().peephole().keep();
+ P = new Parser(this);
}
- // All passes up to Phase, except ELF
- public CodeGen driver( Phase phase ) { return driver(phase,null,null); }
+ // Run requested phases.
+
+ // No code emission, just IR generation
+ public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); }
+ // No object file writing, but code generation for a specific cpu/os pair (allows emulation)
public CodeGen driver( Phase phase, String cpu, String callingConv ) {
- if( _phase==null ) parse();
- int p1 = phase.ordinal();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Opto .ordinal() ) opto();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.TypeCheck .ordinal() ) typeCheck();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.LoopTree .ordinal() ) loopTree();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Select .ordinal() && cpu != null ) instSelect(cpu,callingConv);
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Schedule .ordinal() ) GCM();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.LocalSched.ordinal() ) localSched();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.RegAlloc .ordinal() ) regAlloc();
- if( _phase.ordinal() < p1 && _phase.ordinal() < Phase.Encoding .ordinal() ) encode();
+ if( phase.ordinal() < Phase.Export.ordinal() )
+ // No code outputted
+ return driver(phase,cpu,callingConv,false,false,0);
+ if( _srcName == null )
+ throw new RuntimeException("No source filename provided, so do not know how to name the obj file");
+ boolean emitEntrySymbol = !_srcName.contains(".");
+ return driver( phase, cpu, callingConv, false, emitEntrySymbol, 0 );
+ }
+ // Write an object file for a specific cpu/os pair
+
+ public CodeGen driver( String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol ) { return driver(Phase.Export,cpu,callingConv,inMemory,emitEntrySymbol,0); }
+ // Generic driver
+ public CodeGen driver( Phase phase, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
+ int p1 = phase.ordinal(), p2 = _phase==null ? -1 : _phase.ordinal();
+ if( p2 < p1 && p2 < Phase.Parse .ordinal() ) { parse(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Iter .ordinal() ) { iter(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Opto .ordinal() ) { opto(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.TypeCheck .ordinal() ) { typeCheck(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.LoopTree .ordinal() ) { loopTree(); p2 = dump(dump); }
+ if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { unlinkImports(); p2 = dump(dump); }
+ if( p2 < p1 && p1 >= Phase.Encoding .ordinal() ) { serialize(); p2 = dump(dump); } // Include ideal graph in object file
+ if( p2 < p1 && p2 < Phase.Unlink .ordinal() ) { unlink(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Select .ordinal() && cpu != null ) { instSelect(cpu,callingConv); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Schedule .ordinal() ) { GCM(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.LocalSched.ordinal() ) { localSched();p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.RegAlloc .ordinal() ) { regAlloc(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Encoding .ordinal() ) { encode(); p2 = dump(dump); }
+ if( p2 < p1 && p2 < Phase.Export .ordinal() ) { exportELF(inMemory,emitEntrySymbol); p2 = dump(dump); }
return this;
}
- // Run all the phases through final ELF emission
- public CodeGen driver( String cpu, String callingConv, String obj ) throws IOException {
- return driver(Phase.Encoding,cpu,callingConv).exportELF(obj);
+ public String entryClinitName() {
+ return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName)+".<clinit>";
}
+ // Verbose printing during compilation
+ private int dump(int dump) {
+ int p2 = _phase.ordinal();
+ if( (dump & (1<<p2)) == 0 ) return p2;
+ if( (dump & (1<<29)) != 0 ) {
+ String fn = ""+p2+"-"+_phase+".dot";
+ try {
+ Files.writeString(Path.of(fn),
+ new GraphVisualizer().generateDotOutput(compunit(), null, null));
+ } catch(IOException e) { throw Utils.TODO("Cannot write DOT file"); }
+ return p2;
+ }
+
+ if( (dump & (1<<30)) != 0 )
+ System.err.println("After "+_phase+":");
+ System.err.println(IRPrinter.prettyPrint(this));
+ return p2;
+ }
+
+
+ // Record times by phase
+ public final long[] _times = new long[Phase.LastPhase.ordinal()];
// ---------------------------
/**
@@ -89,9 +287,39 @@ public class CodeGen {
return _uid++;
}
- // Next available memory alias number
- private int _alias = 2; // 0 is for control, 1 for memory
- public int getALIAS() { return _alias++; }
+
+ // These next fields all 2-way map some *global* program feature to a
+ // local dense index, suitable for packing in BitSets. The mapping takes a
+ // global value - always a {source file/class} name, and an order number
+ // counting up per feature in the same file. The dense local index can be
+ // different in different compilations.
+
+ // The global info allows loading the same remote class info from different
+ // paths and aligning them. Example: Compiling A loads pre-compiled B and
+ // C, both of which load pre-compiled D. The 2 different D loads unify via
+ // this global info.
+
+ // These mappings are all trivial identities when compiling one file; they
+ // only become complex when loading separately compiled code.
+
+ // Compute local function index (FIDX) from global function info. This is
+ // called *in order* during parsing, and that order is part of the global
+ // unique mapping
+ public final GlobalBits _aliases = new GlobalBits();
+ public int alias(String clz) { return _aliases.next(clz); }
+
+ // Compute local function index (FIDX) from global function info. This is
+ // called *in order* during parsing, and that order is part of the global
+ // unique mapping
+ public final GlobalBits _fidxs = new GlobalBits();
+ // Return local index for specific global file & order. Used to find fidxs for e.g. FREF class <init> fcns
+ public int fidx( String clz, int order ) { return _fidxs.next(clz, order); }
+ // New fidx in the current file
+ public int fidx( String clz ) { return fidx(clz, -1); }
+
+ // Compute local RPC index from global RPC info, one per call
+ public final GlobalBits _rpcs = new GlobalBits();
+ public int rpc( String clz ) { return _rpcs.next(clz); }
// idepths are cached and valid until *inserting* CFG edges (deleting is
@@ -116,30 +344,21 @@ public class CodeGen {
public final BitSet _visit = new BitSet();
public BitSet visit() { assert _visit.isEmpty(); return _visit; }
- // Start and stop; end points of the generated IR
+ // Start and Stop; end points of the generated IR
public StartNode _start;
public StopNode _stop;
// Global Value Numbering. Hash over opcode and inputs; hits in this table
// are structurally equal.
- public final HashMap<Node,Node> _gvn = new HashMap<>();
+ public final HashMap<Node,Node> _gvn;
- // Source of unique function indices
- private int _fidx =0;
- public TypeFunPtr makeFun( TypeFunPtr fun ) {
- int fidx = _fidx++;
- assert fidx<64; // TODO: need a larger FIDX space
- return fun.makeFrom(fidx);
- }
- // Signature for MAIN
- public TypeFunPtr _main;
- public void setMain(FunNode main) {
- _main = main.sig();
- link(main);
- }
// Reverse from a constant function pointer to the IR function being called.
// Error to call with a non-constant TFP
public FunNode link( TypeFunPtr tfp ) { return link(tfp.fidx()); }
+ // Read-only linker lookup, suitable for printers and debugger display.
+ // In particular, do not lazily remove a partially dead function.
+ public FunNode lookupFun(int fidx) { return _linker.atX(fidx); }
+ public FunNode lookupFun(TypeFunPtr tfp) { return lookupFun(tfp.fidx()); }
// Return the FunNode from a fidx
public FunNode link( int fidx ) {
FunNode fun =_linker.atX(fidx);
@@ -158,20 +377,48 @@ public class CodeGen {
// "Linker" mapping from constant TypeFunPtrs to heads of function. These
// TFPs all have exact single fidxs and their return is wiped to BOTTOM (so
// the return is not part of the match).
- final Ary<FunNode> _linker = new Ary<>(FunNode.class);
+ public final Ary<FunNode> _linker = new Ary<>(FunNode.class);
+
+ // Extern function declarations; input is the FIDX assigned to the name.
+ final HashMap<Integer,String> _externFunc = new HashMap<>();
+ public void externFunc(int fidx, String ex) {
+ assert !_externFunc.containsKey(fidx);
+ _externFunc.put(fidx,ex);
+ }
+ public String externFunc(int fidx) { return _externFunc.get(fidx); }
+ public String funcName(int fidx ) {
+ FunNode fun = link(fidx);
+ if( fun!=null ) return fun._name;
+ return externFunc(fidx);
+ }
+
+ public boolean owns(FunNode fun) {
+ return fun._compunit != null && fun._compunit._src != null;
+ }
+
+
+ // ---------------------------
// Parser object
public final Parser P;
// Parse ASCII text into Sea-of-Nodes IR
- public int _tParse;
public CodeGen parse() {
assert _phase == null;
_phase = Phase.Parse;
long t0 = System.currentTimeMillis();
- P.parse();
- _tParse = (int)(System.currentTimeMillis() - t0);
+ 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;
JSViewer.show();
return this;
}
@@ -188,24 +435,64 @@ public class CodeGen {
public void iterCnt() { if( !_midAssert ) _iter_cnt++; }
public void iterNop() { if( !_midAssert ) _iter_nop_cnt++; }
+ // 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: non-singleton
+ // memory must restart at FULL so newly discovered private escapes can
+ // only be removed, never added. Seed the complete graph once.
+ _start.walk(n -> {
+ n._type = iterEscapeReset(n._type);
+ add(n);
+ return null;
+ });
+ _iter.iterate(this);
+ assert Opto.fixedPointCheck(this);
+
+ _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0;
+ 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;
+ }
+
+
// Run ideal optimizations
- public int _tOpto;
public CodeGen opto() {
- assert _phase == Phase.Parse;
+ if( _phase == Phase.Parse )
+ iter();
+ assert _phase == Phase.Iter;
_phase = Phase.Opto;
long t0 = System.currentTimeMillis();
Opto.opto(this);
- _tOpto = (int)(System.currentTimeMillis() - t0);
+ _times[Phase.Opto.ordinal()] = System.currentTimeMillis() - t0;
return this;
}
public <N extends Node> N add( N n ) { return _iter.add(n); }
public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
+ public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); }
// ---------------------------
// Last check for bad programs
- public int _tTypeCheck;
public CodeGen typeCheck() {
// Demand phase Opto for cleaning up dead control flow at least,
// required for the following GCM.
@@ -213,33 +500,123 @@ public class CodeGen {
_phase = Phase.TypeCheck;
long t0 = System.currentTimeMillis();
- Parser.ParseException err = _stop.walk( Node::err );
- _tTypeCheck = (int)(System.currentTimeMillis() - t0);
- if( err != null )
- throw err;
+ final Ary<Node> errs = new Ary<>(Node.class);
+ _stop.walk( n -> {
+ if( n.err() != null )
+ errs.add(n);
+ return null;
+ });
+ String unresolved = unresolvedTypeName();
+ _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;
+ throw min.err();
+ }
+ if( unresolved != null )
+ throw Parser.error("Unknown struct type '"+unresolved+"'",null);
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;
+ }
+
+ // Forward-ref structs are open while parsing. They must all be resolved
+ // to closed structs before leaving type checking.
+ private String unresolvedTypeName() {
+ String[] unresolved = new String[1];
+ _stop.walk(n -> {
+ TypeStruct ts = requiredStruct(n);
+ if( unresolved[0]==null && ts!=null && ts._fref )
+ unresolved[0] = ts._name;
+ return null;
+ });
+ return unresolved[0];
+ }
+
+ public static TypeStruct requiredStruct(Node n) {
+ if( n instanceof NewNode alloc ) return alloc._ts;
+ if( n instanceof ConFldOffNode off ) return off._ts;
+ if( n instanceof FldOffNode off && off.in(0)._type instanceof TypeMemPtr ptr ) return ptr._obj;
+ return null;
+ }
+
// ---------------------------
// Build the loop tree; break never-exit loops
- public int _tLoopTree;
public CodeGen loopTree() {
- assert _phase.ordinal() <= Phase.TypeCheck.ordinal();
+ assert _phase.ordinal() <= Phase.Serialize.ordinal();
_phase = Phase.LoopTree;
long t0 = System.currentTimeMillis();
// Build the loop tree, fix never-exit loops
- _start.buildLoopTree(_start,_stop);
- _tLoopTree = (int)(System.currentTimeMillis() - t0);
+ _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;
}
// ---------------------------
+ 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();
+ 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--);
+ }
+ //_iter.iterate(this);
+ }
+
+ // ---------------------------
// Code generation CPU target
public Machine _mach;
// Chosen calling convention (usually either Win64 or SystemV)
public String _callingConv;
+ public String _cCallingConv;
// Callee save registers
public RegMask _callerSave;
+ public RegMask _cCallerSave;
// All returns have the following inputs:
// 0 - ctrl
@@ -252,10 +629,9 @@ public class CodeGen {
public RegMask _rpcMask;
// Convert to target hardware nodes
- public int _tInsSel;
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.LoopTree.ordinal();
+ assert _phase.ordinal() <= Phase.Unlink.ordinal();
_phase = Phase.Select;
_callingConv = callingConv;
@@ -267,12 +643,17 @@ public class CodeGen {
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
@@ -292,12 +673,20 @@ public class CodeGen {
_uid = 1; // All new machine nodes reset numbering
var map = new IdentityHashMap<Node,Node>();
_instSelect( _stop, map );
- _stop = ( StopNode)map.get(_stop );
+ _stop = (StopNode)map.get(_stop );
StartNode start = (StartNode)map.get(_start);
_start = start==null ? new StartNode(_start) : start;
_instOuts(_stop,visit());
_visit.clear();
- _tInsSel = (int)(System.currentTimeMillis() - t0);
+
+ // 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;
}
@@ -319,11 +708,20 @@ public class CodeGen {
// Produce a machine node from n; map it to flag as done so stops cycles.
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) );
- if( x instanceof MachNode mach )
+ // 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;
}
@@ -335,14 +733,90 @@ public class CodeGen {
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);
}
}
+ // ---------------------------
+ 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() || fun._type.isHigh() )
+ 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.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() );
+ }
+ }
+
+ _times[Phase.Unlink.ordinal()] = System.currentTimeMillis() - t0;
+ }
// ---------------------------
// Control Flow Graph in Reverse Post Order.
- public int _tGCM;
public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
// Global schedule (code motion) nodes
@@ -353,28 +827,26 @@ public class CodeGen {
long t0 = System.currentTimeMillis();
GlobalCodeMotion.buildCFG(this);
- _tGCM = (int)(System.currentTimeMillis() - t0);
+ _times[Phase.Schedule.ordinal()] = System.currentTimeMillis() - t0;
if( show )
- System.out.println(new GraphVisualizer().generateDotOutput(_stop,null,null));
+ System.out.println(new GraphVisualizer().generateDotOutput(compunit(),null,null));
return this;
}
// ---------------------------
// Local (basic block) scheduler phase, a classic list scheduler
- public int _tLocal;
public CodeGen localSched() {
assert _phase == Phase.Schedule;
_phase = Phase.LocalSched;
long t0 = System.currentTimeMillis();
ListScheduler.sched(this);
- _tLocal = (int)(System.currentTimeMillis() - t0);
+ _times[Phase.LocalSched.ordinal()] = System.currentTimeMillis() - t0;
return this;
}
// ---------------------------
// Register Allocation
- public int _tRegAlloc;
public RegAlloc _regAlloc;
public CodeGen regAlloc() {
assert _phase == Phase.LocalSched;
@@ -382,15 +854,14 @@ public class CodeGen {
long t0 = System.currentTimeMillis();
_regAlloc = new RegAlloc(this);
_regAlloc.regAlloc();
- _tRegAlloc = (int)(System.currentTimeMillis() - t0);
+ _times[Phase.RegAlloc.ordinal()] = System.currentTimeMillis() - t0;
return this;
}
- // Human readable register name
- public String reg(Node n) { return reg(n,null); }
- public String reg(Node n, FunNode fun) {
+ // Human-readable register name
+ public String reg(Node n) {
if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) {
- String s = _regAlloc.reg(n,fun);
+ String s = _regAlloc.reg(n);
if( s!=null ) return s;
}
return "N"+ n._nid;
@@ -399,50 +870,171 @@ public class CodeGen {
// ---------------------------
// Encoding
- public int _tEncode;
- public Encoding _encoding; // Encoding object
- public void preEncode() { } // overridden by alternative ports
+ public Encoding _encoding;
public CodeGen encode() {
assert _phase == Phase.RegAlloc;
_phase = Phase.Encoding;
long t0 = System.currentTimeMillis();
- _encoding = new Encoding(this);
- preEncode();
- _encoding.encode();
- _tEncode = (int)(System.currentTimeMillis() - t0);
+
+ _encoding = new Encoding(this).encode();
+
+ _times[Phase.Encoding.ordinal()] = System.currentTimeMillis() - t0;
return this;
}
// ---------------------------
// Exporting to external formats
- public CodeGen exportELF(String fname) throws IOException {
+ ElfWriter _elf;
+ public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) {
assert _phase == Phase.Encoding;
_phase = Phase.Export;
- if( fname == null ) new LinkMem(this).link(); // In memory patching
- else new ElfFile(this).export(fname); // External ELF file
+ 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;
}
+
+ // ---------------------------
+
+ // Search a external path list, each path is recursively searched for .o
+ // files, which are partially loaded and searched for public symbols.
+
+ // This is a state machine which lazily searches down the set of search
+ // paths until the requested module is class is found.
+
+ // Map from external Strings to either partially read Simple ElfFile or ExternNode
+ public final HashMap<String,Object> _externSymbols = new HashMap<>();
+
+ // State machine elements; the outermost element is _externPaths
+ private int _extPathIdx; // Search index into the extern path list
+ private final Ary<File> _files = new Ary<>(File.class); // Files in the current extern path being searched
+ private int _extFileIdx; // Index into _files
+
+ public ElfReader findExternalSimple( String name ) {
+ return findExternalSymbol(name) instanceof ElfReader elf ? elf : null;
+ }
+
+ public ExternNode findExternal( String name ) {
+ return findExternalSymbol(name) instanceof ExternNode extern ? extern : null;
+ }
+
+ // Search the search-path for the name; return an ElfReader if found in a
+ // Simple-made ELF; return an ExternNode if from another ELF, or null if
+ // not found.
+ private Object findExternalSymbol( String name ) {
+ // State Machine!
+
+ while( true ) {
+ // Check for an immediate hit
+ switch( _externSymbols.get(name) ) {
+
+ case ExternNode extern:
+ // Name maps to an ExternNode
+ return extern;
+
+ case ElfReader elf:
+ return elf;
+
+ case null:
+ // Name is unknown. Advance the file-system search, pulling
+ // out new ELF files from the search directory list and loading
+ // their published symbols.
+
+ // The last directory was mined out, so get the next search
+ // directory and find all ELF-like files.
+ if( _extFileIdx >= _files._len ) {
+ if( _externPaths==null || _extPathIdx >= _externPaths._len )
+ return null; // A true miss in the whole search path
+ loadExtPath(); // Load next batch file of files
+ break;
+ }
+ // Load and parse an ELF header
+ ElfReader elf = getElf(_files.at(_extFileIdx++));
+ if( elf == null ) break; // Not an ELF
+ // Load public symbols from the ELF
+ elf.loadPublicSymbols();
+ if( elf._strs==null ) break; // No .simple section with strings
+ // Put public symbol in global table; first names shadow all others
+ // Map from symbol string to ELFReader; most lookups
+ // will miss and no need to unpack ElfReader types
+ _externSymbols.putIfAbsent(elf._strs[1],elf);
+ for( String str : elf._strs )
+ if( str.startsWith(Parser.CLZ) )
+ _externSymbols.putIfAbsent(str,elf);
+ break;
+
+ default: throw Utils.TODO("should not reach here");
+ }
+ }
+ }
+
+ // Load all the .o files in the next external path.
+ private void loadExtPath() {
+ _files.clear();
+ _extFileIdx = 0;
+ String extpath = _externPaths.at(_extPathIdx++);
+ File f = new File(extpath);
+ if( !f.isAbsolute() )
+ f = new File(_cwd+extpath);
+ fillExtFiles(f);
+ }
+
+ // Recursive search (TODO: gzip, archives) and gather all .o files.
+ private void fillExtFiles(File dir) {
+ if( dir.isDirectory() )
+ for( File f : dir.listFiles() )
+ fillExtFiles(f);
+ else if( dir.getName().endsWith(".o") )
+ _files.add(dir);
+ }
+
+ // Map from object file names to ElfReaders
+ public final HashMap<String,ElfReader> _elfs = new HashMap<>();
+ ElfReader getElf(File f) {
+ ElfReader elf = _elfs.get(f.getPath());
+ if( elf != null )
+ return elf;
+ _elfs.put(f.getPath(), elf = ElfReader.load(f,null));
+ return elf;
+ }
+
// ---------------------------
public boolean _asmLittle=true;
- SB asm(SB sb) { return ASMPrinter.print(sb,this); }
public String asm() { return asm(new SB()).toString(); }
+ SB asm(SB sb) {
+ ASMPrinter.print(sb,this);
+ return sb;
+ }
// Testing shortcuts
- public Node ctrl() { return _stop.ret().ctrl(); }
- public Node expr() { return _stop.ret().expr(); }
- public String print() { return _stop.print(); }
+ public Node ctrl() { return compunit()._stop.ret().ctrl(); }
+ public Node expr() { return compunit()._stop.ret().expr(); }
+ public String print() { return compunit()._stop.print(); }
// Debugging helper
@Override public String toString() {
- return _phase.ordinal() > Phase.Schedule.ordinal()
- ? IRPrinter._prettyPrint( this )
- : _stop.p(9999);
+ if( _phase!=null && _phase.ordinal() > Phase.Schedule.ordinal() )
+ return IRPrinter.prettyPrint( this );
+ if( _stop == null )
+ return "No StopNode";
+ return _stop.p(4999);
}
// Debugging helper
- public Node f(int idx) { return _stop.find(idx); }
+ public Node f(int idx) {
+ for( CompUnit cu : _compunits.values() ) {
+ Node n = (cu._stop == null ? _stop : cu._stop).find(idx);
+ if( n != null ) return n;
+ }
+ return null;
+ }
String printCFG() {
@@ -461,28 +1053,4 @@ public class CodeGen {
}
System.out.println();
}
-
- public void print_as_hex() {
- for (byte b : _encoding._bits.toByteArray()) {
- System.out.print(String.format("%02X", b));
- }
- System.out.println();
- }
-
- //// Debug purposes for now
-// public CodeGen printENCODING() {
-// ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-//
-// for(Node bb : CodeGen.CODE._cfg) {
-// for(Node n: bb.outs()) {
-// if(n instanceof MachNode) {
-// ((MachNode) n).encoding(E);
-// }
-// }
-// }
-//
-// print_as_hex(outputStream);
-// // Get the raw bytes from the output stream
-// return this;
-// }
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
new file mode 100644
index 00000000..2681066e
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -0,0 +1,138 @@
+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.type.TypeStruct;
+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.*;
+import static com.seaofnodes.simple.codegen.CodeGen.CODE;
+
+// A compilation-unit, which is an external reference and source and object
+// files. These are mirrored over a file system tree (so form a tree), both
+// the source tree and build/object tree.
+public class CompUnit {
+ public final CompUnit _par; // Parent, e.g. A/B
+ final String _fname; // Full slashed file name starting from module root, e.g. A/B/C
+ final public String _cname; // Full dotted class name starting from module root, e.g. A.B.C
+ final String _name; // Base name, e.g. C
+ final ExternNode _ext; // Found in external libs; e.g. libc
+ final File _smp; // Simple source file, e.g. module_root/A/B/C.smp
+ public final File _obj; // ELF output file, e.g. build/A/B/C.o
+ final public String _src; // Source code, from file or test case
+ //public Encoding _encoding; // Encoding for output
+
+ public TypeStruct _clz; // The one TypeStruct being published
+ //BAOS _serial; // Serialized IR for this ELF file
+ Ary<CompUnit> _deps; // CompUnits that this CompUnit depends on
+
+ //// List of symbols exported by this compilation unit, and their Node
+ //// definitions.
+ //public HashMap<String,Node> _exported;
+ // Per-Compilation-Unit Start/StopNodes, keeping alive all exported Nodes.
+ // Null means no code loaded (yet).
+ public StartCUNode _start;
+ public StopCUNode _stop ;
+
+
+ // Discovered source file. Source is loaded if required to parse, and null
+ // otherwise.
+ CompUnit( CompUnit par, String fname, String cname, String name ) throws IOException {
+ _par = par;
+ _fname= fname;
+ _cname= cname;
+ _name = name;
+ _ext = null;
+ _smp = new File( CODE. _modDir + "/" + fname + ".smp");
+ _obj = new File( CODE._buildDir + "/" + fname + ".o" );
+
+ boolean mustCompile = _smp.exists() && // Has source to compile
+ ( !_obj.exists() || // No object file
+ _smp.lastModified() > _obj.lastModified() || // Out-of-date with source?
+ checkDependentObjs() ); // Out-of-date with other objs?
+ _src = mustCompile ? new String(Files.readAllBytes(_smp.toPath())) : null;
+ }
+
+ // Test case. No source file. Source code passed in as a String. Fake
+ // class name.
+ CompUnit( String fname, String src ) {
+ _par = null;
+ _name = fname;
+ _fname= fname;
+ _cname= fname;
+ _ext = null;
+ _smp = null;
+ _obj = null;
+ _src = src;
+ }
+
+ // External CompUnit, pulled in from an external library like libc
+ CompUnit( ExternNode ext, String symbol ) {
+ _par = null;
+ _name = symbol;
+ _fname= _name;
+ _cname= null;
+ _ext = ext;
+ _smp = null;
+ _obj = null;
+ _src = null;
+ }
+
+ // External Simple object file, or namespace parent, found on an external search path.
+ CompUnit( File obj, CompUnit par, String fname, String cname, String name ) {
+ _par = par;
+ _name = name;
+ _fname= fname;
+ _cname= cname;
+ _ext = null;
+ _smp = null;
+ _obj = obj;
+ _src = null;
+ }
+
+ // Return true if _obj is out-of-date relative to dependents.
+ private boolean checkDependentObjs() {
+ ElfReader elf = CODE.getElf(_obj);
+ elf.loadPublicSymbols();
+ for( String dep : elf._deps ) {
+ File obj = new File(CODE._buildDir+"/"+dep+".o");
+ if( obj.lastModified() > _obj.lastModified() ) // Out-of-date with deps?
+ return true;
+ }
+ return false;
+ }
+
+ // Add a discovered dependency, for later writing out in an ELF file, ".simple" section
+ void addDep(CompUnit dep) {
+ if( _deps == null ) _deps = new Ary<>(CompUnit.class);
+ _deps.add(dep);
+ }
+
+ public void addFun(CodeGen code, FunNode fun) {
+ assert fun.in(1)==_start;
+ _stop.addDef(fun.ret());
+ code.add(_stop);
+ _stop.addDep(fun);
+ }
+
+ void setStart(CodeGen code) {
+ _stop = new StopCUNode().init();
+ _start = new StartCUNode(code._start,_stop, Type.BOTTOM, _fname).init();
+ code._stop.addDef(_stop);
+ }
+
+
+ @Override public String toString() {
+ String x = "{"+_cname;
+ if( _smp!=null && _smp.exists()) x += ",hasSMP";
+ if( _obj!=null && _obj.exists()) x += ",hasObj";
+ if( _src!=null ) x += ",hasSrc";
+ if( _ext != null ) x += ",isExt";
+ x += "}";
+ return x;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
deleted file mode 100644
index 8f9e2981..00000000
--- a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
+++ /dev/null
@@ -1,381 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.Encoding.BAOS;
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Ary;
-import java.io.*;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.HashMap;
-import java.util.Map;
-
-public class ElfFile {
-
- final CodeGen _code;
- Ary<Section> _sections = new Ary<>(Section.class);
-
- ElfFile( CodeGen code ) { _code = code; }
-
- public static int writeCString(DataSection strtab, String name) {
- // place name in the string table
- int pos = strtab._contents.size();
- byte[] name_bytes = name.getBytes();
- strtab._contents.write(name_bytes, 0, name_bytes.length);
- // the strings are null terminated
- strtab._contents.write(0);
- return pos;
- }
-
- public static void write4( ByteArrayOutputStream bits, int op ) {
- bits.write(op );
- bits.write(op>> 8);
- bits.write(op>>16);
- bits.write(op>>24);
- }
- public static void write8( ByteArrayOutputStream bits, long i64 ) {
- write4(bits, (int) i64 );
- write4(bits, (int)(i64>>32));
- }
-
- public static final int SYMBOL_SIZE = 4+1+1+2+8+8;
-
- public static final int SYM_BIND_LOCAL = 0;
- public static final int SYM_BIND_GLOBAL = 1;
- public static final int SYM_BIND_WEAK = 2;
-
- public static final int SYM_TYPE_NOTYPE = 0;
- public static final int SYM_TYPE_OBJECT = 1;
- public static final int SYM_TYPE_FUNC = 2;
- public static final int SYM_TYPE_SECTION = 3;
-
- public static class Symbol {
- String _name;
- int _name_pos;
-
- // index in the symbol table
- int _index;
-
- // which section is it in
- int _parent;
-
- // depends on the symbol type
- // TODO
- int _value, _size;
-
- // top 4bits are the "bind", bottom 4 are "type"
- int _info;
-
- // bind 1011
- // 00001111
- // 10110000
-
- Symbol(String name, int parent, int bind, int type) {
- _name = name;
- _parent = parent;
- _info = (bind << 4) | (type & 0xF);
- }
-
- void writeHeader(ByteBuffer out) {
- out.putInt(_name_pos); // name
- out.put((byte) _info); // info
- // default visibility
- out.put((byte) 0); // other
- out.putShort((short) _parent); // shndx
- out.putLong(_value); // value
- out.putLong(_size); // size
- }
- }
-
- public static final int SHF_WRITE = 1;
- public static final int SHF_ALLOC = 2;
- public static final int SHF_EXECINSTR = 4;
- public static final int SHF_STRINGS = 32;
- public static final int SHF_INFO_LINK = 64;
-
- public static final int SECTION_HDR_SIZE = 64;
- abstract public static class Section {
- String _name;
- int _index;
-
- int _type, _flags;
- int _link, _info;
-
- // where's the name in the string table
- int _name_pos;
- int _file_offset;
-
- Section(String name, int type) {
- _name = name;
- _type = type;
- }
-
- void writeHeader(ByteBuffer out) {
- out.putInt(_name_pos); // name
- out.putInt(_type); // type
- out.putLong(_flags); // flags
- out.putLong(0); // addr
- out.putLong(_file_offset); // offset
- out.putLong(size()); // size
- out.putInt(_link); // link
- out.putInt(_info); // info
- out.putLong(16); // addralign
- if (_type == 2) {
- out.putLong(SYMBOL_SIZE);// entsize
- } else if (_type == 4) {
- out.putLong(24); // entsize
- } else {
- out.putLong(0); // entsize
- }
- }
-
- abstract void write(ByteBuffer out);
- abstract int size();
- }
-
- public static class SymbolSection extends Section {
- Ary<Symbol> _symbols = new Ary<>(Symbol.class);
- Ary<Symbol> _loc = new Ary<>(Symbol.class);
-
- SymbolSection(String name, int type) {
- super(name, type);
- }
-
- @Override
- void writeHeader(ByteBuffer out) {
- // points to string table section
- _link = 1;
-
- // number of non-local symbols
- _info = _loc.len() + 1;
-
- super.writeHeader(out);
- }
-
- void push(Symbol s) {
- if ((s._info >> 4) != SYM_BIND_LOCAL) {
- _symbols.push(s);
- } else {
- _loc.push(s);
- }
- }
-
- @Override
- void write(ByteBuffer out) {
- // Index 0 both designates the first entry in the table and serves as the undefined symbol index
- for( int i = 0; i < SYMBOL_SIZE/4; i++ ) {
- out.putInt(0);
- }
- // index is already set
- for( Symbol s : _loc ) {
- s.writeHeader(out);
- }
- for( Symbol s : _symbols ) {
- s.writeHeader(out);
- }
- }
-
- @Override
- int size() {
- return (1 + _symbols.len() + _loc.len()) * SYMBOL_SIZE;
- }
- }
-
- public static class DataSection extends Section {
- ByteArrayOutputStream _contents;
-
- DataSection(String name, int type) {
- super(name, type);
- _contents = new ByteArrayOutputStream();
- }
-
- DataSection(String name, int type, ByteArrayOutputStream contents) {
- super(name, type);
- _contents = contents;
- }
-
- @Override
- void write(ByteBuffer out) {
- out.put(_contents.toByteArray());
- }
-
- @Override
- int size() {
- return _contents.size();
- }
- }
-
- private void pushSection(Section s) {
- _sections.push(s);
- // ELF is effectively "1-based", due to the NULL section
- s._index = _sections._len;
- }
-
- /* creates function and stores where it starts*/
- private final HashMap<TypeFunPtr,Symbol> _funcs = new HashMap<>();
- private void encodeFunctions(SymbolSection symbols, DataSection text) {
- for( int i=0; i<_code._cfg._len; i++ ) {
- if( !(_code._cfg.at(i) instanceof FunNode fun) ) continue;
- // skip until the function ends
- while( !(_code._cfg.at(i) instanceof ReturnNode ret) )
- i++;
- int end = _code._encoding._opStart[ret._nid] + _code._encoding._opLen[ret._nid];
-
- Symbol func = new Symbol(fun._name, text._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC);
- func._value = _code._encoding._opStart[fun._nid];
- func._size = end - func._value;
- symbols.push(func);
- _funcs.put(fun.sig(), func);
- }
- }
-
- public void export(String fname) throws IOException {
- DataSection strtab = new DataSection(".strtab", 3 /* SHT_SYMTAB */);
- // first byte is reserved for an empty string
- strtab._contents.write(0);
- pushSection(strtab);
-
- // place all the symbols
- SymbolSection symbols = new SymbolSection(".symtab", 2 /* SHT_STRTAB */);
- pushSection(symbols);
-
- // zero flag by default
- // we've already constructed this entire section in the encoding phase
- DataSection text = new DataSection(".text", 1 /* SHT_PROGBITS */, _code._encoding._bits);
- text._flags = SHF_ALLOC | SHF_WRITE | SHF_EXECINSTR;
- pushSection(text);
-
- // Build and write constant pool
- BAOS cpool = new BAOS();
- Encoding enc = _code._encoding;
- enc.writeConstantPool(cpool,false);
- DataSection rdata = new DataSection(".rodata", 1 /* SHT_PROGBITS */, cpool);
- rdata._flags = SHF_ALLOC;
- pushSection(rdata);
-
- // populate function symbols
- encodeFunctions(symbols, text);
-
- int idx = 1;
- for( Section s : _sections ) {
- Symbol sym = new Symbol(s._name, idx++, SYM_BIND_LOCAL, SYM_TYPE_SECTION);
- // we can reuse the same name pos from the actual section
- sym._name_pos = s._name_pos;
- sym._size = s.size();
- symbols.push(sym);
- }
-
- // calculate local index
- int num = 1;
- for( Symbol s : symbols._loc )
- s._index = num++;
- // extra space for .rela.text
- int start_global = num+1; // Add one to skip the final .rela.text local symbol
- for( Symbol a: symbols._symbols )
- a._index = start_global++;
-
- // create .text relocations
- DataSection text_rela = new DataSection(".rela.text", 4 /* SHT_RELA */);
- for( Node n : enc._externals.keySet()) {
- int nid = n._nid;
- String extern = enc._externals.get(n);
-
- Symbol sym = new Symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE);
- sym._index = start_global++;
- symbols.push(sym);
-
- int offset = enc._opStart[nid] + enc._opLen[nid] - 4;
-
- // u64 offset
- write8(text_rela._contents, offset);
- // u64 info
- write8(text_rela._contents, ((long)sym._index << 32L) | 4L /* PLT32 */);
- // i64 addend
- write8(text_rela._contents, -4);
- }
-
- // Write relocations for the constant pool
- for( Encoding.Relo relo : enc._bigCons.values() ) {
- Symbol glob = new Symbol("CPOOL$"+start_global, rdata._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC);
- glob._value = relo._target;
- glob._size = relo._t.size();
- glob._index = start_global++;
- symbols.push(glob);
- write8(text_rela._contents, relo._opStart+relo._off);
- write8(text_rela._contents, ((long)glob._index << 32L) | relo._elf );
- write8(text_rela._contents, -4);
- }
-
- text_rela._flags = SHF_INFO_LINK;
- text_rela._link = 2;
- text_rela._info = text._index;
- pushSection(text_rela);
-
- // Final .rela.text symbol
- Symbol sym = new Symbol(text_rela._name, num++, SYM_BIND_LOCAL, SYM_TYPE_SECTION);
- sym._name_pos = text_rela._name_pos;
- sym._size = text_rela.size();
- symbols.push(sym);
-
- // populate string table
- for( Section s : _sections ) { s._name_pos = writeCString(strtab, s._name); }
- for( Symbol s : symbols._symbols ) { s._name_pos = writeCString(strtab, s._name); }
- for( Symbol s : symbols._loc ) { s._name_pos = writeCString(strtab, s._name); }
-
- int size = 64; // ELF header
- // size of all section data
- for( Section s : _sections ) {
- s._file_offset = size;
- size += s.size();
- }
- // headers go at the very end
- int shdr_offset = size;
- // there's a "null" section at the start of the section headers
- size += SECTION_HDR_SIZE*(1 + _sections.len());
-
- // System.out.printf("Hello %d, %d, %s\n", size, shdr_offset);
- ByteBuffer out = ByteBuffer.allocate(size);
- out.order(ByteOrder.LITTLE_ENDIAN);
-
- // ELF header
- out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 });
- out.put(new byte[8]);
- out.putShort((short)1 /* ET_REL */); // type
- out.putShort((short)62 /* AMD64 */); // machine
- out.putInt(1); // version
- out.putLong(0); // entry
- out.putLong(0); // phoff
- out.putLong(shdr_offset); // shoff
- out.putInt(0); // flags
- out.putShort((short)64); // ehsize
- out.putShort((short)0); // phentsize
- out.putShort((short)0); // phnum
- out.putShort((short)64); // shentsize
- out.putShort((short) (1 + _sections.len())); // shnum
- out.putShort((short)1); // shstrndx
-
- // raw section data
- for( Section s : _sections ) {
- s.write(out);
- }
-
- // "null" section
- for( int i = 0; i < 16; i++ ) {
- out.putInt(0);
- }
-
- for( Section s : _sections ) {
- s.writeHeader(out);
- }
- assert out.position() == size;
-
- File file = new File(fname);
- if( file.getParentFile()!=null )
- file.getParentFile().mkdirs();
- BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
- bos.write(out.array());
- bos.close();
- }
-
-}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java b/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
new file mode 100644
index 00000000..534bb10f
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
@@ -0,0 +1,234 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.io.*;
+import java.nio.file.Files;
+import java.util.HashMap;
+
+public class ElfReader {
+
+ public final File _file;
+ public final byte[] _buf;
+ int _x; // Cursor into buffer
+ // ELF Header
+ public final ElfHeader _header;
+ // All sections
+ public final Section[] _sections;
+ // Simple section, if available
+ public final Section _simple;
+ // Byte array in/out stream - used as a reader
+ public final BAOS _bais;
+ // Compilation unit; written into FunNodes
+ public final CompUnit _compunit;
+
+ // Strings from the Simple section, either small public set or whole set
+ public String[] _strs;
+ // Dependent file names
+ public String[] _deps;
+ // Only published symbol this file
+ public TypeStruct _clz;
+ // Canonical named types, distinct from optimized same-named graph states.
+ public TypeStruct[] _pubTypes;
+ // Nodes read in this Elf
+ public Ary<Node> _nodes;
+
+ // Load an Elf file (mostly lazily)
+ public static ElfReader load( File f, CompUnit cu ) {
+ try { return new ElfReader(f,cu); }
+ catch( IOException ioe ) { return null; }
+ }
+
+ // Load the Simple section from the ELF file
+ private ElfReader( File f, CompUnit cu ) throws IOException {
+ _file = f;
+ _compunit = cu;
+ _buf = Files.readAllBytes(f.toPath());
+ _header = new ElfHeader();
+ // No attempt is made to parse the program header table.
+ _sections = new Section[_header._shnum];
+ for( int i=0; i<_header._shnum; i++ )
+ _sections[i] = new Section(i);
+
+ // Load section names
+ Section secstr = _sections[_header._shstrndx];
+ for( Section sec : _sections )
+ if( sec._type != 0 )
+ sec._name = secstr.str(sec._noff);
+
+ Section simple = null;
+ for( Section sec : _sections )
+ if( ".simple".equals(sec._name) )
+ simple = sec;
+ _simple = simple;
+ _bais = new BAOS(_buf, (int)_simple._offset);
+ }
+
+ // For Serialize self-test
+ ElfReader( BAOS bais ) {
+ _file = null;
+ _compunit = null;
+ _buf = bais.buf();
+ _header = null;
+ _sections = null;
+ _simple = null;
+ _bais = bais;
+ }
+
+ // Load the public symbols and dependent objs, but not the whole IR
+ void loadPublicSymbols() {
+ // Check already loaded and cached
+ if( _strs == null && _simple != null )
+ // Read only the published public strings, for speed purposes when
+ // scanning a large count of ELF files but getting hits in only a
+ // few of them. i.e. optimizing (prematurely?) for the traditional
+ // case of linking a small file against a large library
+ Serialize.read_public_strs(this);
+ }
+
+ // Loads the entire IR
+ public TypeStruct loadSimple(CodeGen code) {
+ Serialize.readAll(code, this, code._aliases, code._fidxs, code._rpcs);
+ return _clz;
+ }
+
+ // Loads strings, mappings, and types, but not Nodes. Used after the
+ // public-string scan finds a matching Simple ELF, so parsing can resolve
+ // imported field/array aliases before the full graph is linked in later.
+ public TypeStruct loadPublicTypes(CodeGen code) {
+ Serialize.readTypes(code, this, code._aliases, code._fidxs, code._rpcs);
+ return _clz;
+ }
+
+ int u1() { return _buf[_x++]&0xFF; }
+ int magic() { // Big endian
+ return (u1() << 24) | (u1() << 16) | (u1() << 8) | u1();
+ }
+ int u2() { return u1() | (u1()<<8); } // Little endian
+ int u4() { // Little endian
+ return u1() | (u1() << 8) | (u1() << 16) | (u1() << 24);
+ }
+ long u8() { return u4() | ((long)u4()<<32); }
+ void skip(int skip) { _x += skip; }
+
+
+ public class ElfHeader {
+ final int _magic;
+ final boolean _format32; // Format32 or format64
+ final byte _osabi;
+ final boolean _little; // True if little endian, false if big endian
+ final char _type;
+ final char _machine;
+ final long _entry; // Process entry point
+ final long _phoff; // Program header table offset, from file start
+ final long _shoff; // Section header table offset, from file start
+ final int _flags; // Flags
+ final int _phnum; // Number of program header entries
+ final int _shentsize;
+ final int _shnum; // Number of section header entries
+ final int _shstrndx; // Section header index contains section names
+
+
+ ElfHeader() throws IOException {
+ _x = 0;
+ if( (_magic = magic()) != 0x7F454C46 ) bad("magic number",_magic);
+ byte EI_CLASS = (byte)u1();
+ if( EI_CLASS != 1 && EI_CLASS != 2 ) bad("EI_CLASS",EI_CLASS);
+ _format32 = (EI_CLASS==1);
+ byte EI_DATA = (byte)u1();
+ if( EI_DATA != 1 && EI_DATA != 2 ) bad("EI_DATA",EI_DATA);
+ _little = EI_DATA == 1;
+ if( u1() != 1 ) bad("version",1);
+ if( (_osabi=(byte)u1()) != 0 ) bad("EI_VERSION",_osabi);
+ byte abiversion = (byte)u1();
+ if( abiversion != 0 ) bad("EI_ABIVERSION",abiversion);
+ skip(7); // Pad
+
+ // Endian matters now
+ if( !_little ) throw new IOException("TODO: Big endian");
+ if( (_type = (char)u2()) != 1 ) bad("ET_REL",_type);
+ if( (_machine = (char)u2()) != 0x3E ) bad("AMD x86-64",_machine);
+ int ver = u4();
+ if( ver != 1 ) bad("Version",ver);
+ // Format32 matters now
+ if( _format32 ) bad("Format",1);
+ _entry = u8(); // Process entry point
+ _phoff = u8(); // Program header table offset
+ _shoff = u8(); // Section header table offset
+ _flags = u4(); // Flags
+ int ehsize = u2();
+ if( ehsize != 0x40 ) bad("EHSIZE",ehsize);
+ int phentsize = u2();
+ if( phentsize != 0x38 && phentsize != 0 ) bad("PHENTSIZE",phentsize);
+ _phnum = u2();
+ _shentsize = u2();
+ if( _shentsize != 0x40 ) bad("SHENTSIZE",_shentsize);
+ _shnum = u2();
+ // String section
+ _shstrndx = u2();
+ assert _x == ehsize;
+ }
+ }
+
+ public class Section {
+ final int _noff; // Offset in string section for name
+ final int _type; // Section type
+ final long _flags;
+ final long _addr; // Offset in virtual mem, if loaded
+ final long _offset; // Offset in file
+ final long _size; // Size in bytes
+ final int _link; // Associated section index, varies
+ final int _info; // Varies
+ final byte _align; // Log2 alignment
+ final int _esize; // Element size, if non-zero
+ String _name;
+
+ Section(int sidx) throws IOException {
+ _x = (int)(_header._shoff + (sidx * _header._shentsize));
+ _noff = u4();
+ _type = u4();
+ _flags = u8();
+ _addr = u8();
+ _offset= u8();
+ _size = u8();
+ _link = u4();
+ _info = u4();
+ long align = u8();
+ if( align==0 ) align=1;
+ _align = (byte)Long.numberOfTrailingZeros(align);
+ if( (1L << _align ) != align )
+ throw new IOException("Bad section alignment: "+align);
+ _esize = (int)u8();
+ }
+
+ String str(int off) {
+ assert _type == 3/*SHT_STRTAB*/; // String table section header
+ int start = (int)(_offset+off);
+ int x = start;
+ while( _buf[x++] !=0 ) ;
+ return new String(_buf,start,x-start-1);
+ }
+ }
+
+ private static void bad(String msg, int val) throws IOException {
+ throw new IOException("Bad "+msg+": "+val);
+ }
+
+
+
+ //public TypeMemPtr lookup( String name ) {
+ // if( _simple == null ) return null;
+ // BAOS baos = new BAOS(_buf, (int)_simple._offset);
+ // Ary<String> strs = Serialize.read_strs(baos);
+ //
+ //
+ //
+ // Ary<Node> nodes = Serialize.read_types(baos);
+ //
+ // throw Utils.TODO("Elf load symbol "+name);
+ //}
+}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
new file mode 100644
index 00000000..508cdcca
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -0,0 +1,387 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.HashMap;
+
+public class ElfWriter {
+
+ final CodeGen _code;
+ Ary<Section> _sections;
+ StringSection _strtab;
+
+ ElfWriter( CodeGen code ) {
+ _code = code;
+ }
+
+ // ------------------------------------------------------------------------
+ abstract public class Section {
+ static final int SHT_NULL = 0;
+ static final int SHT_PROGBITS= 1;
+ static final int SHT_SYMTAB = 2;
+ static final int SHT_STRTAB = 3;
+ static final int SHT_RELA = 4;
+ static final int SHT_SIMPLE = 10; // Simple section
+
+ final String _name;
+ final int _type, _entsize, _index, _name_pos, _flags;
+ int _link, _info;
+ int _file_offset;
+
+ Section(String name, int type, int entsize, int flags) {
+ _name = name;
+ _type = type;
+ _entsize = entsize;
+ _flags = flags;
+ _index = _sections._len;
+ _sections.push(this);
+ _name_pos = name.isEmpty()
+ ? 0 // Null section
+ : _strtab == null
+ ? 1 // StringSection
+ // All the other sections
+ : _strtab.writeCString(name);
+ }
+
+ void writeHeader(ByteBuffer out) {
+ out.putInt(_name_pos); // name
+ out.putInt(_type); // type
+ out.putLong(_flags); // flags
+ out.putLong(0); // addr
+ out.putLong(_file_offset); // offset
+ out.putLong(size()); // size
+ out.putInt(_link); // link
+ out.putInt(_info); // info
+ out.putLong(16); // addralign
+ out.putLong(_entsize); // Fixed-size element size
+ }
+
+ abstract void write(ByteBuffer out);
+ abstract int size();
+ }
+
+ // ------------------------------------------------------------------------
+ // Generic data sections; carries about a BAOS for bits
+ public class DataSection extends Section {
+ static final int SHF_WRITE = 1;
+ static final int SHF_ALLOC = 2;
+ static final int SHF_EXECINSTR = 4;
+ static final int SHF_INFO_LINK = 64;
+
+ final BAOS _contents;
+
+ DataSection( String name, int type, int flags, int entsize ) { this(name, type, flags, entsize, new BAOS() ); }
+ DataSection( String name, int type, BAOS contents, int flags ) { this(name, type, flags, 0, contents ); }
+ private DataSection( String name, int type, int flags, int entsize, BAOS contents ) {
+ super(name, type, entsize, flags);
+ _contents = contents;
+ }
+
+ @Override void write(ByteBuffer out) { out.put(_contents.toByteArray()); }
+ @Override int size() { return _contents.size(); }
+ public void write2( int op ) {
+ _contents.write(op );
+ _contents.write(op>> 8);
+ }
+ public void write4( int op ) {
+ _contents.write(op );
+ _contents.write(op>> 8);
+ _contents.write(op>>16);
+ _contents.write(op>>24);
+ }
+ public void write8( long i64 ) {
+ write4( (int) i64 );
+ write4( (int)(i64>>32));
+ }
+
+ }
+
+ // ------------------------------------------------------------------------
+ // Section to collect and de-dup strings
+ public class StringSection extends DataSection {
+ final HashMap<String,Integer> _dedup = new HashMap<>();
+
+ StringSection() {
+ super(".strtab",SHT_STRTAB, 0, 0 );
+ int emptyoff = writeCString(""); // Always empty string at offset 0
+ assert emptyoff == 0;
+ int namepos = writeCString(".strtab");
+ assert namepos == _name_pos;
+ }
+
+ public int writeCString(String name) {
+ if( name==null ) return 0; // Anonymous functions have no name
+ Integer ii = _dedup.get(name);
+ if( ii != null ) return ii;
+ // place name in the string table
+ int pos = _contents.size();
+ _dedup.put(name,pos);
+ byte[] name_bytes = name.getBytes();
+ _contents.write(name_bytes);
+ // the strings are null terminated
+ _contents.write(0);
+ return pos;
+ }
+ }
+
+ // ------------------------------------------------------------------------
+ // The relocations; 3 words per relocation
+ public class ReloSection extends DataSection {
+ ReloSection( int info ) {
+ super(".rela.text", SHT_RELA, SHF_INFO_LINK, 24/*entsize*/);
+ _link = 2;
+ _info = info;
+ }
+
+ void writeReloOffPlus(int offset, int symidx, byte relo) {
+ assert symidx > 0;
+ write8(offset);
+ write8(((long)symidx << 32L) | relo);
+ write8(-4);
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // Symbols section; emit the symbols into the data section directly
+ public class SymbolSection extends DataSection {
+ public static final int SYMBOL_SIZE = 4+1+1+2+8+8;
+ final BAOS _globals;
+ final int _nlocals;
+
+ SymbolSection(int nlocals) {
+ super(".symtab", SHT_SYMTAB, 0/*flags*/, SYMBOL_SIZE);
+ _globals = new BAOS();
+ _nlocals = nlocals;
+ }
+
+ // Global symbol index
+ int gidx() { return _nlocals+(_globals.size()/SYMBOL_SIZE); }
+
+ public int symbol( String name, int shndx, int bind, int type, long value, long size ) {
+ int gidx = gidx(); // Global index before I write to _globals
+ BAOS baos = bind==SYM_BIND_GLOBAL ? _globals : _contents;
+ write4(baos,_strtab.writeCString(name));
+ int info = (bind << 4) | (type & 0xF);
+ baos.write(info);
+ baos.write(0); // other/visibility
+ write2(baos,shndx); // section index
+ write8(baos,value);
+ write8(baos,size);
+ return bind==SYM_BIND_GLOBAL ? gidx : -99;
+ }
+
+ public int symbol( String name, int shndx, int bind, int type ) {
+ return symbol(name,shndx,bind,type,0,0);
+ }
+
+ // creates function and stores where it starts.
+ // Filters to just this class.obj file.
+ void encodeFunctions(StopNode stop, Encoding enc, int text_idx) {
+ for( Node stopcu : stop._inputs ) {
+ for( Node ret : stopcu._inputs ) {
+ FunNode fun = ((ReturnNode)ret).fun();
+ if( !_code.owns(fun) )
+ continue;
+ int end = enc.opStart(fun.ret()) + enc.opLen(fun.ret());
+ long value = enc.opStart(fun);
+ long size = end - value;
+ if( fun._name != null ) // Anonymous functions have no name
+ symbol(fun._name, text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, value, size);
+ }
+ }
+ }
+
+ // Insert a default-main for top-level compilations, if the top-level
+ // does not declare a main.
+ void symbolEntryClinit(int text_idx, Encoding enc) {
+ FunNode clinit = null;
+ String entryName = _code.entryClinitName();
+ for( FunNode fun : _code._linker )
+ if( fun != null && _code.owns(fun) ) {
+ if( "main".equals(fun._name) )
+ return; // Top-level declared a main, take the existing one
+ if( entryName.equals(fun._name) )
+ clinit = fun; // Keep top-level entry, if we don't find an explicit main
+ }
+ // No top-level main, so the top-level clinit becomes a default main
+ if( clinit != null ) {
+ assert enc.opStart(clinit) == 0;
+ symbol("main",text_idx, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, enc.opStart(clinit), 0);
+ return;
+ }
+ throw new IllegalStateException("Missing entry <clinit> "+entryName);
+ }
+
+ static void write2( BAOS bits, int op ) {
+ bits.write(op );
+ bits.write(op>> 8);
+ }
+ static void write4( BAOS bits, int op ) {
+ bits.write(op );
+ bits.write(op>> 8);
+ bits.write(op>>16);
+ bits.write(op>>24);
+ }
+ static void write8( BAOS bits, long i64 ) {
+ write4(bits, (int) i64 );
+ write4(bits, (int)(i64>>32));
+ }
+
+ @Override
+ void writeHeader(ByteBuffer out) {
+ // points to string table section
+ _link = _strtab._index;
+ _info = _nlocals;
+ super.writeHeader(out);
+ }
+
+ @Override void write(ByteBuffer out) {
+ out.put(_contents.toByteArray());
+ out.put(_globals .toByteArray());
+ }
+ @Override int size() {
+ return _contents.size() + _globals.size();
+ }
+ }
+
+ public static final int SYM_BIND_LOCAL = 0;
+ public static final int SYM_BIND_GLOBAL = 1;
+
+ public static final int SYM_TYPE_NOTYPE = 0;
+ public static final int SYM_TYPE_OBJECT = 1;
+ public static final int SYM_TYPE_FUNC = 2;
+ public static final int SYM_TYPE_SECTION = 3;
+
+ public static final int SECTION_HDR_SIZE = 64;
+
+ // ------------------------------------------------------------------------
+ public class SimpleSection extends DataSection {
+ SimpleSection(BAOS serial) { super(".simple", SHT_SIMPLE, serial, 0); }
+ }
+
+
+ // ------------------------------------------------------------------------
+
+ public ElfWriter export(boolean emitEntrySymbol) {
+ // Sections are created in the order they are emitted.
+ _sections = new Ary<>(Section.class);
+
+ // Null section
+ Section nullSection = new DataSection("",Section.SHT_NULL,0,0);
+
+ // String section next; written out early and available to record/dedup
+ // strings for all remaining sections
+ _strtab = new StringSection();
+
+ // place all the symbols
+ int nlocals = 1/*null*/+7/*number of sections*/+1/*Simple section*/;
+ SymbolSection symbols = new SymbolSection(nlocals);
+
+ // we've already constructed these entire sections in the encoding phase
+ Encoding enc = _code._encoding;
+ DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, enc._bits , DataSection.SHF_ALLOC | DataSection.SHF_WRITE | DataSection.SHF_EXECINSTR );
+ // Constant pool
+ DataSection rodata = new DataSection(".rodata", Section.SHT_PROGBITS, enc._cpool, DataSection.SHF_ALLOC );
+ // Static/global pool
+ DataSection rwdata = new DataSection(".data" , Section.SHT_PROGBITS, enc._sdata, DataSection.SHF_ALLOC | DataSection.SHF_WRITE );
+
+
+ // populate function symbols
+ symbols.encodeFunctions(_code._stop, _code._encoding, text._index);
+ // Export the source compilation unit <clinit> as the C runtime "main".
+ // Encoding keeps this function at text offset zero.
+ if( emitEntrySymbol )
+ symbols.symbolEntryClinit(text._index,enc);
+
+ // create external .text relocations
+ ReloSection relocations = new ReloSection(text._index);
+
+ for( Node n : enc._externals.keySet()) {
+ String extern = enc._externals.get(n);
+ int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_NOTYPE);
+ int offset = enc.opStart(n) + enc.opLen(n) - 4;
+ relocations.writeReloOffPlus(offset, symidx, (byte)4/*PLT32*/);
+ }
+
+ // Write relocations for the constant pool
+ for( Encoding.Relo relo : enc._bigCons.values() ) {
+ boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant();
+ DataSection data = ro ? rodata : rwdata;
+ int symidx = symbols.symbol("CPOOL$"+symbols.gidx(), data._index, SYM_BIND_GLOBAL, SYM_TYPE_FUNC, relo._target, relo._t.size());
+ relocations.writeReloOffPlus(relo._opStart+relo._off, symidx, relo._elf );
+ }
+
+ // Create a "Simple" section for Simple types and ir
+ SimpleSection simple = new SimpleSection(_code._serial);
+
+ // Write the local section header symbols
+ assert nlocals == 1+_sections._len;
+ symbols.symbol("",0,0,0); // Null symbol at index 0
+ for( Section s : _sections )
+ symbols.symbol(s._name, s._index, SYM_BIND_LOCAL, SYM_TYPE_SECTION, 0, s.size());
+
+ int size = 64; // ELF header size
+ // size of all section data
+ for( Section s : _sections ) {
+ s._file_offset = size;
+ size += s.size();
+ }
+ // headers go at the very end
+ int shdr_offset = size;
+ size += SECTION_HDR_SIZE*_sections.len();
+
+ ByteBuffer out = ByteBuffer.allocate(size);
+ out.order(ByteOrder.LITTLE_ENDIAN);
+
+ // ELF header
+ out.put(new byte[]{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0 });
+ out.put(new byte[8]);
+ out.putShort((short)1 /* ET_REL */); // type
+ out.putShort((short)62 /* AMD64 */); // machine
+ out.putInt(1); // version
+ out.putLong(0); // entry
+ out.putLong(0); // phoff
+ out.putLong(shdr_offset); // shoff
+ out.putInt(0); // flags
+ out.putShort((short)64); // ehsize
+ out.putShort((short)0); // phentsize
+ out.putShort((short)0); // phnum
+ out.putShort((short)64); // shentsize
+ out.putShort((short) _sections.len()); // shnum
+ out.putShort((short) _strtab._index); // shstrndx
+
+ // raw section data
+ for( Section s : _sections )
+ s.write(out);
+
+ // Section headers
+ for( Section s : _sections )
+ s.writeHeader(out);
+
+ assert out.position() == size;
+
+ String objName = _code._buildDir + "/" + _code._srcName + ".o";
+ File file = new File(objName);
+ if( file.getParentFile()!=null )
+ file.getParentFile().mkdirs();
+ try {
+ BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));
+ bos.write(out.array());
+ bos.close();
+ } catch( IOException ioe ) {
+ // Caller does not want to deal, but it is a failed compilation in any case
+ throw new RuntimeException(ioe);
+ }
+ // Reset for next ELF
+ _sections = null;
+ _strtab = null;
+ return this;
+ }
+
+}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
index e3684445..4c7475fd 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
@@ -1,12 +1,10 @@
package com.seaofnodes.simple.codegen;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.print.IRPrinter;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.io.ByteArrayOutputStream;
+import com.seaofnodes.simple.util.*;
import java.util.*;
@@ -24,11 +22,14 @@ public class Encoding {
// Top-level program graph structure
public final CodeGen _code;
+ // CFG Nodes in RPO order, per-encoding.
+ public Ary<CFGNode> _cfg;
+
// Instruction bytes. The registers are encoded already. Relocatable data
// is in a fixed format depending on the kind of relocation.
// - RIP-relative offsets into the same chunk are just encoded with their
- // check relative offsets; no relocation info is required.
+ // chunk relative offsets; no relocation info is required.
// - Fixed address targets in the same encoding will be correct for a zero
// base; moving the entire encoding requires adding the new base address.
@@ -36,17 +37,10 @@ public class Encoding {
// - RIP-relative to external chunks have a zero offset; the matching
// relocation info will be used to patch the correct value.
- public static class BAOS extends ByteArrayOutputStream {
- public byte[] buf() { return buf; }
- void set( byte[] buf0, int count0 ) { buf=buf0; count=count0; }
- };
- final public BAOS _bits = new BAOS();
-
- public int [] _opStart; // Start of opcodes, by _nid
- public byte[] _opLen; // Length of opcodes, by _nid
-
- // Function headers now padded when printing
- public boolean _padFunHeads;
+ public final BAOS _bits = new BAOS(); // Instructions / encodings
+ public final BAOS _cpool = new BAOS(); // Constant r/o pool
+ public final BAOS _sdata = new BAOS(); // Static r/w pool
+ public final HashMap<Node,Relo> _bigCons = new HashMap<>();
// Big Constant relocation info.
public static class Relo {
@@ -54,15 +48,16 @@ public class Encoding {
public final Type _t; // Constant type
public final byte _off; // Offset from start of opcode
public final byte _elf; // ELF relocation type, e.g. 2/PC32
- public int _target; // Where constant is finally placed
- public int _opStart; // Opcode start
+ public int _target; // Where constant is finally placed
+ public int _opStart; // Opcode start
Relo( Node op, Type t, byte off, byte elf ) {
_op=op; _t=t; _off=off; _elf=elf;
}
}
- public final HashMap<Node,Relo> _bigCons = new HashMap<>();
- Encoding( CodeGen code ) { _code = code; }
+ Encoding( CodeGen code ) {
+ _code = code;
+ }
// Shortcut to the defining register
public short reg(Node n) {
@@ -103,14 +98,6 @@ public class Encoding {
}
// Convenience for writing log-N
- static void addN( int log, Type t, BAOS bits ) {
- long x = t instanceof TypeInteger ti
- ? ti.value()
- : log==3
- ? Double.doubleToRawLongBits( ((TypeFloat)t).value())
- : Float.floatToRawIntBits((float)((TypeFloat)t).value());
- addN(log,x,bits);
- }
static void addN( int log, long x, BAOS bits ) {
for( int i=0; i < 1<<log; i++ ) {
bits.write((int)x);
@@ -123,48 +110,88 @@ public class Encoding {
public Encoding add4( int op ) { addN(2,op,_bits); return this; }
public Encoding add8(long op ) { addN(3,op,_bits); return this; }
+ private final static int SENTINEL = -1;
+ private final IntHashMap _opStart = new IntHashMap(SENTINEL);
+ private final IntHashMap _opLen = new IntHashMap(SENTINEL);
+
+
+ // Read op start for n
+ public int opStart(Node n) { return _opStart.get(n._nid); }
+ // Read op length for n
+ public byte opLen (Node n) { return (byte)_opLen.get(n._nid); }
+
+ // Set op start for n
+ public void opStart(Node n, int off) {
+ assert off != SENTINEL;
+ _opStart.put(n._nid,off);
+ }
+
+ // Set op start for n
+ public void opStartAdd(Node n, int off) {
+ if( off==0 ) return;
+ int old = _opStart.get(n._nid);
+ assert old != SENTINEL && old+off != SENTINEL;
+ _opStart.put(n._nid,old+off);
+ }
+
+ // Set op length for n
+ public void opLen(Node n, byte len) {
+ assert len != SENTINEL;
+ _opLen.put(n._nid,len);
+ }
// Nodes need "relocation" patching; things done after code is placed.
// Record src and dst Nodes.
private final HashMap<Node,CFGNode> _internals = new HashMap<>();
// Source is a Call, destination in the Fun.
public Encoding relo( CallNode call ) {
- _internals.put(call,_code.link(call.tfp()));
+ FunNode fun = _code.link(call.tfp());
+ if( fun != null && !_code.owns(fun) )
+ _externals.put(call,fun._name);
+ else
+ _internals.put(call,fun);
return this;
}
- public Encoding relo( ConstantNode con ) {
- TypeFunPtr tfp = (TypeFunPtr)con._con;
- _internals.put(con,_code.link(tfp));
+ // Code address as a constant
+ public Encoding relo( FunPtrNode fptr ) {
+ TypeFunPtr tfp = (TypeFunPtr)fptr._type;
+ FunNode fun = _code.link(tfp);
+ if( fun != null && !_code.owns(fun) )
+ _externals.put(fptr,fun._name);
+ else
+ _internals.put(fptr,fun);
return this;
}
+ // Local jump
public void jump( CFGNode jmp, CFGNode dst ) {
_internals.put(jmp,dst.uctrlSkipEmpty());
}
-
- final HashMap<Node,String> _externals = new HashMap<>();
+ // External references only located by Strings a link-time
+ public final HashMap<Node,String> _externals = new HashMap<>();
public Encoding external( Node n, String extern ) {
_externals.put(n,extern);
return this;
}
// Store t as a 32/64 bit constant in the code space; generate RIP-relative
- // addressing to load it
+ // addressing to load it. Type is stored in either the .rodata or .data.
public void largeConstant( Node relo, Type t, int off, int elf ) {
- assert t.isConstant();
+ // TODO: Any-old struct in the cpool
+ assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name));
assert (byte)off == off;
assert (byte)elf == elf;
_bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf));
}
// --------------------------------------------------
- void encode() {
+ Encoding encode( ) {
// Basic block layout: negate branches to keep blocks in-order; insert
// unconditional jumps. Attempt to keep backwards branches taken,
// forwards not-taken (this is the default prediction on most
// hardware). Layout is still Reverse Post Order but with more
// restrictions.
- basicBlockLayout();
+ basicBlockLayout( );
// Write encoding bits in order into a big byte array.
// Record opcode start and length.
@@ -174,8 +201,15 @@ public class Encoding {
// encodings and compact the code, changing all the offsets.
compactShortForm();
+ // Write the constant pool
+ writeConstantPool(_cpool,true );
+ // Write the static memory
+ writeConstantPool(_sdata,false);
+
// Patch RIP-relative and local encodings now.
patchLocalRelocations();
+
+ return this;
}
// --------------------------------------------------
@@ -183,29 +217,48 @@ public class Encoding {
// unconditional jumps. Attempt to keep backwards branches taken, forwards
// not-taken (this is the default prediction on most hardware). Layout is
// still Reverse Post Order but with more restrictions.
- private void basicBlockLayout() {
+ private void basicBlockLayout( ) {
IdentityHashMap<LoopNode,Ary<CFGNode>> rpos = new IdentityHashMap<>();
- Ary<CFGNode> rpo = new Ary<>(CFGNode.class);
- rpos.put(_code._start.loop(),rpo);
+ _cfg = new Ary<>(CFGNode.class);
+ rpos.put(_code._start.loop(),_cfg);
BitSet visit = _code.visit();
- rpo.add(_code._stop);
- FunNode main = _code.link(_code._main);
- for( Node n : _code._start._outputs )
- if( n instanceof FunNode fun && fun != main ) {
- int x = rpo._len;
- _rpo_cfg(fun, visit, rpos );
- assert rpo.at(x) instanceof ReturnNode;
+
+ // Do them all except the <clinit>s
+ Ary<FunNode> clinits = new Ary<>(FunNode.class);
+ FunNode entry = null;
+ String entryName = _code.entryClinitName();
+ for( FunNode fun : _code._linker ) {
+ if( fun != null && !fun.isDead() && _code.owns(fun) ) {
+ if( fun.isClz() ) {
+ if( fun._name.equals(entryName) )
+ entry = fun;
+ else
+ clinits.add(fun);
+ } else {
+ int x = _cfg._len;
+ _rpo_cfg(fun,visit,rpos);
+ assert _cfg.at(x) instanceof ReturnNode;
+ }
}
- // Do any main function last... so it lands at offset 0 in the RPO
- if( main!=null )
- _rpo_cfg(main, visit, rpos );
- rpo.add(_code._start);
+ }
+ // Now the <clinit>s last, so when reversed they become first. The
+ // program entry <clinit> is last of all, so it lands at offset zero.
+ for( FunNode clinit : clinits ) {
+ int x = _cfg._len;
+ _rpo_cfg(clinit,visit,rpos);
+ assert _cfg.at(x) instanceof ReturnNode;
+ }
+ if( entry != null ) {
+ int x = _cfg._len;
+ _rpo_cfg(entry,visit,rpos);
+ assert _cfg.at(x) instanceof ReturnNode;
+ }
+
// Reverse in-place
- for( int i=0; i< rpo.size()>>1; i++ )
- rpo.swap(i,rpo.size()-1-i);
+ for( int i=0; i< _cfg.size()>>1; i++ )
+ _cfg.swap(i,_cfg.size()-1-i);
visit.clear();
- _code._cfg = rpo; // Save the new ordering
}
@@ -279,7 +332,7 @@ public class Encoding {
private static boolean shouldInvert(CFGNode t, CFGNode f, int bld) {
int tld = t.loopDepth(), fld = f.loopDepth();
// These next two are symmetric and can happen in any order; if `tld <
- // bld` is true, the `fld < bld` must be false, or else both directions
+ // bld` is true, then `fld < bld` must be false, or else both directions
// exit the loop... and the IF test would not be in the loop.
// true to exit a loop usually (and false falls into Yet Another Loop
@@ -324,24 +377,26 @@ public class Encoding {
// --------------------------------------------------
// Write encoding bits in order into a big byte array.
// Record opcode start and length.
- public FunNode _fun; // Currently encoding function
+
+ // Current function is used by the spill-op encodings to query the stack
+ // frame layout and get the spill offsets.
private void writeEncodings() {
- _opStart= new int [_code.UID()];
- _opLen = new byte[_code.UID()];
- for( CFGNode bb : _code._cfg ) {
+ for( CFGNode bb : _cfg ) {
if( !(bb instanceof MachNode mach0) )
- _opStart[bb._nid] = _bits.size();
- else if( bb instanceof FunNode fun ) {
- _fun = fun; // Currently encoding function
- _opStart[bb._nid] = _bits.size();
+ opStart(bb, _bits.size());
+ else if( bb instanceof FunNode ) {
+ opStart(bb, _bits.size());
mach0.encoding( this );
- _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]);
+ opLen(bb, (byte) (_bits.size() - opStart(bb)));
}
for( Node n : bb._outputs ) {
- if( n instanceof MachNode mach && !(n instanceof FunNode) ) {
- _opStart[n._nid] = _bits.size();
+ if( n instanceof MachNode mach && !(n instanceof FunNode) &&
+ // FunPtrs are encoding in the block of their control, not
+ // the block of the ReturnNode input - which is also a CFG.
+ !(n instanceof FunPtrNode && bb instanceof ReturnNode) ) {
+ opStart(n, _bits.size());
mach.encoding( this );
- _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]);
+ opLen(n, (byte) (_bits.size() - opStart(n)));
}
}
}
@@ -351,10 +406,10 @@ public class Encoding {
// Short-form RIP-relative support: replace short encodings with long
// encodings and expand the code, changing all the offsets.
private void compactShortForm() {
- int len = _code._cfg._len;
+ int len = _cfg._len;
int[] oldStarts = new int[len];
for( int i=0; i<len; i++ )
- oldStarts[i] = _opStart[_code._cfg.at(i)._nid];
+ oldStarts[i] = opStart(_cfg.at(i));
// TODO: Rewrite this algo to use the small "_jmps" list of just the
// jumps instead of walking all blocks.
@@ -368,24 +423,27 @@ public class Encoding {
while( slide != 0) { // While no fails
slide = 0;
for( int i=0; i<len; i++ ) {
- CFGNode bb = _code._cfg.at(i);
- _opStart[bb._nid] += slide;
+ CFGNode bb = _cfg.at(i);
+ opStartAdd(bb, slide);
// Slide down all other (non-CFG) ops in the block
for( Node n : bb._outputs )
- if( n instanceof MachNode && !(n instanceof CFGNode) )
- _opStart[n._nid] += slide;
+ if( n instanceof MachNode && !(n instanceof CFGNode) &&
+ // Match writeEncodings(): FunPtrs are encoded in the
+ // block of their control, not again at the Return.
+ !(n instanceof FunPtrNode && bb instanceof ReturnNode) )
+ opStartAdd(n, slide);
if( bb instanceof RIPRelSize riprel ) {
CFGNode target = (bb instanceof IfNode iff ? iff.cproj(0) : (CFGNode)bb.out(0)).uctrlSkipEmpty();
// Delta is from opStart to opStart. X86 at least counts
// the delta from the opEnd, but we don't have the end until
// we decide the size - so the encSize has to deal
- int delta = _opStart[target._nid] - _opStart[bb._nid];
+ int delta = opStart(target) - opStart(bb);
byte opLen = riprel.encSize(delta);
// Recorded size is smaller than the current size?
- if( _opLen[bb._nid] < opLen ) {
+ if( opLen(bb) < opLen ) {
// Start sliding the code down; record slide amount and new size
- slide += opLen - _opLen[bb._nid];
- _opLen[bb._nid] = opLen;
+ slide += opLen - opLen(bb);
+ opLen(bb, opLen);
}
}
}
@@ -396,21 +454,21 @@ public class Encoding {
// short-jumps span function headers, the padding will not make any
// short jumps fail.
for( int i=0; i<len; i++ ) {
- CFGNode bb = _code._cfg.at(i);
+ CFGNode bb = _cfg.at(i);
// Functions pad to align 16
if( bb instanceof FunNode ) {
- int newStart = _opStart[bb._nid]+slide;
+ int newStart = opStart(bb)+slide;
slide += (newStart+15 & -16)-newStart;
}
- _opStart[bb._nid] += slide;
+ opStartAdd(bb, slide);
for( Node n : bb._outputs )
- if( n instanceof MachNode && !(n instanceof CFGNode) )
- _opStart[n._nid] += slide;
+ if( n instanceof MachNode && !(n instanceof CFGNode) &&
+ !(n instanceof FunPtrNode && bb instanceof ReturnNode) )
+ opStartAdd(n, slide);
}
- _padFunHeads = true;
// Copy/slide the bits to make space for all the longer branches
- int grow = _opStart[_code._cfg.at(len-1)._nid] - oldStarts[len-1];
+ int grow = opStart(_cfg.at(len-1)) - oldStarts[len-1];
if( grow > 0 ) { // If no short-form ops, nothing to do here
int end = _bits.size();
byte[] bits = new byte[end+grow];
@@ -418,7 +476,7 @@ public class Encoding {
int start = oldStarts[i];
if( start==0 && i>1 ) continue;
int oldStart = oldStarts[i];
- int newStart = _opStart[_code._cfg.at(i)._nid];
+ int newStart = opStart(_cfg.at(i));
System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start);
end = start;
}
@@ -433,22 +491,28 @@ public class Encoding {
void patchLocalRelocations() {
// Walk the local code-address relocations
for( Node src : _internals.keySet() ) {
- int start = _opStart[src._nid];
+ int start = opStart(src);
Node dst = _internals.get(src);
- int target = _opStart[dst._nid];
- ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start);
+ // If function is entirely dead, only the function pointer remains
+ // and, it can only be used to test against zero or equals to
+ // another function pointer... i.e., there Is No Code Here.
+ int target = dst == null ? start : opStart(dst);
+ ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
}
}
// --------------------------------------------------
// Write the constant pool into the BAOS and optionally patch locally
- void writeConstantPool( BAOS bits, boolean patch ) {
+ void writeConstantPool( BAOS bits, boolean ro ) {
padN(16,bits);
// radix sort the big constants by alignment
Ary<Relo>[] raligns = new Ary[5];
for( Node op : _bigCons.keySet() ) {
Relo relo = _bigCons.get(op);
+ // non-constant structs in the r/w data, everything else in r/o data
+ if( (relo._t instanceof TypeStruct ts && !ts.isConstant()) == ro )
+ continue;
int align = relo._t.alignment();
Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>(Relo.class)) : raligns[align];
relos.add(relo);
@@ -459,7 +523,7 @@ public class Encoding {
HashMap<Type,Integer> targets = new HashMap<>();
// By alignment
- for( int align = 4; align >= 0; align-- ) {
+ for( int align = raligns.length-1; align >= 0; align-- ) {
Ary<Relo> relos = raligns[align];
if( relos == null ) continue;
for( Relo relo : relos ) {
@@ -469,30 +533,37 @@ public class Encoding {
targets.put(relo._t,target = bits.size());
// Write constant into constant pool
switch( relo._t ) {
- case TypeTuple tt -> cpool(align,bits,tt);
- case TypeStruct ts -> cpool(bits,ts);
- // Simple primitive (e.g. larger int, float)
+ case TypeTuple tt -> throw Utils.TODO("no tuples here, use structs instead");
+ case TypeStruct ts -> addStruct(bits,ts);
+ // Simple primitive (e.g. larger int, float, function ptr)
default -> addN(align,relo._t,bits);
}
}
- // Record target address and opcode start
+ // Record target address and opcode start.
+ // Target is relative to the cpool/sdata start.
relo._target = target;
- relo._opStart= _opStart[relo._op._nid];
- // Go ahead and locally patch in-memory
- if( patch )
- ((RIPRelSize)relo._op).patch(this, relo._opStart, _opLen[relo._op._nid], relo._target - relo._opStart);
+ relo._opStart= opStart(relo._op);
}
}
+
}
- // Constant tuples put all entries at same alignment
- private void cpool(int align, BAOS bits, TypeTuple tt) {
- for( Type tx : tt._types )
- addN(align,tx,bits);
+ // Emit a single scalar as bits
+ static void addN( int log, Type t, BAOS bits ) {
+ long x = switch( t ) {
+ case TypeInteger ti -> ti.value();
+ case TypeFloat tf -> log==3
+ ? Double.doubleToRawLongBits( tf.value())
+ : Float.floatToRawIntBits((float)tf.value());
+ case TypeFunPtr tfp -> 0; // These need to be relocated
+ case TypeMemPtr tmp -> 0; // These need to be relocated
+ default -> { if( t==Type.NIL ) yield 0; else throw Utils.TODO(); }
+ };
+ addN(log,x,bits);
}
// Structs use internal field layout
- private void cpool( BAOS bits, TypeStruct ts ) {
+ private void addStruct( BAOS bits, TypeStruct ts ) {
// Field order by offset
int[] layout = ts.layout();
int off=0; // offset in the struct
@@ -507,7 +578,7 @@ public class Encoding {
off += ((TypeConAry)f._t).len();
} else {
int log = f._t.log_size();
- addN(log,f._t,bits);
+ addN(log,f._t.isConstant() ? f._t : f._t.makeZero(),bits);
off += 1<<log;
}
}
@@ -522,14 +593,14 @@ public class Encoding {
void patchGlobalRelocations() {
for( Node src : _externals.keySet() ) {
- int start = _opStart[src._nid];
+ int start = opStart(src);
String dst = _externals.get(src);
int target = switch( dst ) {
case "calloc" -> SENTINEL_CALLOC;
case "write" -> SENTINEL_WRITE ;
default -> throw Utils.TODO();
};
- ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start);
+ ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
}
}
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
new file mode 100644
index 00000000..b0060024
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
@@ -0,0 +1,171 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.util.*;
+import java.util.HashMap;
+import java.util.Arrays;
+import java.util.Objects;
+
+
+// This utility 2-way maps some *global* program feature to a local dense
+// index, suitable for packing in BitSets. The mapping takes a global value -
+// always a {source file/class} name and an order number counting up per
+// feature in the same file. The dense local index can be different in
+// different compilations.
+
+// The global info allows loading the same remote class info from different
+// paths and aligning them. Example: Compiling A loads pre-compiled B and C,
+// both of which load pre-compiled D. The 2 different D loads unify via this
+// global info.
+
+// These mappings are all trivial identities when compiling one file; they
+// only become complex when loading seperately compiled code.
+
+public class GlobalBits {
+ public static final int RESERVED = 2;
+ private final HashMap<String,int[]> _clzOrder2Local = new HashMap<>();
+ private final Ary<String> _local2Clz = new Ary<>(String.class);
+ private final AryInt _local2Order = new AryInt();
+ // Local dense index
+ private int _local;
+
+ GlobalBits() {
+ _local = RESERVED;
+ // Reserved null-class bits are global identities and are never
+ // remapped.
+ int[] xs = new int[RESERVED];
+ xs[0] = RESERVED; // Per-class next order counter
+ for( int i=0; i<RESERVED; i++ ) {
+ if( i > 0 ) xs[i] = i;
+ _local2Clz .setX(i,null);
+ _local2Order.setX(i,i);
+ }
+ _clzOrder2Local.put(null,xs);
+ }
+
+ // Get a next local index for this clz
+ int next( String clz ) { return next(clz,-1); }
+
+ // Given a clz and order, return an existing local index or make a new
+ // mapping and return that.
+ int next(String clz, int order ) {
+ // Get the per-clz Order->Local mapping
+ int[] xs = _clzOrder2Local.get(clz);
+ if( xs == null )
+ xs = new int[]{RESERVED,1}; // Make, if it does not exist
+ // Caller does not have an order in mind, just take the next one stored
+ // in xs[0]
+ if( order == -1 )
+ order = xs[0]++;
+ // Order exists from prior compilation, but not recorded locally
+ else if( order >= xs[0] )
+ xs[0] = order+1;
+ // Grow as needed
+ while( xs.length <= order )
+ xs = Arrays.copyOf(xs,xs.length*2);
+ // Have a local mapping already?
+ if( xs[order] != 0 )
+ return xs[order];
+ int local = _local++;
+ bind(local,clz,order);
+ return local; // The dense local index
+ }
+
+ // Bind a specific dense local index to {clz,order}. Used by both fresh
+ // allocation and deserialization, where local indexes must not compact
+ // across holes because XInt bitsets are serialized against this space.
+ private void bind( int local, String clz, int order ) {
+ if( _local <= local )
+ _local = local+1;
+ _local2Clz .setX(local,clz );
+ _local2Order.setX(local,order);
+
+ // Inline-only fidx holes are represented as {null,0}. They need the
+ // reverse mapping above, but no forward map because xs[0] is the
+ // per-class next-order counter.
+ if( clz==null && order==0 )
+ return;
+
+ int[] xs = _clzOrder2Local.get(clz);
+ if( xs == null )
+ xs = new int[]{RESERVED,1};
+ if( order >= xs[0] )
+ xs[0] = order+1;
+ while( xs.length <= order )
+ xs = Arrays.copyOf(xs,xs.length*2);
+ _clzOrder2Local.put(clz,xs);
+ xs[order] = local;
+ }
+
+ // A file-local function that will inline, and never write to disk
+ public int nextInline( ) {
+ int local = _local++;
+ _local2Clz .setX(local,null);
+ _local2Order.setX(local,0);
+ return local;
+ }
+ // Get a next local index for the same clz as this old index
+ public int next( int old ) { return next(_local2Clz.at(old)); }
+
+ // Map a file-local dense index to this compilation unit's local dense index.
+ public int map( GlobalBits fileBits, int fileLocal ) {
+ if( fileLocal >= fileBits._local2Clz.size() )
+ throw new ArrayIndexOutOfBoundsException(""+fileLocal+" >= "+fileBits._local2Clz.size());
+ return findOrNext(fileBits._local2Clz.at(fileLocal), fileBits._local2Order.at(fileLocal));
+ }
+
+ // Map a global {clz,order} to a local one; keep any existing mapping or
+ // make a new one as needed.
+ private int findOrNext(String clz, int order ) {
+ // Reserved null-class identities are not represented in xs[order],
+ // because xs[0] is the next-order counter.
+ if( clz==null && order < _local2Clz.size() && _local2Order.at(order)==order && _local2Clz.at(order)==null )
+ return order;
+ int[] xs = _clzOrder2Local.get(clz);
+ if( xs != null && order < xs.length ) {
+ int local = xs[order];
+ if( local < _local2Clz.size() && _local2Order.at(local)==order && Objects.equals(_local2Clz.at(local),clz) )
+ return local;
+ }
+ return next(clz,order);
+ }
+
+ // Write out to BAOS enough bits to unwind the local back to global index
+ void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ // Unwind a local index to the clz.
+ baos.packed2(_local2Clz.size());
+ for( int i=RESERVED; i<_local2Clz.size(); i++ ) {
+ String clz = _local2Clz.at(i);
+ baos.packed2(clz==null ? 0 : strs.get(clz));
+ baos.packed2( _local2Order.at(i) );
+ }
+ }
+
+ void gather( HashMap<String,Integer> strs ) {
+ for( int i=RESERVED; i<_local2Clz.size(); i++ )
+ Serialize.gather(strs,_local2Clz.at(i));
+ }
+
+ // Read in the BAIS bits needed to make a file-local -> global mapping
+ static GlobalBits packed( BAOS bais, String[] strs ) {
+ var gb = new GlobalBits();
+ int n = bais.packed2();
+ for( int i=RESERVED; i<n; i++ ) {
+ int sx = bais.packed2();
+ String clz = strs[sx];
+ if( clz.isEmpty() ) clz = null;
+ int order = bais.packed2();
+ gb.bind(i,clz,order);
+ }
+ return gb;
+ }
+
+ @Override public String toString() {
+ SB sb = new SB().p('[');
+ for( int i=0; i<_local2Clz.size(); i++ ) {
+ if( i > 0 ) sb.p(',');
+ sb.p(i).p(" <-> ").p(_local2Clz.at(i)).p('#').p(_local2Order.at(i));
+ }
+ return sb.p(']').toString();
+ }
+
+}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index d9c7a6ad..5d8ed03c 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -24,10 +24,10 @@ public abstract class GlobalCodeMotion {
schedEarly(code);
// Break up shared global constants by functions
- breakUpGlobalConstants(code._start);
+ breakUpGlobalConstants(code._start );
code._visit.clear();
- schedLate (code);
+ schedLate(code);
}
// Post-Order of CFG
@@ -45,45 +45,91 @@ public abstract class GlobalCodeMotion {
// Break up shared global constants by functions
private static void breakUpGlobalConstants( Node start ) {
+
// For all global constants
for( int i=0; i< start.nOuts(); i++ ) {
Node con = start.out(i);
- if( con instanceof MachNode mach && mach.isClone() ) {
- breakUpGlobalConstants(con);
- // While constant has users in different functions
- while( true ) {
- // Find a function user, and another function
- FunNode fun = null;
- boolean done=true;
- for( Node use : con.outs() ) {
- FunNode fun2 = use instanceof ReturnNode ret ? ret.fun() : use.cfg0().fun();
- if( fun==null || fun==fun2 ) fun=fun2;
- else { done=false; break; }
- }
- // Single function user, so this constant is not shared
- if( done ) {
- if( con.in(0)==start ) i--;
- con.setDef(0,fun);
- break;
- }
- // Move function users to a private constant
- Node con2 = mach.copy(); // Private constant clone
- con2._inputs.set(0,null); // Preserve edge invariants from clone
- con2.setDef(0,fun);
- // Move function users to this private constant
- for( int j=0; j<con._outputs._len; j++ ) {
- Node use = con.out(j);
- FunNode fun2 = use.cfg0().fun();
- if( fun2==fun ) {
- use.setDef(use._inputs.find(con),con2);
- j--;
- }
- }
+ // Instruction selection can expand one shared TypeNode constant
+ // into a constant-valued machine expression. Split either form
+ // before scheduling, since separate functions have no CFG LCA.
+ if( !(con instanceof CFGNode) &&
+ ((con._type.isHighOrConst() && con.isConst()) ||
+ con instanceof MachNode mach && mach.isClone() && con._type.isConstant()) ) {
+ breakUpGlobalConstantSingle( con );
+ i--; // Removed a global constant, re-run same index
+ }
+ }
+ }
+
+
+ private static void breakUpGlobalConstantSingle( Node con ) {
+ // While constant has users in different functions
+ while( true ) {
+ // Find a function user, and another function
+ FunNode fun = null;
+ boolean done=true;
+ for( Node use : con.outs() ) {
+ if( use == null ) continue;
+ FunNode fun2 = useFun(use);
+ if( fun2==null || !CodeGen.CODE.owns(fun2) ) continue;
+ if( fun==null || fun==fun2 ) fun=fun2;
+ else { done=false; break; }
+ }
+ // Single function user, so this constant is not shared
+ if( done ) {
+ con.setDef(0,fun);
+ return;
+ }
+ // Move function users to a private constant
+ Node con2 = con.copy(); // Private constant clone
+ // Node copies made through Node(Node) carry unregistered inputs,
+ // while some machine copies construct fresh registered edges.
+ // Detach correctly in either case before pinning to the function.
+ Node old = con2.in(0);
+ if( old != null && old._outputs.find(con2) != -1 )
+ con2.setDef(0,null);
+ else
+ con2._inputs.set(0,null);
+ con2.setDef(0,fun);
+ // Move function users to this private constant
+ for( int j=0; j<con._outputs._len; j++ ) {
+ Node use = con.out(j);
+ if( use == null ) continue;
+ FunNode fun2 = useFun(use);
+ if( fun2==null || !CodeGen.CODE.owns(fun2) ) continue;
+ if( fun2==fun ) {
+ use.setDef(use._inputs.find(con),con2);
+ j--;
}
}
}
}
+ private static FunNode useFun(Node use) {
+ if( use instanceof ReturnNode ret )
+ return ret.fun();
+ if( use instanceof ParmNode parm )
+ return parm.fun();
+ CFGNode cfg = use.cfg0();
+ if( cfg==null ) return null;
+ FunNode fun = cfg.fun();
+ // Use itself is not in a function... which makes it a 2-part
+ // constant such as happens on some chips where large constants have
+ // to be built up in parts. PtrToInt followed by the array-body Add is
+ // another zero-code/global-value chain, so walk through any number of
+ // single-use pieces until reaching the function-owned use.
+ if( fun==null ) {
+ do {
+ assert use.nOuts()==1;
+ use = use.out(0);
+ cfg = use.cfg0();
+ fun = cfg==null ? null : cfg.fun();
+ } while( fun==null );
+ }
+ return fun;
+ }
+
+
// ------------------------------------------------------------------------
// Visit all nodes in CFG Reverse Post-Order, essentially defs before uses
// (except at loops). Since defs are visited first - and hoisted as early
@@ -252,6 +298,8 @@ public abstract class GlobalCodeMotion {
// Block of use. Normally from late[] schedule, except for Phis, which go
// to the matching Region input.
private static CFGNode use_block(Node n, Node use, CFGNode[] late) {
+ if( use instanceof ParmNode parm && n.cfg0().fun()==parm.fun() )
+ return late[use._nid];
if( !(use instanceof PhiNode phi) )
return late[use._nid];
CFGNode found=null;
@@ -286,6 +334,12 @@ public abstract class GlobalCodeMotion {
lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, st );
}
break; // Loads do not cause anti-deps on other loads
+ case EscapeNode esc:
+ if( load._alias == esc.fld()._alias ) {
+ assert late[mem._nid] != null;
+ lca = anti_dep( load, late[mem._nid], mem.cfg0(), lca, mem );
+ }
+ break;
case CallNode call:
assert late[call._nid]!=null;
lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call);
diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index 6dd6fbf1..1d88597c 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -68,9 +68,9 @@ abstract public class IFG {
// Reset all to empty
resetBBLiveOut();
resetIFG();
+ WORK.clear();
// Last block has nothing live out
- assert WORK.isEmpty();
for( CFGNode bb : alloc._code._cfg )
if( bb.blockHead())
WORK.push( bb );
@@ -231,7 +231,9 @@ abstract public class IFG {
selfConflict(alloc,n,lrg,TMP.get(lrg));
}
private static void selfConflict(RegAlloc alloc, Node n, LRG lrg, Node prior) {
- if( prior!=null && prior != n ) {
+ // Casts never produce a *new* value, just lift our understanding of the value.
+ // They do not force self-conflicts.
+ if( prior!=null && prior != n && !(n instanceof CheckCastNode) ) {
lrg.selfConflict(prior);
lrg.selfConflict(n);
alloc.fail(lrg); // 2 unrelated values live at once same live range; self-conflict
@@ -402,6 +404,7 @@ abstract public class IFG {
swap(color_stack,sptr,bidx); // Pick best at sptr
}
+ // Returns true if 'lrg' is better than 'best'
private static boolean betterLRG( LRG best, LRG lrg ) {
// If single-def varies, keep the not-single-def
if( best.size1() != lrg.size1() )
@@ -409,6 +412,10 @@ abstract public class IFG {
// If hasSplit varies, keep the hasSplit
if( best.hasSplit() != lrg.hasSplit() )
return lrg.hasSplit();
+ // If both are single register (so both sizes are 1),
+ // return a multi-use over a multi-def
+ if( best.size1() )
+ return !best._multiUse && lrg._multiUse;
// Keep large register count
return best.size() < lrg.size();
}
@@ -438,7 +445,7 @@ abstract public class IFG {
private static int pickRiskyScore( LRG lrg ) {
// Pick single-def clonables that are not right next to their single-use.
// Failing to color these will clone them closer to their uses.
- if( !lrg._multiDef && lrg._machDef.isClone() ) {
+ if( !lrg._multiDef && lrg._machDef.isClone() && lrg._machUse != null ) {
Node def = ((Node)lrg._machDef);
Node use = ((Node)lrg._machUse);
CFGNode cfg = def.cfg0();
@@ -458,7 +465,7 @@ abstract public class IFG {
// TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
// and "area" for benefit
- return 1000;
+ return 1000 + (lrg._multiUse ? -100 : 0);
}
private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
diff --git a/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/src/main/java/com/seaofnodes/simple/codegen/LRG.java
index 009ddcdb..3a4596fe 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/LRG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/LRG.java
@@ -56,6 +56,10 @@ public class LRG {
// Adjacent Live Range neighbors. Only valid during coloring
Ary<LRG> _adj;
+ // Function fully containing the live range; used to find the stack frame
+ // layout, for spill op offsets.
+ final public FunNode _fun;
+
public int nadj() { return _adj==null ? 0 : _adj._len; }
void addNeighbor(LRG lrg) {
@@ -78,7 +82,7 @@ public class LRG {
// More registers than neighbors
boolean lowDegree() { return nadj() < _mask.size(); }
- LRG( short lrg ) { _lrg = lrg; _reg = -1; }
+ LRG( short lrg, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; }
boolean leader() { return _leader == null; }
@@ -114,6 +118,7 @@ public class LRG {
}
// Union `this` and `lrg`, folding together all stats.
private LRG _union( LRG lrg ) {
+ assert _fun == lrg._fun;
// Set U-F leader
lrg._leader = this;
// Fold together stats
diff --git a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
index 2415203d..e0b59395 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
@@ -1,6 +1,8 @@
package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
// In-memory linking
@@ -8,15 +10,23 @@ public class LinkMem {
final CodeGen _code;
LinkMem( CodeGen code ) { _code = code; }
- public CodeGen link() {
- Encoding enc = _code._encoding;
-
+ public CodeGen link(Encoding enc) {
// Patch external calls internally
enc.patchGlobalRelocations();
- // Write any large constants into a constant pool; they
- // are accessed by RIP-relative addressing.
- enc.writeConstantPool(enc._bits,true);
+ // Place constant pool after code, static data after constants
+ int code = 0; // Code start
+ int cpool = (enc._bits .size() + code + 15) & -16; // pad to 16
+ int sdata = (enc._cpool.size() + cpool + 15) & -16;
+
+
+ // Patch local ops, e.g. loading float constants from the constant pool
+ for( Node op : enc._bigCons.keySet() ) {
+ Encoding.Relo relo = enc._bigCons.get(op);
+ boolean ro = !(relo._t instanceof TypeStruct ts) || ts.isConstant();
+ int target = relo._target+(ro ? cpool : sdata);
+ ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart);
+ }
return _code;
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
index ddf5a679..b6b3a22c 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
@@ -28,7 +28,7 @@ public abstract class ListScheduler {
}
void free() { _n = null; FREE.push(this); }
- static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); }
+ static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CFGNode) ? n.in(0) : n); }
private XSched init(CFGNode bb, Node n) {
_n = n;
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 2766414f..91d07d57 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -6,28 +6,42 @@ import com.seaofnodes.simple.type.TypeFunPtr;
abstract public class Machine {
// Human readable machine name. Something like "x86-64" or "arm" or "risc5"
public abstract String name();
+
// Default opcode size for printing; 4 bytes for most 32-bit risc chips
public int defaultOpSize() { return 4; }
+
// Human-readable name for a register number, e.g. "RAX" or "R0"
public abstract String[] regs();
- // Create a split op; any register to any register, including stack slots
- public abstract SplitNode split( String kind, byte round, LRG lrg);
+
+ // Create a split op; any register to any register, including stack slots.
+ // The FunNode holds the stack layout after RegAlloc, which in turn
+ // dictates the stack slot (the frame offset) for stack splits.
+ public abstract SplitNode split( LRG lrg, String kind, byte round );
+
// List of caller-save registers, as a 64bit mask
public abstract long callerSave();
+ public long callerSave(String conv) { return callerSave(); }
+
// List of never-save registers, e.g. RSP or a ZERO register if you have one
public abstract long neverSave();
+
// Call Argument Mask. Passed in the function signature and argument
// number (2-based; 0 is for control and 1 for memory). Also passed in a 0
// for the function itself, or for *outgoing* calls, the maximum stack slot
// given to the incoming function arguments (stack slots reserved for
// incoming arguments).
public abstract RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot);
+ public RegMask callArgMask(TypeFunPtr tfp, int arg, int maxArgSlot, String conv) { return callArgMask(tfp,arg,maxArgSlot); }
+
// Return register mask, based on signature (GPR vs FPR)
public abstract RegMask retMask(TypeFunPtr tfp);
+
// Return PC register
public abstract int rpc();
+
// Return a MachNode unconditional branch
public abstract CFGNode jump();
+
// Instruction select from ideal nodes
public abstract Node instSelect( Node n );
@@ -93,5 +107,9 @@ Pre-alloc Post-Alloc SP+48 RPC
// shadow slots if defined in the ABI, even if all arguments are passed in
// registers.
public abstract short maxArgSlot(TypeFunPtr tfp);
+ public short maxArgSlot(TypeFunPtr tfp, String conv) { return maxArgSlot(tfp); }
+
+ // Calling convention used for external C symbols.
+ public String cCallingConv(String callingConv) { return callingConv; }
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Module.java b/src/main/java/com/seaofnodes/simple/codegen/Module.java
new file mode 100644
index 00000000..a5d85dbc
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/Module.java
@@ -0,0 +1,98 @@
+package com.seaofnodes.simple.codegen;
+
+
+/***
+ Modules!?!?!?
+
+ Goal: under no circumstances can you load from an uninitialized variable
+ (although you can see default inits for fields that have defaults).
+ Goal: nested tree-structured name spaces, to control complexity.
+ Goal: field decl order within a file is fairly flexible.
+
+ A Module a root-level SMP file, and the full file path must be provided on
+ the command line. All other Simple structs are relative to this file.
+
+ Example: "simple /user/Dave/hello.smp"
+
+ Defines a module "hello" in directory "/user/Dave", compiles and produces
+ a `hello` or `hello.exe` output file, and runs it.
+
+ A SMP file has some <finit> file-init code which is static-global.
+ This code is executed on "first touch" of the file (defined below).
+
+ Example: hello.smp contains:
+ sys.io.p("Hello World!");
+ On first-touch, will print "Hello World!".
+
+ A SMP file is a lexical scope, and code inside it can reference those names.
+
+ Example: hello.smp contains:
+ val greet = "Hello World!"; // Declare file-scoped variable
+ sys.io.p(greet); // Reference file-scoped variable
+
+ Static code is NOT allowed to have forward-references AND be executed
+ before the <finit> finishes initializing all variables. After var
+ initialization, a singleton instance of the implied file struct is made,
+ and then <finit> code can call anything.
+
+ Example:
+ sys.io.p(greet); // Illegal forward reference
+ val greet = "Oops!";
+
+ Example:
+ val delay = { -> greet }; // Legal forward ref, but cannot execute (yet)
+ val greet = "Hello World!"; // Ref is resolved
+ sys.io.p(delay()); // Allowed to call delay()
+ val nprimes = { N ->
+ // compute number of primes < N
+ }
+ // Can otherwise execute any code in a <finit>
+ sys.io.p(nprimes(100));
+
+ Inside a directory, you can have sibling SMP files:
+ File hello.smp contains:
+ sys.io.p(I9A.GREETING); // File hello references sibling file I9A static globals
+ File I9A.smp contains:
+ val GREETING = translate("Hello World!");
+ val translate = { str msg -> ... };
+
+ <finit> code cannot reference sibling files (nor child files, see below)
+ until all static variables are initialized. This prevents cyclic
+ initialization bugs, where file A uses vars from file B and vice-versa.
+
+ File Chicken.smp contains:
+ val lay = Egg.hatch; // Illegal, defining var 'lay' after calling sibling Egg
+ File Egg.smp contains:
+ val hatch = Chicken.lay; // Illegal
+
+ You can make cross references, if they get delayed until after all fields
+ are declared.
+
+ File Chicken.smp contains:
+ int lay = 2;
+ lay = Egg.egg;
+
+ File Egg.smp contains:
+ int egg = 3;
+ egg = Chicken.lay;
+
+ The problem here, of course, is that you need to touch one of Chicken or
+ Egg first.
+
+
+ Inside a SMP file, you can define structs; structs are also a lexical
+ scope. Bare code in the struct is part of the struct's <init> and only
+ runs when an instance is created. Structs do not have static <clinit> (nor
+ <finit>). Structs <init> cannot have any forward references, and locally
+ declared functions cannot have both forward references and be executed (or
+ passed along to a possibly-executing function).
+
+ In the same directory as a SMP file, Simple allows other directories with
+ names matching SMP files. Inside these dirs can be more SMP files that are
+ lexically nested, similar to embedded structs.
+
+
+*/
+
+public abstract class Module {
+}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index f8ce1e54..b88067c4 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -1,10 +1,12 @@
package com.seaofnodes.simple.codegen;
-import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.Utils;
+
import java.util.Arrays;
+import java.util.BitSet;
// Optimistic Algorithm
abstract public class Opto {
@@ -24,34 +26,10 @@ abstract public class Opto {
// get a fairly precise set of Call Graph edges between CallNodes and
// FunNodes.
public static void opto(CodeGen code) {
-
- // Pessimistic peephole optimization on a worklist - this completes
- // whatever peepholes can run after parsing.
- code._iter.iterate(code);
-
// Reset Types before running the worklist algorithm
Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only
resetToTop(code,oldTypes);
-
- // The whole-world assumption: we use it if compiling `main`, and not otherwise.
- // The assumption is no unknown callers into functions:
- // - during or past an Opto phase (but not before, since parse through
- // pessimistic must assume somebody calls an unknown function which
- // calls everybody).
- // - Have a `main` to start tracing the Call Graph from
- // - - True for most test cases
- // - - False for lacking a main; we assume the linker will call all public functions
-
- // True if we can make the "whole world" assumption, which means we are
- // compiling the program entry: `main`.
- boolean wholeWorld = code.link(code._main) != null;
-
-
- // If wholeWorld, remove the Start inputs to functions - they are not
- // called unless they get linked (hence go dead and can be removed).
- // If not wholeWorld, keep only public methods (and not the std lib).
- unlinkStart(wholeWorld,code);
-
+ unlinkStart(code);
// OPTIMISTIC INTERPROCEDURAL SCCP
sccp(code, oldTypes);
@@ -60,6 +38,11 @@ abstract public class Opto {
// peepholes can make more progress.
moveChangesToWorklist(code, oldTypes);
+ // SCCP can leave a recursive numeric component at BOTTOM with no
+ // arithmetic mode selected. Resolve the whole component from all
+ // available int/float evidence before IterPeeps consumes the types.
+ resolveNumericModes(code);
+
// Progress with improved types. E.g. since we have a full Call Graph
// here, we might do better inlining.
code._iter.iterate(code);
@@ -73,15 +56,23 @@ abstract public class Opto {
// Propagate field size constants
code._iter.iterate(code);
- // Throw away the Call Graph
- // 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.
- removeCallGraph(code);
+ // Retain surviving functions in their CompUnits for later code
+ // emission. Start inputs are semantic: they mean unknown callers.
+ for( FunNode fun : code._linker )
+ if( fun != null && !fun.isDead() && !fun._type.isHigh() )
+ keepInCompUnit(code,fun);
+
+ for( CompUnit cu : code._compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem != null ) mem.unkeep();
+ }
// To help with testing, sort StopNode Returns by NID
- Arrays.sort(code._stop._inputs._es,0,code._stop.nIns(),(x,y) -> x._nid - y._nid );
+ for( Node stop : code._stop._inputs )
+ Arrays.sort(stop._inputs._es,0,stop.nIns(),
+ (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
+ (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
}
// Reset Types before running the worklist algorithm
@@ -90,9 +81,6 @@ abstract public class Opto {
oldTypes.setX(x._nid,x._type); // Record original types for asserts
x._type = Type.TOP; // Reset all Nodes to TOP
code._iter.add(x); // Visit everybody at least once; most Nodes produce better than TOP
- // Unlink all existing (conservative) linkages.
- if( x instanceof CallNode call )
- call.unlink_all();
return null;
} );
}
@@ -101,41 +89,49 @@ abstract public class Opto {
// If wholeWorld, remove the Start inputs to functions - they are not
// called unless they get linked (hence go dead and can be removed). If
// not wholeWorld, keep only public methods (and not the std lib).
- private static void unlinkStart(boolean wholeWorld, CodeGen code) {
- for( FunNode fun : code._linker ) {
- if( fun != null && !fun.isDead() && !funIsPublic(code,wholeWorld,fun) ) {
- assert fun.in(1)==code._start; // Start always in slot 1
- fun.removeDeadPath(1);
- // By same logic, remove stop->return linkage, without
- // remove the (now dead) ReturnNode; it awaits in limbo
- // for the opto phase to link to some valid call
- code._stop._inputs.del(code._stop._inputs.find(fun.ret()));
- fun.ret().delUse(code._stop);
- }
+ private static void unlinkStart(CodeGen code) {
+ // Keep the Start & Start.ProjMem alive even as we remove all uses
+ for( CompUnit cu : code._compunits.values() ) {
+ ProjNode mem = cu._start.proj(1);
+ if( mem!=null ) mem.keep();
}
- }
- // Function is public (callable from Start directly).
- // - Always true for main
- // - Never true for stdlib, or anonymous functions
- // - Named functions use `!wholeWorld`
- private static boolean funIsPublic( CodeGen code, boolean wholeWorld, FunNode fun ) {
- // Always true for main
- if( fun.sig().fidx() == code._main.fidx() ) return true;
- // Never true for stdlib or anonymous functions
- if( fun._name == null || fun._name.startsWith("sys.") )
- return false;
- // Named functions (and not stdlib) are callable if not whole-world.
- return !wholeWorld;
+ // Iterate the Stop-of-Stops
+ for( Node stop : code._stop._inputs ) {
+ for( int i=0; i<stop.nIns(); i++ ) {
+ ReturnNode ret = (ReturnNode)stop.in(i);
+ 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()) &&
+ !code.publicFIDX(fun.sig().fidx()) &&
+ fun.in(1) instanceof StartNode ) {
+ fun.removeDeadPath(1);
+ // Unhook from Stop without treating the ReturnNode as
+ // DEAD. It stays in limbo until Opto links when a caller
+ // is discovered (or not, and the function is really dead).
+ stop._inputs.del(i--);
+ ret.delUse(stop);
+ }
+ // Unlink the existing conservative call linkages.
+ for( int j=1; j<fun.nIns(); j++ )
+ if( fun.in(j) instanceof CallNode call )
+ call.unlink(fun,j--);
+ }
+ }
}
// As part of building a CallGraph, when opto finds a function ptr flowing
// into a Call, link the Call and Fun.
private static void linkCG(CodeGen code, TypeFunPtr tfp, CallNode call) {
if( tfp.nargs() != call.nargs() ) return; // Error calls hit this
- for( long fidxs=tfp.fidxs(); fidxs!=0; fidxs=TypeFunPtr.nextFIDX(fidxs) ) {
- int fidx = Long.numberOfTrailingZeros(fidxs);
- FunNode fun = code.link(fidx);
+ int[] fidxs = tfp.fidxs();
+ for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
+ // unlinkStart deliberately leaves uncalled functions in a
+ // temporarily dead limbo. A newly discovered call revives them;
+ // do not use the cleanup lookup which removes dead linker entries.
+ FunNode fun = code.lookupFun(fidx);
// null here means an external function; i.e. this Call
// calls to an outside library and all its arguments escape.
if( fun != null && !call.linked(fun) ) {
@@ -146,6 +142,55 @@ abstract public class Opto {
}
}
+ // Start added some FIDXs, the same functions now can be reached from start
+ // and called by anybody. Add the CG edges.
+ private static void linkStart( CodeGen code, TypeTuple tt ) {
+ TypeMem tmem = (TypeMem)tt._types[1];
+ int[] fidxs = tmem._escFs;
+ if( XInt.isHigh(fidxs) ) {
+ // A scalar BOTTOM can flow into memory while compiling an error
+ // program. It exposes all public functions, not private fields.
+ for( FunNode fun : code._linker )
+ if( fun != null && code.publicFIDX(fun.sig().fidx()) )
+ linkStart(code,fun);
+ return;
+ }
+ for( int fidx = XInt.next(fidxs,0); fidx >=0; fidx = XInt.next(fidxs,fidx) ) {
+ FunNode fun = code._linker.atX(fidx);
+ if( fun==null ) assert code._externFunc.containsKey(fidx);
+ else linkStart(code,fun);
+ }
+ }
+
+ // Function is now reachable by unknown callers through Start. The
+ // default inputs must therefore use conservative declared types.
+ private static void linkStart( CodeGen code, FunNode fun ) {
+ assert !fun.isDead();
+ StartCUNode start = fun._compunit._start;
+ if( fun.nIns() < 2 || fun.in(1) != start ) {
+ assert !start.isDead();
+ keepInCompUnit(code,fun);
+ // Function is reachable by any *remote* caller who gets the pointer!
+ fun.insertDef(1,start);
+ code._iter.add(fun);
+ for( Node p : fun._outputs )
+ if( p instanceof ParmNode parm ) {
+ Node defalt = parm._idx==1 ? start.proj(1) : ConstantNode.seed(parm._declaredType).peephole();
+ parm.insertDef(1,defalt);
+ code.add(parm);
+ }
+ }
+ }
+
+ // Keep a surviving function rooted in its compilation unit without
+ // claiming that it has unknown callers.
+ private static void keepInCompUnit(CodeGen code, FunNode fun) {
+ StopCUNode stop = fun._compunit._stop;
+ if( stop._inputs.find(fun.ret()) == -1 )
+ code.add(stop).addDef(fun.ret());
+ }
+
+
// The core SCCP algorithm
private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
// Iter workList is empty at this point
@@ -157,108 +202,159 @@ abstract public class Opto {
count++;
Type oval = n._type, nval = n.compute();
if( oval == nval ) continue;
- assert oval.isa(nval); // Types start high and always fall
- Type pesiVal = oldTypes.at(n._nid);
- assert nval.isa(pesiVal); // Never fall worse than the pessimistic pass
+ assert oval.isa(nval) : "Non-monotonic SCCP: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" old="+oval+" new="+nval; // Types start high and always fall
+ Type pesiVal = oldTypes.atX(n._nid);
+ assert pesiVal==null || nval.isa(pesiVal) : "SCCP below pessimistic type: "+n.getClass().getSimpleName()+"#"+n._nid+" "+n+" pessimistic="+pesiVal+" new="+nval; // Never fall worse than the pessimistic pass
n._type = nval;
- // If a TFP adds a new function input to a call, link to the new Fun
+ // Now we have a series of stanzas where we lazily create the Call
+ // Graph - adding graph edges between every call site and called
+ // function, including external (unknown) callers. These edges are
+ // not in from the start because they are *too many*; we would get
+ // O(n^2) edges. So all along we have been treating them as
+ // "virtual" CFG edges - a FunNode taking a Start input is treated
+ // as if every call on the planet might call it. Now we make these
+ // CG edges *concrete*, adding them back as we discover that a call
+ // calls a particular function.
+
+ // If a TFP adds a new function input to a call, link to the new
+ // Fun. This adds new edges to the graph allowing argument values
+ // to flow from calls into functions. The added edges are
+ // effectively a refined Call Graph.
if( nval instanceof TypeFunPtr tfp ) {
for( Node use : n._outputs )
if( !use._type.isHigh() && use instanceof CallNode call && call.fptr() == n )
linkCG(code,tfp,call);
}
- // Link a Call which becomes alive
+ // Link a Call which becomes alive. Like above, adds new Call
+ // Graph edges to the graph.
if( n instanceof CallNode call && oval.isHigh() && !nval.isHigh() && call.fptr()._type instanceof TypeFunPtr )
linkCG(code,call.tfp(),call);
+ // If an otherwise-dead function pointer escapes, any future linked
+ // caller might find and call it. Force the function to be alive
+ // and called by Start.
+ if( n instanceof StartNode && n._type instanceof TypeTuple tt )
+ linkStart(code,tt);
+
+
// Since n._type changed, visit all output neighbors
code._iter.addAll(n._outputs);
n.moveDepsToWorklist(code._iter);
// Quadratic (expensive) small-step assert
- assert check(code);
+ assert worklistCheck(code);
}
}
- // Quadratic (expensive) small-step assert
- private static boolean check(CodeGen code) {
- code._start.walk( x -> {
+ // Quadratic (expensive) fixed-point assert
+ public static boolean fixedPointCheck(CodeGen code) {
+ return fixedPointCheck(code,code._stop);
+ }
+ public static boolean fixedPointCheck(CodeGen code, Node root) {
+ root.walk( x -> {
Type xval = x.compute();
- assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x ));
+ assert xval == x._type;
return null;
});
return true;
}
+ // Quadratic (expensive) small-step assert for SCCP, where changed nodes
+ // may already be on the worklist.
+ private static boolean worklistCheck(CodeGen code) {
+ code._midAssert = true;
+ Node root = code._start;
+ root.walk( x -> {
+ Type xval = x.compute();
+ assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x ))
+ : "Non-monotonic or missed SCCP work: "+x.getClass().getSimpleName()+"#"+x._nid+" "+x+" old="+x._type+" new="+xval;
+ return null;
+ });
+ code._midAssert = false;
+ return true;
+ }
+
// After SCCP add everything changed to the worklist and see if peepholes
// can make more progress.
private static void moveChangesToWorklist(CodeGen code, Ary<Type> oldTypes) {
code._start.walk( x -> {
assert x.compute() == x._type; // Hit the fixed point
assert x._nid >= oldTypes._len || x._type.isa(oldTypes.at(x._nid)); // Hit at least the bottom-up type
- code._iter.add(x);
+ code.add(x);
return null;
});
}
+ // Resolve arithmetic modes which are ambiguous only because they belong to
+ // a recursive component. Float wins because integer operands can be
+ // converted to float.
+ private static void resolveNumericModes(CodeGen code) {
+ final BitSet visited = new BitSet();
+ code._stop.walk(n -> {
+ if( n instanceof ModeNode modeNode && modeNode.mode()==0 ) {
+ visited.clear();
+ visited.set(n._nid); // Inspect inputs, not Bool's integer result
+ int mode = 0;
+ for( int i=1; i<n.nIns(); i++ )
+ mode |= numericEvidence(n.in(i),visited);
+ if( mode != 0 ) {
+ // If both evidence, FP wins
+ modeNode.setMode((byte)((mode&2)==2 ? 2 : 1)).init();
+ code.add(n);
+ }
+ }
+ return null;
+ });
+ }
+
+ // Gather numeric evidence through Phi and interprocedural return cycles.
+ // The visited set makes self- and mutually-recursive calls finite.
+ private static int numericEvidence(Node n, BitSet visited) {
+ if( n==null || visited.get(n._nid) ) return 0;
+ visited.set(n._nid);
+
+ Type t = n._type;
+ if( t instanceof TypeFloat ) return 2;
+ if( t instanceof TypeInteger ) return 1;
+ if( t!=Type.BOTTOM && !(t instanceof TypeTuple) )
+ return 0; // Something unrelated, like a ptr
+
+ int evidence = 0;
+ switch( n ) {
+ case ModeNode modeNode -> evidence = modeNode.mode();
+ case PhiNode phi -> {}
+ case ReturnNode ret -> {}
+ case ProjNode proj when proj._idx==2 && proj.in(0) instanceof CallEndNode cend -> {
+ for( int i=1; i<cend.nIns(); i++ )
+ evidence |= numericEvidence(cend.in(i),visited);
+ }
+ case ConvertNode convert -> {
+ Type dst = convert.dst();
+ evidence = dst instanceof TypeFloat ? 2 :
+ dst instanceof TypeInteger ? 1 : 0;
+ }
+ default -> { return 0; }
+ }
+ for( int i=1; i<n.nIns(); i++ )
+ evidence |= numericEvidence(n.in(i),visited);
+
+ return evidence;
+ }
+
+
// Freeze field sizes; do struct layouts; convert field offsets into
// constants.
private static void fieldSizes( CodeGen code ) {
for( int i=0; i<code._start.nOuts(); i++ ) {
Node use = code._start.out(i);
if( use instanceof ConFldOffNode off ) {
- TypeMemPtr tmp = (TypeMemPtr) Parser.TYPES.get(off._name);
- off.subsume( off.asOffset(tmp._obj) );
- i--;
- }
- }
- }
-
- // 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.
- private static void removeCallGraph(CodeGen code) {
- code._start.walk( x -> {
- // Unlink all existing (conservative) linkages.
- if( x instanceof CallNode call ) {
- for( Node y : call._outputs ) {
- // Keep function alive and hooked to Start so it gets
- // codegen'd. Function is callable by this call via
- // TFP, although perhaps not by any other means.
- if( y instanceof FunNode fun && !(fun.in(1) instanceof StartNode) ) {
- ParmNode rpc = fun.rpc();
- if( rpc==null ) // Ensure valid RPC
- rpc = makeRPC(fun);
- // Insert a hook to Start and Stop
- fun.insertDef(1,code._start);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode parm )
- parm.insertDef(1,ConstantNode.make(parm._type).peephole());
- code._stop.addDef(fun.ret());
- }
- }
- call.unlink_all();
+ Node con = off.asOffset();
+ if( con != null ) { // Can be null for trying to reference missing field
+ off.subsume( con );
+ i--;
}
- return null;
- } );
-
- }
-
- // Make a valid Parm 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.
- private static ParmNode makeRPC( FunNode fun ) {
- ParmNode rpc = new ParmNode("$rpc",0,fun.ret().rpc()._type,fun);
- for( int i=1; i<fun.nIns(); i++ ) {
- CallEndNode cend = ((CallNode)fun.in(i)).cend();
- rpc.addDef(ConstantNode.make(cend._rpc).peephole());
+ }
}
- fun.ret().setDef(3,rpc.init());
- return rpc;
}
-
-
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
new file mode 100644
index 00000000..191016be
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -0,0 +1,379 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.IterPeeps;
+import com.seaofnodes.simple.node.*;
+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.util.*;
+
+// Parse a given file, and all its dependencies
+public abstract class ParseAll {
+
+ // Worklist of symbols to search and files to parse
+ private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class);
+
+ private static final Ary<CompUnit> NEEDS_LOAD = new Ary<>(CompUnit.class);
+
+ // Return a compilation unit (cached or new). If new, it does not necessarily
+ // need to be compiled.
+ public static CompUnit makeCUnit( CodeGen code, CompUnit par, String name ) throws IOException {
+ return makeCUnit(code,par,name,null,false);
+ }
+
+ private static CompUnit makeCUnit( CodeGen code, CompUnit par, String name, File obj ) throws IOException {
+ return makeCUnit(code,par,name,obj,obj!=null);
+ }
+
+ private static CompUnit makeCUnit( CodeGen code, CompUnit par, String name, File obj, boolean external ) throws IOException {
+ // Slashed file name? Move leading parts into parent CUs.
+ int idx = name.indexOf("/");
+ if( idx >= 0 ) {
+ String head = name.substring(0,idx);
+ CompUnit sub = makeCUnit(code,par,head,external ? externalObj(code,par,head) : null,external);
+ return makeCUnit(code,sub,name.substring(idx+1).intern(),obj,external);
+ }
+
+ String fname = par==null ? name : (par._fname + "/" + name).intern();
+ CompUnit cunit = code._compunits.get(fname);
+ if( cunit != null ) return cunit;
+ String cname = par==null ? name : (par._cname + "." + name).intern();
+ code._compunits.put(fname, cunit = external ? new CompUnit(obj,par,fname,cname,name) : new CompUnit(par,fname,cname,name));
+ return cunit;
+ }
+
+ // Parse a top-level string, used for e.g. testing
+ static void parseSource( CodeGen code, String src ) {
+ parseSource(code,"Test",src);
+ }
+
+ // Parse a top-level string with an explicit compilation-unit name.
+ static void parseSource( CodeGen code, String fname, String src ) {
+ CompUnit cunit = new CompUnit(fname,src);
+ code._compunits.put(cunit._fname,cunit);
+ parseAll(code,cunit);
+ }
+
+ // module_root/.../src.smp is source file name.
+ static void parsePath(CodeGen code, String fname) {
+ try {
+ // Split and build the parent-path down to the source file.
+ CompUnit cu = makeCUnit( code, null, fname );
+ // Compile this CompUnit
+ parseAll(code,cu);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static void parseAll(CodeGen code, CompUnit cunit) {
+ WORK.clear(); // Cleanup from prior tests
+ NEEDS_LOAD.clear();
+ // Prime the pump with the first CompUnit
+ WORK.add(cunit);
+ // Work through all unparsed compilation units and parse
+ while( !WORK.isEmpty() ) {
+ CompUnit cu = WORK.remove(0);
+ // No source code? Means we do not need to parse, but instead need to load the symbols as-if we parsed
+ if( cu._src==null || cu._src.isEmpty() )
+ loadDeps(code,cu); // Load recursive dependents
+ else
+ parseOne( code, cu ); // Parse
+ }
+
+ // Load all pre-compiled CompUnits
+ for( CompUnit cu : NEEDS_LOAD )
+ loadCompUnit(code,cu);
+ if( !NEEDS_LOAD.isEmpty() )
+ code._stop.walk(n -> {
+ if( n instanceof CallNode )
+ code.add(n);
+ return null;
+ });
+
+ // Many types will have been declared cyclically either internally or
+ // across multiple compilation units.
+
+ // Close over all recursive types, and upgrade TYPES
+ Ary<TypeStruct> ary = new Ary<>(TypeStruct.class);
+ for( Type t : Parser.TYPES.values() )
+ if( t instanceof TypeStruct ts )
+ ary.add(ts);
+ TypeStruct[] tss = Type.closeOver(ary.asAry(),Parser.TYPES);
+ for( TypeStruct ts : tss )
+ Parser.TYPES.put(ts._name,ts);
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._ext==null )
+ cu._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cu._cname));
+
+ // Walk over all Nodes, and upgrade the internal constants to the
+ // closed-over types.
+ code._stop.walk( (Node n) -> {
+ n.upgradeType(Parser.TYPES);
+ if( n instanceof FRefNode fref ) {
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(fref._name);
+ if( clz == null )
+ return null;
+ // Class pointer:
+ TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
+ // Class pointer constant:
+ ConstantNode clzCon = code.con(clzptr);
+ // Replace and optimize
+ fref.addDef(clzCon);
+ code.add(fref);
+ }
+ // Adds sort by NIDs, which just got shuffled
+ if( n instanceof AddNode add )
+ code.add(add);
+ return null;
+ } );
+
+ // The complete public interface is now known. Keep this independent
+ // of later graph/linker liveness so SCCP computations remain local and
+ // monotonic.
+ code.freezePublicInterface();
+ }
+
+ // Parse one Simple source code file. Add all the FRefs produced to the worklist.
+ private static void parseOne( CodeGen code, CompUnit cunit ) {
+ cunit.setStart(code);
+ // Parse a source file, return missing external references
+ Ary<FRefNode> frefs = code.P.parse(cunit);
+
+ // Find all missing external references, or complain. This can trigger more parses.
+ while( !frefs.isEmpty() ) {
+ FRefNode fref = frefs.remove(0);
+ // Symbol not found in source code (or we would not be here).
+ // Search the module for the fref
+ try {
+ CompUnit xcunit = findCUnitModule(code,cunit,fref._name);
+ if( xcunit == null )
+ xcunit = findCUnitExternalSimple(code,fref._name);
+ if( xcunit != null ) {
+ // Replace the FRef with the discovered class name.
+ fref._name = Parser.addClzPrefix(xcunit._cname);
+ cunit.addDep(xcunit);
+ } else {
+ throw new RuntimeException("Undefined name '" + fref._name +"'");
+ }
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ addRequiredTypes(code,cunit);
+ }
+
+ private static void addRequiredTypes(CodeGen code, CompUnit cunit) {
+ LinkedHashSet<String> required = new LinkedHashSet<>();
+ code._stop.walk(n -> {
+ TypeStruct ts = CodeGen.requiredStruct(n);
+ if( ts!=null && ts._fref ) required.add(ts._name);
+ return null;
+ });
+ for( String typeName : required ) {
+ try {
+ CompUnit xcunit = findRequiredType(code,cunit,typeName);
+ if( xcunit != null )
+ cunit.addDep(xcunit);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+ }
+
+ private static CompUnit findRequiredType(CodeGen code, CompUnit cunit, String typeName) throws IOException {
+ for( CompUnit cu = cunit; cu != null; cu = cu._par ) {
+ String symbol = typeName.startsWith(cu._cname+".")
+ ? typeName.substring(cu._cname.length()+1)
+ : typeName;
+ CompUnit xcunit = findCUnitModule(code,cu,symbol);
+ if( xcunit != null )
+ return xcunit;
+ }
+ return findCUnitExternalSimple(code,typeName);
+ }
+
+ // Load the public symbols for this CompUnit, and add dependent CompUnits to the NEEDS_LOAD list.
+ private static void loadDeps(CodeGen code, CompUnit cunit) {
+ // Load IR after parsing
+ if( NEEDS_LOAD.find(cunit) != -1 )
+ return; // Already on the to-be-loaded list?
+ try {
+ NEEDS_LOAD.add(cunit);
+ // Load dependent classes
+ ElfReader elf = ElfReader.load(cunit._obj,cunit);
+ elf.loadPublicSymbols();
+ for( String fname : elf._deps ) {
+ // Check CompUnit for being up to date
+ CompUnit sub = makeCUnit(code,null,fname,null/*depObj(code,cunit,fname)*/);
+ // If not seen before or out of date, parse the new nested cunit
+ if( sub._clz == null && sub._par == null )
+ WORK.add(sub);
+ }
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static File depObj(CodeGen code, CompUnit cunit, String fname) {
+ if( cunit._smp != null )
+ return null;
+ ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(fname.replace('/','.')));
+ if( elf == null )
+ throw new RuntimeException("Cannot find external Simple dependency '"+fname+"'");
+ return elf._file;
+ }
+
+ private static File externalObj(CodeGen code, CompUnit par, String name) {
+ String cname = par==null ? name : par._cname + "." + name;
+ ElfReader elf = code.findExternalSimple(Parser.addClzPrefix(cname));
+ return elf == null ? null : elf._file;
+ }
+
+ private static void loadCompUnit(CodeGen code, CompUnit cunit) {
+ // Expecting a previously compiled object file
+ assert cunit._obj != null;
+ // Extract published symbols and put them in the internal tables;
+ // cross-references will pick them up there.
+ ElfReader elf = ElfReader.load(cunit._obj,cunit);
+ cunit._clz = elf.loadSimple(code);
+ StartNode lstart = (StartNode)elf._nodes.at(0);
+ StopNode lstop = ( StopNode)elf._nodes.last();
+
+ // Add all new loaded functions to the linker table
+ for( Node n : lstop._inputs ) {
+ StopCUNode stop = (StopCUNode)n;
+ StartCUNode start = stop.start();
+ CompUnit cu = code._compunits.get(start._fname);
+ cu._start = start;
+ cu._stop = stop ;
+
+ for( Node ret : stop._inputs ) {
+ FunNode fun = ((ReturnNode)ret).fun();
+ code.link(fun);
+ }
+ }
+
+ // Hook the new code into the existing code graph
+ for( Node lstopcu : lstop._inputs )
+ code._stop.addDef(lstopcu);
+ lstart.subsume(code._start);
+
+ // Add the new top-level loaded class
+ Parser.TYPES.put(cunit._clz._name,cunit._clz);
+ Field inst = cunit._clz.field(cunit._name);
+ if( inst != null ) // Also the instance type, if a constructor exists
+ Parser.TYPES.put(cunit._cname, ((TypeMem)((TypeFunPtr)inst._t)._ret)._t);
+ }
+
+ // Symbol not found in source code (or we would not be here).
+ // Search the module for the fref
+ public static CompUnit findCompUnitOrThrow(CodeGen code, CompUnit cu, String symbol) {
+ CompUnit cu2 = findCompUnit(code,cu,symbol);
+ if( cu2 != null ) return cu2;
+ throw new RuntimeException("Undefined name '" + symbol +"'");
+ }
+
+ public static CompUnit findCompUnit(CodeGen code, CompUnit cu, String symbol) {
+ // Symbol not found in source code (or we would not be here).
+ // Search the module for the fref
+ try {
+ CompUnit local = findCUnitModule(code,cu,symbol);
+ if( local != null )
+ return local;
+ return findCUnitExternalSimple(code,symbol);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ // Find the symbol in module, starting from cunit. This searches up the nested
+ // class chain and sideways 1 step, so that any prefix of the class chain can
+ // be skipped as a shortcut. Symbol can have dotted names, which then go
+ // down into classes. e.g.
+ // A/X/Y exists.
+ // A/B contains X.Y, which is a shortcut for A.X.Y
+
+ // However if A/B contains "Y", the search would be for A.B.Y then A.Y both of
+ // which do not exist.
+ private static CompUnit findCUnitModule( CodeGen code, CompUnit cunit, String symbol ) throws IOException {
+ if( cunit == null ) // Ran off top of module scope?
+ return null; // Failed search
+
+ // Check self
+ if( cunit._name.equals(symbol) )
+ return cunit;
+
+ // If symbol is itself nested (has embedded dots), move the leading part
+ // over to the CompUnit path. So e.g.:
+ // CompUnit{A.B}, Symbol="X.Y"
+ // becomes
+ // CompUnit{A.B.X}, Symbol="Y"
+ int dotIdx = symbol.indexOf(".");
+ if( dotIdx != -1 ) {
+ String pre = symbol.substring(0,dotIdx);
+ String rest = symbol.substring(dotIdx+1 );
+ String fname = cunit._fname + "/" + pre;
+ File f = new File( code._modDir + "/" + fname + ".smp" );
+ if( f.exists() ) {
+ CompUnit sub = makeCUnit(code,cunit,pre);
+ // Note that "sub" itself is not put on the to-be-parsed-list. We're
+ // referring to a child class and do not need to parse the middles.
+ CompUnit dcunit = findCUnitModule(code,sub,rest);
+ if( dcunit != null )
+ return dcunit; // Nested dot search found cunit
+ }
+ }
+
+ // Look in mod / fname / name / symbol.smp
+ String fname = cunit._fname + "/" + symbol;
+ File f = new File( code._modDir + "/" + fname + ".smp" );
+ if( f.exists() ) {
+ CompUnit sub = makeCUnit(code,cunit,symbol);
+ // If not seen before or out of date, parse the new nested cunit
+ if( sub._clz == null && WORK.find(sub) == -1 )
+ WORK.add(sub);
+ return sub;
+ }
+
+ // Look up the scope
+ return findCUnitModule( code, cunit._par, symbol );
+ }
+
+ // Search external Simple objects only. C linkage uses explicit "C"
+ // declarations, which produce ExternNodes and are resolved by the linker.
+ public static CompUnit findCUnitExternalSimple( CodeGen code, String symbol ) {
+ String clzName = symbol.startsWith(Parser.CLZ) ? symbol : Parser.addClzPrefix(symbol);
+ String cname = clzName.substring(Parser.CLZ.length());
+ String fname = cname.replace('.','/');
+
+ CompUnit cunit = code._compunits.get(fname);
+ if( cunit != null )
+ return cunit;
+
+ ElfReader elf = code.findExternalSimple(clzName);
+ if( elf == null )
+ return null;
+
+ try {
+ cunit = makeCUnit(code,null,fname,elf._file);
+ } catch( IOException ioe ) {
+ throw new RuntimeException(ioe);
+ }
+ ElfReader typedElf = ElfReader.load(cunit._obj,cunit);
+ TypeStruct clz = typedElf == null ? null : typedElf.loadPublicTypes(code);
+ if( clz != null )
+ Parser.TYPES.put(clz._name,clz);
+ else
+ clz = (TypeStruct)Parser.TYPES.get(clzName);
+ if( clz == null )
+ Parser.TYPES.put(clzName,clz = TypeStruct.make(clzName,true));
+ cunit._clz = clz;
+ if( WORK.find(cunit) == -1 && NEEDS_LOAD.find(cunit) == -1 )
+ WORK.add(cunit);
+ return cunit;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
index 777182dc..48319006 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
@@ -3,7 +3,7 @@ package com.seaofnodes.simple.codegen;
// This Node rip-relative encodings has sizes that vary by delta
public interface RIPRelSize {
// delta is the distance from the opcode *start* to the target start. Each
- // hardware target adjusts as needed, X86 measures from the opcode *end*
+ // hardware target adjusts as needed. X86 measures from the opcode *end*
// and small jumps are 2 bytes, so they'll need to subtract 2.
byte encSize(int delta);
diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
index da521082..4c00391c 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -56,6 +56,8 @@ public class RegAlloc {
public int _spills, _spillScaled;
+ private boolean _done;
+
// -----------------------
// Live ranges with self-conflicts or no allowed registers
private final IdentityHashMap<LRG,String> _failed = new IdentityHashMap<>();
@@ -64,6 +66,7 @@ public class RegAlloc {
_failed.put(lrg,"");
}
boolean success() { return _failed.isEmpty(); }
+ public boolean done() { return _done; }
// -----------------------
@@ -72,16 +75,16 @@ public class RegAlloc {
short _lrg_num;
// Define a new LRG, and assign n
- LRG newLRG( Node n ) {
+ LRG newLRG( Node n, FunNode fun ) {
LRG lrg = lrg(n);
if( lrg!=null ) return lrg;
- lrg = new LRG(_lrg_num++);
+ lrg = new LRG(_lrg_num++, fun);
LRG old = _lrgs.put(n,lrg); assert old==null;
return lrg;
}
// LRG for n
- LRG lrg( Node n ) {
+ public LRG lrg( Node n ) {
LRG lrg = _lrgs.get(n);
if( lrg==null ) return null;
LRG lrg2 = lrg.find();
@@ -130,18 +133,19 @@ public class RegAlloc {
}
// Printable register number for node n
- String reg( Node n ) { return reg(n,null); }
- String reg( Node n, FunNode fun ) {
+ String reg( Node n ) {
LRG lrg = lrg(n);
- if( lrg==null ) return null;
+ return lrg==null ? null : reg(lrg);
+ }
+ String reg( LRG lrg ) {
// No register yet, use LRG
if( lrg._reg == -1 ) return "V"+lrg._lrg;
// Chosen machine register unless stack-slot and past RA
String[] regs = _code._mach.regs();
- if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || fun==null )
+ if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || lrg._fun==null )
return RegMask.reg(regs,lrg._reg);
// Stack-slot past RA uses the frame layout logic
- return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]";
+ return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]";
}
// -----------------------
@@ -177,6 +181,7 @@ public class RegAlloc {
round++;
}
postColor(); // Remove no-op spills
+ _done = true;
}
private boolean graphColor(byte round) {
@@ -245,6 +250,8 @@ public class RegAlloc {
// single-register. Split after the def and before the use. Does not
// require a full pass.
+ // Compute def nOuts *before* replacing those uses with a split
+ int defNouts = ((Node)lrg._machDef).nOuts();
// Split just after def
if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
// Force must-split, even if a prior split same block because register
@@ -256,7 +263,7 @@ public class RegAlloc {
// st4 [V1],len - No good, must split around
insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/);
// Split just before use
- if( lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && ((Node)lrg._machDef).nOuts()==1) )
+ if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) )
insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg);
return true;
}
@@ -483,8 +490,8 @@ public class RegAlloc {
void findAllLRG( LRG lrg ) {
_ns.clear();
int wd = 0;
- _ns.push((Node)lrg._machDef);
- _ns.push((Node)lrg._machUse);
+ if( lrg._machDef != null ) _ns.push((Node)lrg._machDef);
+ if( lrg._machUse != null ) _ns.push((Node)lrg._machUse);
while( wd < _ns._len ) {
Node n = _ns.at(wd++);
if( lrg(n)!=lrg ) continue;
@@ -543,12 +550,12 @@ public class RegAlloc {
// Clone simple constants if possible
Node split = def instanceof MachNode mach && mach.isClone() && (umask==null || mach.outregmap().overlap(umask))
? mach.copy()
- : _code._mach.split(kind,round,lrg);
+ : _code._mach.split(lrg,kind,round);
_lrgs.put(split,lrg);
return split;
}
private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
- SplitNode split = _code._mach.split(kind,round,lrg);
+ SplitNode split = _code._mach.split(lrg,kind,round);
_lrgs.put(split,lrg);
return split;
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
index c7dda1da..81e47e8e 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
@@ -33,7 +33,6 @@ public class RegMask {
public RegMask(long bits ) { _bits0 = bits; }
public RegMask(long bits0, long bits1 ) { _bits0 = bits0; _bits1 = bits1; }
public RegMask(RegMask r) { _bits0 = r._bits0; _bits1 = r._bits1; }
- private RegMask() { _bits0 = _bits1 = 0; }
// AND, with copy-on-write if changing
RegMask and( RegMask mask ) {
diff --git a/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
new file mode 100644
index 00000000..8014bb2c
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -0,0 +1,617 @@
+package com.seaofnodes.simple.codegen;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.*;
+
+abstract public class Serialize {
+
+ static void serialize( CodeGen code ) {
+
+ // Get all Nodes in a sane order
+ Ary<Node> nodes = nodeOrder(code);
+
+ // Array of dependent file names
+ HashSet<String> xdeps = new HashSet<>();
+ for( CompUnit cu : code._compunits.values() ) {
+ if( cu._deps != null )
+ for( CompUnit cudep : cu._deps )
+ xdeps.add(cudep._fname);
+ }
+ String[] deps = xdeps.toArray(new String[xdeps.size()]);
+
+ // Top-level clazz being reported
+ String topName = code._srcName == null ? "Test" : code._srcName;
+ CompUnit top = code._compunits.get(topName);
+ TypeStruct clz = top._clz;
+
+ // Preserve which same-named structs are the source-level declarations.
+ // Optimized graph types can carry sharper field states (e.g. len==0),
+ // and cannot be used to reconstruct this mapping during loading.
+ TreeMap<String,TypeStruct> named = new TreeMap<>();
+ for( Type type : Parser.TYPES.values() )
+ if( type instanceof TypeStruct ts )
+ named.put(ts._name,ts);
+ TypeStruct[] pubTypes = named.values().toArray(new TypeStruct[0]);
+
+ // Compress into bytes
+ BAOS baos = write(nodes, clz, pubTypes, deps, code._aliases, code._fidxs, code._rpcs, code._externFunc);
+
+ // --- Expensive bijection assert
+ if( false ) {
+ // Inflate into POJOs; renumbers everything
+ ElfReader elf = new ElfReader(new BAOS(baos.toByteArray()));
+ readAll(code,elf, code._aliases, code._fidxs, code._rpcs);
+ BAOS baos2 = write(elf._nodes,elf._clz,elf._pubTypes,elf._deps, code._aliases, code._fidxs, code._rpcs, code._externFunc);
+
+ // Bi-jection
+ for( int i=0; i<baos.size(); i++ )
+ assert baos.buf()[i]==baos2.buf()[i] : "bijection mismatch at "+i+": "+(baos.buf()[i]&0xFF)+" != "+(baos2.buf()[i]&0xFF);
+ assert baos.size()==baos2.size() : "bijection size mismatch: "+baos.size()+" != "+baos2.size();
+ }
+ // --- Expensive bijection assert
+
+ // Record serialized IR for later ELF writing
+ code._serial = baos;
+ }
+
+ // --------------------------------------------------
+ static BAOS write(Ary<Node> nodes, TypeStruct clz, TypeStruct[] pubTypes, String[] depobjs, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) {
+ // Initialize the mapping from bits/tags to types
+ Type.TAGOFFS();
+
+ BAOS baos = new BAOS();
+ // A - Print a header
+ baos.write('C').write('0').write('D').write('E');
+
+ // Count unique Types
+ var types = new HashMap<Type,Integer>();
+ // Count published types *first*, so can align with published strings
+ types.put(Type.TOP,0);
+ types.put( clz, types.size() );
+ // Have to visit the innards specially, since they did not get the
+ // normal recursive visit when first touching a type, because the
+ // published types are packed early to align with their string names so
+ // lookup in ElfReader can be fast.
+ for( int i=0; i<clz.nkids(); i++ )
+ clz.at(i).gather(types);
+ // Gather types from all sources
+ for( Node n : nodes ) {
+ n._type.gather(types);
+ if( n instanceof CallEndNode cend ) cend._rpc.gather(types);
+ if( n instanceof FunNode fun ) fun.sig().gather(types);
+ if( n instanceof ParmNode parm ) parm._declaredType.gather(types);
+ if( n instanceof TypeNode con ) con._con .gather(types);
+ }
+ // Only record canonical declarations already reachable from this
+ // object's graph. Pulling unrelated parser types into this closure
+ // can include open or partially built types from other units.
+ Ary<TypeStruct> writtenPubs = new Ary<>(TypeStruct.class);
+ for( TypeStruct ts : pubTypes )
+ if( types.containsKey(ts) )
+ writtenPubs.add(ts);
+ // Radix sort by Type ID#
+ Type[] atypes = new Type[types.size()];
+ for( Type t : types.keySet() )
+ atypes[types.get(t)] = t;
+
+ // Count all unique Strings
+ var strs = new HashMap<String,Integer>();
+ strs.put("",0); // Null string is always 0
+ // Find and count *published* Strings first
+ gather(strs,clz._name);
+ // Find and count dependent object file string names
+ if( depobjs != null )
+ for( String fname : depobjs )
+ gather(strs,fname);
+ // Count strings from all types
+ for( Type t : atypes ) {
+ if( t instanceof Field fld ) gather(strs,fld._fname);
+ if( t instanceof TypeStruct ts ) gather(strs,ts . _name);
+ }
+ // Count strings used by local->global bit mappings.
+ aliases.gather(strs);
+ fidxs .gather(strs);
+ rpcs .gather(strs);
+ for( String extern : externFunc.values() )
+ gather(strs,extern);
+ // Count strings from nodes
+ for( Node n : nodes )
+ n.gather(strs);
+ // Radix sort by string ID#
+ String[] astrs = new String[strs.size()];
+ for( String s : strs.keySet() )
+ astrs[strs.get(s)] = s;
+
+ // Check that published strings and types align
+ assert astrs[1] == ((TypeStruct)atypes[1])._name;
+ if( depobjs != null )
+ for( int i=0; i<depobjs.length; i++ )
+ assert astrs[1+i+1] == depobjs[i];
+
+
+ // B - Write unique strings
+ baos.packed4(depobjs==null ? 0 : depobjs.length);
+ baos.packed4(strs.size());
+ // Write in ID# order; the first depobjs.length of these are dependent strings
+ for( String s : astrs )
+ baos.packedS(s);
+
+ // B.2 - Write the local->global mappings
+ aliases.packed(baos,strs);
+ fidxs .packed(baos,strs);
+ rpcs .packed(baos,strs);
+ ArrayList<Integer> externFidxs = new ArrayList<>(externFunc.keySet());
+ Collections.sort(externFidxs);
+ baos.packed2(externFidxs.size());
+ for( int fidx : externFidxs ) {
+ baos.packed2(fidx);
+ baos.packed2(strs.get(externFunc.get(fidx)));
+ }
+
+ // C - Write unique Types
+ baos.packed4(atypes.length);
+ baos.packed2(writtenPubs._len);
+ for( TypeStruct ts : writtenPubs )
+ baos.packed4(types.get(ts));
+ // Write Types in ID# order, no children
+ for( Type t : atypes )
+ t.packed(baos,strs);
+ // Write Types in ID# order, only child IDs
+ for( Type t : atypes ) {
+ int nkids = t.nkids();
+ for( int i=0; i<nkids; i++ )
+ baos.packed4(types.get(t.at(i)));
+ }
+
+
+ // D - Write the nodes. Since this is expected to be the bulk of the
+ // data, we might want to explore various options.
+
+ // 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));
+ n.packed(baos,strs,types,anodes);
+ }
+ // Write out the input indices packed.
+ for( Node n : nodes ) {
+ for( int j=0; j<n.nIns(); j++ ) {
+ Node def = n.in(j);
+ Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def);
+ assert idx != null : "Missing serialized input for "+n+" input "+j+" def "+def;
+ baos.packed2(idx);
+ }
+ if( n instanceof FunNode fun )
+ baos.packed2(anodes.get(fun.ret()));
+ }
+
+ return baos;
+ }
+ public static <T> void gather( HashMap<T,Integer> strs, T s) {
+ if( s!=null && !strs.containsKey(s) )
+ strs.put(s,strs.size());
+ }
+ public static void gather( AryInt is, int s) {
+ if( is.atX(s)==0 ) {
+ int cnt=0;
+ for( int i=1; i<is._len; i++ )
+ if( is._es[i] != 0 )
+ cnt++;
+ is.setX(s,cnt+1);
+ }
+ }
+
+ // Returned array of *public* strings only.
+ static void read_public_strs(ElfReader elf) {
+ BAOS bais = elf._bais;
+ Type.TAGOFFS();
+
+ // A - Read a header
+ if( bais.read()!='C' || bais.read()!='0' || bais.read()!='D' || bais.read()!='E' )
+ throw new IllegalArgumentException("Missing magic word");
+
+ // B - Packed read of #strings, then strings
+ int ndependents = bais.packed4(); // Number of dependent object files
+ int nstrs = bais.packed4(); // Total number of strings
+ String[] strs = new String[nstrs];
+ for( int i=0; i<nstrs; i++ )
+ strs[i] = bais.packedS();
+ elf._strs = strs;
+
+ // Collect dependent file names
+ elf._deps = new String[ndependents];
+ for( int i=0; i<ndependents; i++ )
+ elf._deps[i] = strs[1+i+1];
+ }
+
+ // --------------------------------------------------
+ record TypeRead(Type[] types, String[] strs, GlobalBits fileAliases, GlobalBits fileFidxs, GlobalBits fileRpcs) {}
+
+ static TypeRead readTypes( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) {
+ BAOS bais = elf._bais;
+ // Initialize the mapping from bits/tags to types
+ Type.TAGOFFS();
+
+ // If not read the strings already, read them now
+ String[] strs = elf._strs;
+ if( strs == null ) {
+ // A - Read a header
+ if( bais.read() != 'C' || bais.read() != '0' || bais.read() != 'D' || bais.read() != 'E' )
+ throw new IllegalArgumentException( "Missing magic word" );
+
+ // B - Packed read of #strings, then strings
+ int ndependents = bais.packed4(); // Number of dependent object files
+ int nstrs = bais.packed4(); // Total number of strings
+ elf._strs = strs = new String[nstrs];
+ for( int i = 0; i < nstrs; i++ )
+ strs[i] = bais.packedS();
+
+ // Collect dependent file names
+ elf._deps = new String[ndependents];
+ for( int i = 0; i < ndependents; i++ )
+ elf._deps[i] = strs[1 + i + 1];
+ }
+
+ // B.2 - Read the local->global mappings. Further down we will map
+ // Elf file-local -> global, then global -> CodeGen.CODE local
+ GlobalBits fileAliases = GlobalBits.packed(bais,strs);
+ GlobalBits fileFidxs = GlobalBits.packed(bais,strs);
+ GlobalBits fileRpcs = GlobalBits.packed(bais,strs);
+ int nexterns = bais.packed2();
+ for( int i=0; i<nexterns; i++ ) {
+ int fileFidx = bais.packed2();
+ String extern = strs[bais.packed2()];
+ int fidx = fileFidx < GlobalBits.RESERVED ? fileFidx : fidxs.map(fileFidxs,fileFidx);
+ String old = code.externFunc(fidx);
+ if( old == null )
+ code.externFunc(fidx,extern);
+ else
+ assert old.equals(extern);
+ }
+
+
+ // C - Packed read of #types, then types
+ int ntypes = bais.packed4();
+ int npubTypes = bais.packed2();
+ int[] pubTypeIds = new int[npubTypes];
+ for( int i=0; i<npubTypes; i++ )
+ pubTypeIds[i] = bais.packed4();
+ Type[] types = Type.packed(bais,strs,ntypes, Parser.TYPES,
+ fileAliases,aliases,
+ fileFidxs ,fidxs,
+ fileRpcs ,rpcs);
+
+ // aliases.set(0,0);
+ // Check and collect first published symbols map to TypeStructs
+ elf._clz = (TypeStruct)types[1/*skip zero*/];
+ assert elf._clz._name==strs[1/*skip null ptr*/];
+ for( Type type : types )
+ if( type instanceof TypeStruct ts )
+ Parser.TYPES.put(ts._name,ts);
+ for( Type type : types ) {
+ if( !(type instanceof TypeStruct ts) || !Parser.startsClzPrefix(ts._name) )
+ continue;
+ String cname = ts._name.substring(Parser.CLZ.length());
+ int dot = cname.lastIndexOf('.');
+ String name = dot == -1 ? cname : cname.substring(dot+1);
+ Field inst = ts.field(name.intern());
+ if( inst != null && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem && mem._t instanceof TypeStruct its )
+ Parser.TYPES.put(cname,its);
+ }
+ elf._pubTypes = new TypeStruct[npubTypes];
+ for( int i=0; i<npubTypes; i++ ) {
+ TypeStruct ts = elf._pubTypes[i] = (TypeStruct)types[pubTypeIds[i]];
+ Parser.TYPES.put(ts._name,ts);
+ }
+ return new TypeRead(types,strs,fileAliases,fileFidxs,fileRpcs);
+ }
+
+ static void readAll( CodeGen code, ElfReader elf, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs ) {
+ BAOS bais = elf._bais;
+ TypeRead tread = readTypes(code, elf, aliases, fidxs, rpcs);
+ Type[] types = tread.types();
+ String[] strs = tread.strs();
+ GlobalBits fileAliases = tread.fileAliases();
+
+ // D - Read the nodes
+
+ // Number of nodes
+ int num = bais.packed4();
+ // Packed array of nodes
+ Ary<Node> nodes = new Ary<>(Node.class);
+ CompUnit cu = null;
+ for( int i=0; i<num; i++ ) {
+ Node.Tag tag = Node.Tag.VALS[bais.packed1()];
+ Type t = types[bais.packed2()];
+ Node n = tag.make(bais,strs,types,fileAliases,aliases);
+ n._type = t;
+ nodes.push(n);
+ // A little extra for FunNodes
+ if( n instanceof StartCUNode startcu ) {
+ cu = code._compunits.get(startcu._fname==null ? (code._srcName==null ? "Test" : code._srcName) : startcu._fname);
+ assert cu != null;
+ }
+ if( n instanceof StopCUNode stopcu ) {
+ cu = null;
+ }
+ if( n instanceof FunNode fun ) {
+ assert cu != null;
+ fun._compunit = cu;
+ }
+ if( n instanceof ExternNode ext && ext._con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ String old = code.externFunc(tfp.fidx());
+ if( old == null )
+ code.externFunc(tfp.fidx(), ext._extern);
+ else
+ assert old.equals(ext._extern);
+ }
+ }
+
+ // Node edges
+ for( Node n : nodes ) {
+ int len = n.nIns();
+ for( int i=0; i<len; i++ ) {
+ int idx = bais.packed2();
+ Node def = idx==0 ? null : nodes.at(idx-1);
+ // MemMerge precise aliases are encoded by their input slot.
+ // Remap populated file-local slots into this compilation's
+ // local alias numbering; null holes carry no identity.
+ int local = n instanceof MemMergeNode && i >= GlobalBits.RESERVED && def != null
+ ? aliases.map(fileAliases,i)
+ : i;
+ n.setDefX(local,def);
+ // Call's output[0] is always the CallEnd
+ if( n instanceof CallEndNode cend && i==0 ) {
+ var outs = cend.call()._outputs;
+ outs.swap(0,outs._len-1);
+ }
+ }
+ // A little extra for FunNodes
+ if( n instanceof FunNode fun ) {
+ // Return is hooked like an edge
+ ReturnNode ret = (ReturnNode)nodes.at(bais.packed2()-1);
+ fun.setRet(ret);
+ ret._fun = fun;
+ }
+ }
+ // Add edge from Start to Stop
+ StartNode start = (StartNode)nodes.at(0);
+ StopNode stop = (StopNode)nodes.last();
+ start.setDef(1,stop);
+
+ assert check(code,nodes);
+ elf._nodes = nodes;
+ }
+
+ // Check loaded types and nodes are consistent
+ private static boolean check( CodeGen code, Ary<Node> nodes ) {
+ // Loaded nodes are complete, unlike nodes still being built by the
+ // parser. Recompute them with post-parse semantics. The compilation-
+ // unit Stop is the exception: its escape type depends on the complete
+ // program's public interface, which ParseAll freezes only after every
+ // compilation unit has been loaded.
+ boolean good = true;
+ CodeGen.Phase phase = code._phase;
+ code._phase = CodeGen.Phase.Opto;
+ try {
+ for( Node n : nodes ) {
+ if( n instanceof StopCUNode && !code.publicInterfaceFrozen() )
+ continue;
+ Type nval = n.compute();
+ if( nval != n._type ) {
+ System.err.println("Node "+n+" read in as "+n._type+" but computes as "+nval);
+ good = false;
+ }
+ }
+ } finally {
+ code._phase = phase;
+ }
+ return good;
+ }
+
+ // --------------------------------------------------
+ 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);
+ // Walk all listed functions, in this single compilation unit
+ for( FunNode fun : code._linker )
+ if( fun!=null && !fun.isDead() && fun._compunit == cu )
+ _funWalk(fun,nodes,visit);
+
+ // Just constants used by the listed functions
+ Ary<Node> cons = new Ary<>(Node.class);
+ cons.add(code._start);
+ for( Node n : code._start._outputs ) {
+ if( n instanceof ConstantNode con && !visit.get(con._nid) ) {
+ for( Node use : con.outs() ) {
+ // Special case for Cast:BOT which is basically a double-indirect constant
+ if( use instanceof CheckCastNode cast && cast.in(0)==null )
+ for( Node useuse : cast.outs() )
+ conUsed(cast,useuse,visit,cons);
+ // PtrToInt is another zero-code value rooted at a global
+ // constant. Its array-body Add is the value used by the
+ // function, so include that extra link in the ownership
+ // test as well.
+ if( use instanceof PtrToIntNode ptr )
+ for( Node add : ptr.outs() ) {
+ for( Node addUse : add.outs() )
+ conUsed(add,addUse,visit,cons);
+ conUsed(ptr,add,visit,cons);
+ }
+ // Constant used by somebody, so flag for output
+ conUsed(con,use,visit,cons);
+ }
+ }
+ }
+ // StartCU and memory projection
+ cons.add(cu._start);
+ for( Node n : cu._start._outputs )
+ if( n instanceof ProjNode ) cons.add(n);
+ // All the innards of functions
+ cons.addAll(nodes);
+ // StopCU, Stop
+ cons.add(cu._stop);
+ cons.add(code._stop);
+ visit.clear();
+ return cons;
+ }
+
+ private static void conUsed( Node con, Node use, BitSet visit, Ary<Node> cons ) {
+ if( use == null || !visit.get(use._nid) || visit.get(con._nid) ) return;
+ cons.add(con);
+ visit.set(con._nid);
+ }
+
+ // --------------------------------------------------
+ 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);
+
+ // All the global constants
+ for( Node n : code._start._outputs ) {
+ if( n.isConst() ) {
+ visit.set( n._nid );
+ nodes.add( n );
+ // Special for Cast:BOT
+ for( Node cast : n._outputs )
+ if( cast != null && cast.nIns() == 2 && cast.in( 0 ) == null ) {
+ visit.set( cast._nid );
+ nodes.add( cast );
+ // Array pointer conversion adds the body offset after
+ // the zero-code PtrToInt. Both nodes are global-value
+ // descendants of the constant and otherwise have no
+ // function-local root from which to walk them.
+ if( cast instanceof PtrToIntNode )
+ for( Node add : cast.outs() )
+ if( add instanceof AddNode && !visit.get(add._nid) ) {
+ visit.set(add._nid);
+ nodes.add(add);
+ }
+ }
+ }
+ }
+
+ // All the CompUnits
+ for( CompUnit cu : code._compunits.values() ) {
+ // Whole CompUnits have no users, went dead
+ if( cu._start != null && !cu._start.isDead() ) {
+ nodes.add(cu._start);
+ // Memory proj
+ nodes.add(cu._start.proj(1));
+ // All the functions, including internal ones, in comp-unit order
+ for( FunNode fun : code._linker )
+ if( fun!=null && !fun.isDead() && fun._compunit == cu )
+ _funWalk(fun,nodes,visit);
+ nodes.add(cu._stop);
+ }
+ }
+
+ nodes.add(code._stop);
+ visit.clear();
+ return nodes;
+ }
+
+ // Walk and print whole functions at a time, in CG order
+ private static void _funWalk( FunNode fun, Ary<Node> rpo, BitSet visit ) {
+ int start = rpo._len;
+ _funRPO(fun,rpo,visit);
+ int len = rpo._len - start;
+ for( int i=0; i< len>>1; i++ )
+ rpo.swap(start+i,start+len-1-i);
+ }
+
+ // Walk and gather RPO nodes.
+ private static void _funRPO(Node n, Ary<Node> rpo, BitSet visit ) {
+ if( visit.get(n._nid) ) return; // Been there, done that
+ 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 ) {
+ // nothing
+ } else {
+ // CFG to CFG before CFG to data
+ for( Node use : n._outputs )
+ if( use instanceof CFGNode usecfg && !ncfg.skip(usecfg) ) // Do not walk from Call to Fun
+ _funRPO(use,rpo,visit);
+ }
+ // Walk CFG to data eventually
+ for( Node use : n._outputs )
+ if( !(use instanceof CFGNode) )
+ _funRPO(use,rpo,visit);
+ } else {
+ // Do not walk from a non-CFG to a CFG; CFGs walk to CFGs to
+ // preserve CFG order. Do not walk *into* Phis; wait for the
+ // Region to walk the Phis (otherwise you visit some of the
+ // Phis ahead of others forcing the Phis to be spread around).
+ for( Node use : n._outputs ) {
+ if( use!=null && !(use instanceof CFGNode) && !(use instanceof PhiNode) )
+ _funRPO(use,rpo,visit);
+ }
+ }
+
+ // If parent is a Multi, do not add (yet).
+ // If self is a Multi, add self and children.
+ if( multiChild(n) || n instanceof CallEndNode ) {
+ // nothing; delay until Multi-head or CallNode
+ } else if( n instanceof MultiNode ) {
+ // Now lump all multi/projections together
+ printMulti(n,rpo);
+ } else if( n instanceof CallNode call ) {
+ printMulti(call.cend(),rpo);
+ rpo.add(call);
+ } else if( n instanceof RegionNode ) {
+ // Now lump all Phis together
+ int old = rpo._len;
+ for( Node use : n._outputs )
+ if( use instanceof PhiNode )
+ rpo.add(use);
+ // Sort by label? Want Phis before control outputs
+ Arrays.sort( rpo._es, old, rpo._len, (x,y) -> y.label().compareTo(x.label()) );
+ rpo.add(n);
+ } else if( n instanceof StopNode ) {
+ // nothing
+ } else {
+ rpo.add(n); // Post-order add
+ }
+ }
+
+ private static boolean multiChild(Node n) {
+ return n instanceof Proj || n instanceof PhiNode;
+ }
+
+ private static void printMulti(Node n, Ary<Node> rpo) {
+ // Now lump all multi/projections together
+ for( Node use : n._outputs )
+ rpo.add(use);
+ // Sort by projection order
+ if( !(n instanceof StartNode) )
+ Arrays.sort( rpo._es,rpo._len-n.nOuts(),rpo._len,
+ (x,y) -> x instanceof ProjNode px && y instanceof ProjNode py ? (py.idx() - px.idx()) : (y._nid - x._nid) );
+ rpo.add(n);
+ }
+
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/src/main/java/com/seaofnodes/simple/node/AddFNode.java
deleted file mode 100644
index 4ad02dbb..00000000
--- a/src/main/java/com/seaofnodes/simple/node/AddFNode.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class AddFNode extends ArithFNode {
- public AddFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "AddF"; }
-
- @Override public String glabel() { return "+"; }
-
- @Override double doOp( double x, double y ) { return x+y; }
-
- @Override
- public Node idealize() {
- // Add of 0.
- Type t2 = in(2)._type;
- if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
- return in(1);
-
- // Move constants to RHS: con+arg becomes arg+con
- if ( in(1)._type.isConstant() && !t2.isConstant() )
- return swap12();
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new AddFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/AddNode.java b/src/main/java/com/seaofnodes/simple/node/AddNode.java
index c7fd4daa..46607c23 100644
--- a/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -1,16 +1,19 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
-import static com.seaofnodes.simple.Parser.con;
-
public class AddNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public AddNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public AddNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Add; }
@Override public String label() { return "Add"; }
@Override public String op() { return "+"; }
- @Override long doOp( long x, long y ) { return x + y; }
+ @Override long doOp( long x, long y ) { return x + y; }
+ @Override double doOp(double x, double y ) { return x + y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
// Fold ranges like {0-1} + {2-3} into {2-4}.
if( !overflow(x._min,y._min) &&
@@ -21,6 +24,11 @@ public class AddNode extends ArithNode {
@Override
public Node idealize () {
+ // Settle the one-shot arithmetic mode before applying family-specific
+ // algebraic identities.
+ if( _mode==0 )
+ return super.idealize();
+
Node lhs = in(1);
Node rhs = in(2);
if( rhs instanceof AddNode add && add.err()!=null )
@@ -33,8 +41,8 @@ public class AddNode extends ArithNode {
return lhs;
// Add of same to a multiply by 2
- if( lhs==rhs )
- return new ShlNode(null,lhs,con(1));
+ if( lhs==rhs && _mode==1 )
+ return new ShlNode(null,lhs,CodeGen.CODE.con(1));
// Goal: a left-spine set of adds, with constants on the rhs (which then fold).
@@ -44,7 +52,7 @@ public class AddNode extends ArithNode {
// x+(-y) becomes x-y
if( rhs instanceof MinusNode minus )
- return new SubNode(lhs,minus.in(1));
+ return new SubNode(lhs,minus.in(1),_mode);
// Now we might see (add add non) or (add non non) or (add add add) but never (add non add)
@@ -52,18 +60,21 @@ public class AddNode extends ArithNode {
// Swap to (x + y) + z
// Rotate (add add add) to remove the add on RHS
if( rhs instanceof AddNode add )
- return new AddNode(new AddNode(lhs,add.in(1)).peephole(), add.in(2));
+ return copy(copy(lhs,add.in(1)).peephole(), add.in(2));
// Now we might see (add add non) or (add non non) but never (add non add) nor (add add add)
- if( !(lhs instanceof AddNode) )
+ if( !(lhs instanceof AddNode) ) {
// Rotate; look for (add (phi cons) con/(phi cons))
- return spine_cmp(lhs,rhs,this) ? swap12() : phiCon(this,true);
+ if( spine_cmp(lhs,rhs,this) ) return swap12();
+ Node n = phiCon(this,true);
+ return n == null ? super.idealize() : n;
+ }
// Now we only see (add add non)
// Dead data cycle; comes about from dead infinite loops. Do nothing,
// the loop will peep as dead after a bit.
- if( lhs.in(1) == lhs )
+ if( lhs.nIns()>=2 && lhs.in(1) == lhs )
return null;
// Do we have (x + con1) + con2?
@@ -73,7 +84,7 @@ public class AddNode extends ArithNode {
// because if it later became a constant then we could make this
// transformation.
if( addDep(lhs.in(2))._type.isConstant() && rhs._type.isConstant() )
- return new AddNode(lhs.in(1),new AddNode(lhs.in(2),rhs).peephole());
+ return copy(lhs.in(1),copy(lhs.in(2),rhs).peephole());
// Do we have ((x + (phi cons)) + con) ?
@@ -87,13 +98,13 @@ public class AddNode extends ArithNode {
// Do we rotate (x + y) + z
// into (x + z) + y ?
if( spine_cmp(lhs.in(2),rhs,this) )
- return new AddNode(new AddNode(lhs.in(1),rhs).peephole(),lhs.in(2));
+ return copy(copy(lhs.in(1),rhs).peephole(),lhs.in(2));
return super.idealize();
}
// Rotation is only valid for associative ops, e.g. Add, Mul, And, Or, Xor.
- // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)) ?
+ // Do we have ((phi cons)|(x + (phi cons)) + con|(phi cons)|(op y con)) ?
// Push constant up through the phi: x + (phi con0+con0 con1+con1...)
static Node phiCon(Node op, boolean rotate) {
Node lhs = op.in(1);
@@ -126,7 +137,7 @@ public class AddNode extends ArithNode {
for( int i=1; i<ns.length; i++ )
ns[i] = op.copy(lphi.in(i), rhs instanceof PhiNode ? rhs.in(i) : rhs).peephole();
String label = lphi._label + (rhs instanceof PhiNode rphi ? rphi._label : "");
- Node phi = new PhiNode(label,lphi._minType.meet(op._type),ns).peephole();
+ Node phi = new PhiNode(label,ns).peephole();
// Rotate needs another op, otherwise just the phi
return lhs==lphi ? phi : op.copy(lhs.in(1),phi);
}
@@ -164,6 +175,5 @@ public class AddNode extends ArithNode {
return lo._nid > hi._nid;
}
- @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs); }
- @Override Node copyF() { return new AddFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new AddNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/AndNode.java b/src/main/java/com/seaofnodes/simple/node/AndNode.java
index 70b57d6d..083a9a8c 100644
--- a/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class AndNode extends ArithNode {
- public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public AndNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.And; }
- @Override public String label() { return "And"; }
@Override public String op() { return "&"; }
@Override public String glabel() { return "&"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
deleted file mode 100644
index 7fa428c6..00000000
--- a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import java.util.BitSet;
-
-public abstract class ArithFNode extends Node {
- public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
-
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("("), visited);
- in(2)._print0(sb.append(glabel()), visited);
- return sb.append(")");
- }
-
- abstract double doOp(double x, double y);
-
- @Override
- public Type compute() {
- Type t1 = in(1)._type, t2 = in(2)._type;
- if( t1.isHigh() || t2.isHigh() )
- return TypeFloat.TOP;
- if( t1 instanceof TypeFloat f1 &&
- t2 instanceof TypeFloat f2 ) {
- if( f1.isConstant() && f2.isConstant() )
- return TypeFloat.constant(doOp(f1.value(),f2.value()));
- }
- return t1.meet(t2);
- }
-
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index f2beaacb..8c21aef7 100644
--- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -1,16 +1,26 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public abstract class ArithNode extends Node {
+public abstract class ArithNode extends Node implements ModeNode {
// Source location for late reported errors
Parser.Lexer _loc;
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
public ArithNode( Parser.Lexer loc, Node lhs, Node rhs) { super(null, lhs, rhs); _loc = loc; }
+ public ArithNode( Parser.Lexer loc, Node lhs, Node rhs, byte mode) { this(loc,lhs,rhs); _mode=mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
@Override public String glabel() { return op(); }
abstract String op();
@@ -23,20 +33,93 @@ public abstract class ArithNode extends Node {
abstract long doOp(long x, long y);
abstract TypeInteger doOp(TypeInteger x, TypeInteger y);
+ double doOp(double x, double y) { throw Utils.TODO(); }
+
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
+ static byte mode( Type t1, Type t2 ) {
+ if( t1 instanceof TypeFloat || t2 instanceof TypeFloat ) return 2;
+ if( t1 instanceof TypeInteger && t2 instanceof TypeInteger ) return 1;
+ if( t1 instanceof TypeMemPtr && t2 instanceof TypeMemPtr ) return 1;
+ if( t1 instanceof TypeFunPtr && t2 instanceof TypeFunPtr ) return 1;
+ return 0;
+ }
+
+ // Check that a settled mode agrees with all data inputs. Takes the Node
+ // instead of a varargs list both to avoid allocation and to keep this ready
+ // to become shared mode-aware Node behavior later.
+ static Parser.ParseException nodeErr(Node n, Parser.Lexer loc, String op, byte mode, boolean allowFloat) {
+ Type bad = null;
+ for( int i=1; i<n.nIns(); i++ ) {
+ Type t = n.in(i)._type;
+ if( t.isHigh() )
+ return null;
+ // A weak input is not evidence of a bad mode while the graph is
+ // still being built. Loop Phis commonly await their backedge.
+ if( CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() && t == Type.BOTTOM )
+ return null;
+ bad = switch( t ) {
+ case TypeInteger ti -> mode==1 ? null : t;
+ case TypeMemPtr ti -> mode==1 ? null : t;
+ case TypeFunPtr ti -> mode==1 ? null : t;
+ case TypeFloat tf -> mode==2 ? null : t;
+ default -> t;
+ };
+ if( bad != null ) break;
+ }
+ boolean unsupportedFloat = false;
+ if( !allowFloat )
+ for( int i=1; i<n.nIns(); i++ )
+ if( n.in(i)._type instanceof TypeFloat ) {
+ bad = n.in(i)._type;
+ unsupportedFloat = true;
+ break;
+ }
+ if( bad == null )
+ return null;
+ if( unsupportedFloat || n instanceof BoolNode )
+ return Parser.error("Cannot '"+op+"' "+bad,loc);
+ if( n.nIns()==3 )
+ return Parser.error("Cannot "+n.in(1)._type+" "+op+" "+n.in(2)._type,loc);
+ return Parser.error("Cannot '"+op+"' "+bad,loc);
+ }
+
+ // Arithmetic subclasses which have a floating-point interpretation override.
+ boolean allowFloat() { return false; }
// Generic airthmetic op math: high returns high; low returns low; 2
// constants fold; only 2 non-constants call specialized math.
@Override
- public final TypeInteger compute() {
+ public final Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1.isHigh() || t2.isHigh() )
- return TypeInteger.TOP;
- if( t1 instanceof TypeInteger x &&
- t2 instanceof TypeInteger y )
- return x.isConstant() && y.isConstant()
- ? con(x,y)
- : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) );
- return TypeInteger.BOT;
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ TypeFloat.F64.dual();
+ byte mode = _mode==0 ? mode(t1,t2) : _mode;
+ if( mode==0 )
+ return Type.BOTTOM;
+ if( mode==1 ) {
+ if( t1 instanceof TypeInteger x &&
+ t2 instanceof TypeInteger y )
+ return x.isConstant() && y.isConstant()
+ ? con(x,y)
+ : doOp(x,y).makeWide( (byte)Math.max(x._widen,y._widen) );
+ return TypeInteger.BOT;
+ }
+ // FP handling
+ if( t1 instanceof TypeFloat f1 &&
+ t2 instanceof TypeFloat f2 ) {
+ if( f1.isConstant() && f2.isConstant() )
+ return TypeFloat.constant(doOp(f1.value(),f2.value()));
+ return t1.meet(t2);
+ }
+ return TypeFloat.F64;
}
private TypeInteger con( Type t1, Type t2 ) {
@@ -49,36 +132,50 @@ public abstract class ArithNode extends Node {
// push-down peephole cycle because the constants all fold first.
// Returns "Phi(op(con,con),op(x,y))".
- // This check is slightly different from the
-
// Expected to be called after other ideal checks are done.
@Override public Node idealize() {
+ Type t1 = in(1)._type, t2 = in(2)._type;
+
+ // Can we decide int vs flt?
+ if( _mode==0 ) {
+ byte mode = mode(t1,t2);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+ if( _mode==2 ) {
+ if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
+ if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
+ }
+
if( in(1) instanceof PhiNode lhs &&
in(2) instanceof PhiNode rhs &&
lhs.nIns() >= 2 && !lhs.inProgress() &&
- lhs.region()==rhs.region() && !(lhs.region() instanceof FunNode) &&
- lhs.nIns()>2 && // A 1-input Phi will collapse already
// Disallow with self-looping phi; these will collapse
- (lhs.in(2)!=lhs && rhs.in(2)!=rhs) ) {
- // Profit check: only 1 instance of `this` will remain, all the
- // others will fold to constants.
- int cnt=0;
- for( int i=1; i<lhs.nIns(); i++ )
- if( isCon(lhs,rhs,i) )
- cnt++;
- if( lhs.nIns()-1 - cnt <= 1 ) {
- // Profit! Push up-phi and fold
- Node[] ns = new Node[lhs.nIns()];
- ns[0] = lhs.in(0);
- for( int i=1; i<lhs.nIns(); i++ ) {
- ns[i] = isCon(lhs,rhs,i)
- ? new ConstantNode(con(lhs.in(i)._type, rhs.in(i)._type)).peephole()
- : copy(lhs.in(i), rhs.in(i)).peephole();
+ (lhs.nIns() > 2 && lhs.in(2)!=lhs && rhs.nIns() > 2 && rhs.in(2)!=rhs) ) {
+ Node r = lhs.region();
+ if( r==rhs.region() && !(r instanceof FunNode) &&
+ // For loops, the backedge *must* fold away, lest we unroll forever
+ (!(r instanceof LoopNode) || isCon(lhs,rhs,2)) ) {
+ // Profit check: only 1 instance of `this` will remain, all the
+ // others will fold to constants. For loops, *all* must fold away
+ int cnt=0;
+ for( int i=1; i<lhs.nIns(); i++ )
+ if( isCon(lhs,rhs,i) )
+ cnt++;
+ if( lhs.nIns()-1 - cnt <= 1 ) {
+ // Profit! Push up-phi and fold
+ Node[] ns = new Node[lhs.nIns()];
+ ns[0] = lhs.in(0);
+ for( int i=1; i<lhs.nIns(); i++ ) {
+ ns[i] = isCon(lhs,rhs,i)
+ ? ConstantNode.make(con(lhs.in(i)._type, rhs.in(i)._type)).peephole()
+ : copy(lhs.in(i), rhs.in(i)).peephole();
+ }
+ String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label;
+ return new PhiNode(label, ns).peephole();
}
- String label = lhs._label==rhs._label ? lhs._label : lhs._label + rhs._label;
- return new PhiNode(label,lhs._minType,ns).peephole();
}
}
+
return null;
}
@@ -88,14 +185,14 @@ public abstract class ArithNode extends Node {
static boolean overflow( long x, long y ) {
- if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
+ if( (x ^ y ) < 0 ) return false; // unequal signs, never overflow
return (x ^ (x + y)) < 0; // sum has unequal signs, so overflow
}
@Override public Parser.ParseException err() {
- if( in(1)._type.isHigh() || in(2)._type.isHigh() ) return null;
- if( !(in(1)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(1)._type,_loc);
- if( !(in(2)._type instanceof TypeInteger) ) return Parser.error("Cannot '"+op()+"' " + in(2)._type,_loc);
- return null;
+ return nodeErr(this,_loc,op(),_mode,allowFloat());
}
+
+ @Override public boolean eq( Node n ) { return _mode==((ArithNode)n)._mode; }
+ @Override public int hash() { return _mode; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
index 2765c308..c8722f67 100644
--- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -1,23 +1,29 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
import static com.seaofnodes.simple.type.TypeInteger.*;
-abstract public class BoolNode extends Node {
+abstract public class BoolNode extends Node implements ModeNode {
- public BoolNode(Node lhs, Node rhs) {
- super(null, lhs, rhs);
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
+
+ public BoolNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public BoolNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs); _mode = mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
}
abstract public String op(); // String opcode name
@Override
- public String label() { return getClass().getSimpleName(); }
-
- @Override
public String glabel() { return op(); }
@Override
@@ -37,9 +43,17 @@ abstract public class BoolNode extends Node {
if( in(1)==in(2) )
// LT fails, both EQ and LE succeed
return this instanceof LT ? FALSE : TRUE;
- if( t1 instanceof TypeInteger i1 &&
- t2 instanceof TypeInteger i2 )
- return doOp(i1,i2);
+ byte mode = _mode==0 ? ArithNode.mode(t1,t2) : _mode;
+ if( mode==0 )
+ return BOOL;
+ // Handle integer compares
+ if( mode==1 ) {
+ if( t1 instanceof TypeInteger i1 &&
+ t2 instanceof TypeInteger i2 )
+ return doOp(i1,i2);
+ return BOOL;
+ }
+ // Floating compare. Mixed inputs are converted in idealize().
if( t1 instanceof TypeFloat f1 &&
t2 instanceof TypeFloat f2 &&
f1.isConstant() && f2.isConstant() )
@@ -49,22 +63,39 @@ abstract public class BoolNode extends Node {
TypeInteger doOp(TypeInteger t1, TypeInteger t2) { throw Utils.TODO(); }
boolean doOp(double lhs, double rhs) { throw Utils.TODO(); }
- Node copyF(Node lhs, Node rhs) { return null; }
- public boolean isFloat() { return false; }
+ public boolean isFloat() { assert _mode!=0; return _mode==2; }
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
@Override
public Node idealize() {
// Compare of same
if( in(1)==in(2) )
- return this instanceof LT ? Parser.ZERO : new ConstantNode(TRUE);
+ return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE);
+ // Can we decide int vs flt?
+ Type t1 = in(1)._type, t2 = in(2)._type;
+ if( _mode==0 ) {
+ byte mode = ArithNode.mode(t1,t2);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+ if( _mode==2 ) {
+ if( t1 instanceof TypeInteger ) return copy(new ToFloatNode(in(1)).peephole(),in(2));
+ if( t2 instanceof TypeInteger ) return copy(in(1),new ToFloatNode(in(2)).peephole());
+ }
+
// Equals pushes constant to the right; 5==X becomes X==5.
if( this instanceof EQ ) {
if( !(in(2) instanceof ConstantNode) ) {
// con==noncon becomes noncon==con
if( in(1) instanceof ConstantNode || in(1)._nid > in(2)._nid )
- // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
- return in(1)._type instanceof TypeFloat ? new EQF(in(2),in(1)) : new EQ(in(2),in(1));
+ // Equals sorts by NID otherwise: non.high == non.low becomes non.low == non.high
+ return new EQ(in(2),in(1));
}
// Equals X==0 becomes a !X
if( (in(2)._type == ZERO || in(2)._type == Type.NIL) )
@@ -80,32 +111,44 @@ abstract public class BoolNode extends Node {
return null;
}
+ @Override public boolean eq( Node n ) { return _mode==((BoolNode)n)._mode; }
+ @Override public int hash() { return _mode; }
+ @Override public Parser.ParseException err() {
+ return ArithNode.nodeErr(this,null,op(),_mode,!(this instanceof ULT));
+ }
+
public static class EQ extends BoolNode {
public EQ(Node lhs, Node rhs) { super(lhs,rhs); }
+ public EQ(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
+ @Override public Tag serialTag() { return Tag.EQ; }
public String op() { return "=="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1==i2 && i1.isConstant() ) return TRUE;
if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs); }
- Node copyF() { return new EQF(null,null); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs==rhs; }
+ Node copy(Node lhs, Node rhs) { return new EQ(lhs,rhs,_mode); }
}
public static class NE extends BoolNode {
public NE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public NE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
+ @Override public Tag serialTag() { return Tag.NE; }
public String op() { return "!="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1==i2 && i1.isConstant() ) return TRUE;
if( i1._max < i2._min || i1._min > i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs); }
- Node copyF() { throw Utils.TODO(); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs!=rhs; }
+ Node copy(Node lhs, Node rhs) { return new NE(lhs,rhs,_mode); }
}
public static class LT extends BoolNode {
public LT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
+ @Override public Tag serialTag() { return Tag.LT; }
public String op() { return "<" ; }
public String glabel() { return "&lt;"; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
@@ -113,12 +156,14 @@ abstract public class BoolNode extends Node {
if( i1._min >= i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs); }
- Node copyF() { return new LTF(null,null); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs<rhs; }
+ Node copy(Node lhs, Node rhs) { return new LT(lhs,rhs,_mode); }
}
public static class LE extends BoolNode {
public LE(Node lhs, Node rhs) { super(lhs,rhs); }
+ public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
+ @Override public Tag serialTag() { return Tag.LE; }
public String op() { return "<="; }
public String glabel() { return "&lt;="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
@@ -126,32 +171,18 @@ abstract public class BoolNode extends Node {
if( i1._min > i2._max ) return FALSE;
return BOOL;
}
- Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs); }
- Node copyF() { return new LEF(null,null); }
- }
-
- public static class EQF extends EQ {
- public EQF(Node lhs, Node rhs) { super(lhs,rhs); }
- @Override boolean doOp(double lhs, double rhs) { return lhs == rhs; }
- @Override public boolean isFloat() { return true; }
- @Override Node copy(Node lhs, Node rhs) { return new EQF(lhs,rhs); }
- }
- public static class LTF extends LT {
- public LTF(Node lhs, Node rhs) { super(lhs,rhs); }
- @Override boolean doOp(double lhs, double rhs) { return lhs < rhs; }
- @Override public boolean isFloat() { return true; }
- @Override Node copy(Node lhs, Node rhs) { return new LTF(lhs,rhs); }
- }
- public static class LEF extends LE {
- public LEF(Node lhs, Node rhs) { super(lhs,rhs); }
- @Override boolean doOp(double lhs, double rhs) { return lhs <= rhs; }
- @Override public boolean isFloat() { return true; }
- @Override Node copy(Node lhs, Node rhs) { return new LEF(lhs,rhs); }
+ @Override boolean doOp(double lhs, double rhs) { return lhs<=rhs; }
+ Node copy(Node lhs, Node rhs) { return new LE(lhs,rhs,_mode); }
}
// Unsigned less that, for range checks. Not directly user writable.
public static class ULT extends BoolNode {
- public ULT(Node lhs, Node rhs) { super(lhs,rhs); }
+ public ULT(Node lhs, Node rhs) { super(lhs,rhs,(byte)1); }
+ public ULT(Node lhs, Node rhs, byte mode) {
+ super(lhs,rhs,(byte)1);
+ assert mode==1;
+ }
+ @Override public Tag serialTag() { return Tag.ULT; }
public String op() { return "u<" ; }
public String glabel() { return "u&lt;"; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
new file mode 100644
index 00000000..cab85e44
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -0,0 +1,333 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * A Phi for bulk memory. It covers all aliases except {@link #_aliases};
+ * those aliases must be represented by parallel precise MemPhis at the same
+ * program slice.
+ */
+public class BulkMemPhiNode extends PhiNode {
+
+ private static final BitSet EMPTY = new BitSet();
+
+ // Excluded Aliases; empty means NO exclusions.
+ // Directly visible by design. Empty sets share EMPTY; splitAlias performs
+ // copy-on-first-write and removes the node from GVN before mutation.
+ public BitSet _aliases;
+
+ public BulkMemPhiNode(String label, Node... inputs) {
+ this(label,EMPTY,inputs);
+ }
+
+ private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
+ super(label, inputs);
+ _aliases = aliases;
+ }
+
+ public BulkMemPhiNode(BulkMemPhiNode phi) {
+ super(phi,phi._label);
+ _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone();
+ }
+
+ public BulkMemPhiNode(RegionNode r, Node sample) {
+ super(r,sample);
+ _aliases = EMPTY;
+ }
+
+ public boolean isSplit(int alias) { return _aliases.get(alias); }
+
+ /**
+ * Record that an alias has been split out. The graph rewrite installing
+ * its parallel precise Phi must happen before this semantic change.
+ */
+ public void splitAlias(int alias) {
+ assert alias > 1;
+ if( _aliases.get(alias) ) return;
+ unlock();
+ if( _aliases==EMPTY )
+ _aliases = new BitSet();
+ _aliases.set(alias);
+ }
+
+ @Override public Tag serialTag() { return Tag.BulkMemPhi; }
+
+ @Override
+ public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
+ IdentityHashMap<Node,Integer> anodes) {
+ super.packed(baos,strs,types,anodes);
+ baos.packed2(_aliases.cardinality());
+ for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) )
+ baos.packed2(alias);
+ }
+
+ static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ BitSet bits = new BitSet();
+ for( int i=bais.packed2(); i>0; i-- ) {
+ int alias = bais.packed2();
+ if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias);
+ bits.set(alias);
+ }
+ return new BulkMemPhiNode(label,bits.isEmpty() ? EMPTY : bits,ins);
+ }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ return t instanceof TypeMem mem ? mem :
+ t==Type.BOTTOM ? TypeMem.BOT :
+ TypeMem.TOP;
+ }
+
+ @Override
+ public Node idealize() {
+ Node progress = super.idealize();
+ if( progress != null ) return progress;
+
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || r.nIns()<=1 )
+ return null; // Input is in-progress
+ if( nOuts()==0 ) return null;
+ if( nIns() <= 2 ) return null;
+
+ // "Peek through" a MemMerge that covers this alias set on its default
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mmm && canPeek(mmm) ) {
+ setDef(i,mmm.in(1));
+ return this;
+ }
+
+ // If any input or output uses or defines a specific alias we cover,
+ // make a private MemPhi for it, and add it to the excluded list.
+ for( int i=1; i<nIns(); i++ ) {
+ int alias = inputAlias(in(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ for( int i=0; i<nOuts(); i++ ) {
+ int alias = outputAlias(out(i));
+ if( alias!=0 )
+ return slice(alias);
+ }
+
+ return null;
+ }
+
+ static public boolean checkMem(Node n, int alias) {
+ for( Node m : n._inputs )
+ // Bulk memory supplies this alias
+ if( m instanceof BulkMemPhiNode bulk ) {
+ if( bulk._aliases.get(alias) )
+ return false;
+ } else if( m instanceof MemMergeNode mmm &&
+ mmm.in(1) instanceof BulkMemPhiNode bulk &&
+ bulk._aliases.get(alias) &&
+ (alias >= mmm.nIns() || mmm.in(alias)==null) )
+ return false;
+ return true;
+ }
+
+
+ // True if the MemMerge has no precise slice still covered by this Phi.
+ private boolean canPeek(MemMergeNode mmm) {
+ return missingAlias(mmm,false)==0;
+ }
+
+ // Return an alias required by an input but still covered by this Phi, or 0.
+ private int inputAlias(Node n) {
+ return switch(n) {
+ case ParmNode p -> { assert p._idx==1; yield 0; }
+ case ScopeNode scope -> 0;
+ case ReturnNode ret -> 0;
+ case ProjNode proj -> 0;
+ case ConstantNode con -> {
+ assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
+ yield 0;
+ }
+ case MemMergeNode mmm -> missingAlias(mmm,false);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Return an alias required by a user but still covered by this Phi, or 0.
+ private int outputAlias(Node use) {
+ // This decision depends backwards on an immediate user's alias/set.
+ // Normal graph-neighbor enqueueing flows from defs to uses, so retain
+ // an explicit forward dependency to revisit this Phi when the user
+ // sharpens.
+ use = addDepForwards(use);
+ return switch(use) {
+ case ScopeNode scope -> 0;
+ case ParmNode parm -> 0;
+ case MemMergeNode mmm -> mmm.in(1)==this ? missingAlias(mmm,true) : 0;
+ case BulkMemPhiNode bulk -> missingAlias(bulk);
+ case MemOpNode mem -> unsplit(mem._alias);
+ case MemPhiNode phi -> unsplit(phi._alias);
+ case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
+ default -> {
+ assert use instanceof ReturnNode || use instanceof CallNode
+ : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
+ yield 0;
+ }
+ };
+ }
+
+ // Alias zero/one is bulk, and an already excluded precise alias cannot
+ // trigger another split of this Phi.
+ private int unsplit(int alias) {
+ return alias <= 1 || _aliases.get(alias) ? 0 : alias;
+ }
+
+ // First alias excluded by the neighboring bulk Phi but not by this one.
+ private int missingAlias(BulkMemPhiNode bulk) {
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( !_aliases.get(alias) )
+ return alias;
+ 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) {
+ for( int alias=2; alias<mmm.nIns(); alias++ )
+ if( mmm.in(alias)!=null &&
+ mmm.alias(alias)!=mmm.in(1) &&
+ (!user || mmm.in(alias)!=this) &&
+ !_aliases.get(alias) )
+ return alias;
+ return 0;
+ }
+
+ // Slice out given alias
+ private MemMergeNode slice( int alias ) {
+ assert !_aliases.get(alias);
+ assert !hasDup(alias);
+ // Inputs are the same; MemPhi will sharpen his own inputs
+ MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
+ for( int i=0; i<nIns(); i++ ) mphi.addDef(in(i));
+ Node mem = mphi.peephole();
+ assert ((TypeMem)mem._type)._alias==alias;
+ // The precise Phi is a new alias-sensitive user of every memory
+ // input. Revisit those defs: a predecessor BulkMemPhi may now need
+ // to split this alias even though none of its inputs or types changed.
+ for( int i=1; i<mphi.nIns(); i++ )
+ CodeGen.CODE.add(mphi.in(i));
+
+ BitSet aliases = ((BitSet)_aliases.clone());
+ aliases.set(alias);
+ BulkMemPhiNode bphi = new BulkMemPhiNode(_label,aliases);
+ for( int i=0; i<nIns(); i++ )
+ bphi.addDef(in(i));
+ // Installing bphi creates a new bulk user with a different exclusion
+ // set. Queue the original memory inputs before bphi itself can peep
+ // away and hide those neighbor relationships.
+ for( int i=1; i<bphi.nIns(); i++ )
+ CodeGen.CODE.add(bphi.in(i));
+ Node bulk = bphi.peephole();
+ CodeGen.CODE.add(bulk);
+ CodeGen.CODE.add(mphi);
+ return aggregate(bulk,alias,mem);
+ }
+
+ // Build aggregate memory over a bulk Phi, preserving every precise slice
+ // which the bulk has already excluded. The named alias is newly supplied
+ // (or replaces its old slice).
+ MemMergeNode aggregate(Node bulk, int alias, Node precise) {
+ MemMergeNode mmm = new MemMergeNode(false,null,bulk);
+ for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
+ if( old != alias )
+ mmm.alias(old,precisePhi(old));
+ mmm.alias(alias,precise);
+ assert checkMerge(mmm);
+ mmm.init();
+ // Slicing changes representation, not the set of values in memory.
+ // Newly exposed precise inputs can carry conservative bulk escape
+ // summaries; do not let those invent escapes absent from this Phi.
+ if( _type instanceof TypeMem old && mmm._type instanceof TypeMem mem )
+ mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz,
+ mem._final,old._escFs,old._escAs);
+ return mmm;
+ }
+
+ // Find the precise Phi parallel to this bulk Phi at the same merge.
+ private Node precisePhi(int alias) {
+ Node found = null;
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias ) {
+ assert found==null;
+ found = mphi;
+ }
+ // The parallel Phi may already have collapsed to a common input. Do
+ // not recover it from downstream MemMerges: they are distinct program
+ // points and can legitimately carry different values for this alias.
+ // Reconstruct from this Phi's merge inputs instead. MemPhi selects
+ // the alias from each input MemMerge and collapses when, for example,
+ // the alias is loop-invariant here.
+ if( found==null ) {
+ MemPhiNode mphi = new MemPhiNode("$"+alias,alias);
+ mphi.addDef(region());
+ // Publishing the Phi on the Region makes it visible to recursive
+ // precisePhi lookups before all loop inputs have been installed.
+ // Give that recursion a valid lattice value until peephole computes
+ // the completed precise-memory Phi.
+ mphi.setType(TypeMem.BOT.makeFrom(alias));
+ for( int i=1; i<nIns(); i++ )
+ mphi.addDef(preciseInput(in(i),alias));
+ found = mphi.peephole();
+ // A newly reconstructed precise Phi also creates new users of
+ // its memory inputs. In particular, a BulkMemPhi input may now
+ // need to split this alias, a backwards (user-to-def) change not
+ // covered by processing the new Phi itself.
+ for( int i=1; i<mphi.nIns(); i++ )
+ CodeGen.CODE.add(mphi.in(i));
+ CodeGen.CODE.add(found);
+ }
+ return found;
+ }
+
+ // Select an alias already split out of a predecessor's bulk memory.
+ private Node preciseInput(Node mem, int alias) {
+ if( mem instanceof MemMergeNode mmm )
+ return mmm.alias(alias);
+ if( mem instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) )
+ return bulk.precisePhi(alias);
+ return mem;
+ }
+
+ // Every alias excluded by a bulk default must be supplied explicitly.
+ static boolean checkMerge(MemMergeNode mmm) {
+ if( !(mmm.in(1) instanceof BulkMemPhiNode bulk) ) return true;
+ for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) )
+ if( alias >= mmm.nIns() || mmm.in(alias)==null )
+ return false;
+ return true;
+ }
+
+ private boolean hasDup( int alias ) {
+ for( Node use : region().outs() )
+ if( use instanceof MemPhiNode mphi && mphi._alias==alias )
+ return true;
+ return false;
+ }
+
+ @Override public boolean eq(Node n) {
+ return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n);
+ }
+
+ @Override int hash() { return _aliases.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index b9c70e5f..2b668da8 100644
--- a/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -1,13 +1,11 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
+
import java.util.BitSet;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.IdentityHashMap;
/** Control Flow Graph Nodes
* <p>
@@ -100,8 +98,10 @@ public abstract class CFGNode extends Node {
// Find nearest enclosing FunNode
public FunNode fun() {
CFGNode cfg = this;
- while( !(cfg instanceof FunNode fun) )
+ while( !(cfg instanceof FunNode fun) ) {
+ if( cfg==null ) return null;
cfg = cfg.idom();
+ }
return fun;
}
@@ -112,13 +112,13 @@ public abstract class CFGNode extends Node {
public LoopTree _ltree;
public int _pre; // Pre-order numbers for loop tree finding
- static class LoopTree {
- LoopTree _par;
- final LoopNode _head;
- int _depth;
+ public static class LoopTree {
+ public LoopTree _par;
+ public final LoopNode _head;
+ public int _depth;
LoopTree(LoopNode head) { _head = head; }
- @Override public String toString() { return "LOOP"+_head._nid; }
- int depth() {
+ @Override public String toString() { return label(_head); }
+ public int depth() {
return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
}
}
@@ -127,73 +127,87 @@ public abstract class CFGNode extends Node {
// Tag all CFG Nodes with their containing LoopNode; LoopNodes themselves
// also refer to *their* containing LoopNode, as well as have their depth.
// Start is a LoopNode which contains all at depth 1.
- public void buildLoopTree(StartNode start, StopNode stop) {
- // Unlink all linked calls. This can remove RPC constants which
- // shuffled the StartNode outputs so requires a while loop.
- boolean done=false;
- while(!done) {
- done = true;
- for( Node use : start._outputs )
- if( use instanceof FunNode fun )
- for( Node c : fun._inputs )
- if( c instanceof CallNode call )
- { call.unlink_all(); done=false; }
+ public void buildLoopTree( Ary<FunNode> funs, StopNode stop) {
+ _ltree = stop._ltree = CodeGen.CODE.XCTRL._ltree = new LoopTree((StartNode)this);
+ Ary<FunNode> work = new Ary<>(FunNode.class);
+
+ BitSet post = new BitSet();
+ int pre = 1;
+ _pre = pre++;
+ for( FunNode fun : funs ) {
+ if( fun!=null && !fun.isDead() && !fun._type.isHigh() ) {
+ work.push(fun);
+ if(fun.in(1) instanceof StartCUNode start && start._pre==0 ) {
+ start._pre = pre++;
+ start._ltree = _ltree;
+ post.set(start._nid);
+ }
+ };
}
- _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
- _bltWalk(2,null,stop, new BitSet());
+ for( int i=0; i<work._len; i++ )
+ pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
}
- int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
+
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
// Pre-walked?
if( _pre!=0 ) return pre;
_pre = pre++;
// Pre-walk
for( Node use : _outputs )
- if( use instanceof CFGNode usecfg && !skip( usecfg ) )
- pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post );
-
- // Post-order work: find innermost loop
- LoopTree inner = null, ltree;
- for( Node use : _outputs ) {
- if( !(use instanceof CFGNode usecfg) ) continue;
- if( skip(usecfg) ) continue;
- if( usecfg._type == Type.XCONTROL || // Do not walk dead control
- usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs
- continue;
- // Child visited but not post-visited?
- if( !post.get(usecfg._nid) ) {
- // Must be a backedge to a LoopNode then
- ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
- } else {
- // Take child's loop choice, which must exist
- ltree = usecfg._ltree;
- // If falling into a loop, use the target loop's parent instead
- if( ltree._head == usecfg ) {
- if( ltree._par == null )
- // This loop never had an If test choose to take its
- // exit, i.e. it is a no-exit infinite loop.
- ltree._par = ltree._head.forceExit(fun,stop)._ltree;
- ltree = ltree._par;
- }
+ if( use instanceof CFGNode usecfg ) {
+ if( skip( usecfg ) ) {
+ if( usecfg._pre==0 && usecfg instanceof FunNode fun2 ) work.add( fun2 );
+ } else
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post, work );
}
- // Sort inner loops. The decision point is some branch far removed
- // from either loop head OR either backedge so requires pre-order
- // numbers to figure out innermost.
- if( inner == null ) { inner = ltree; continue; }
- if( inner == ltree ) continue; // No change
- LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
- inner = ltree._head._pre > inner._head._pre ? ltree : inner;
- inner._par = outer;
+
+ if( this instanceof ReturnNode ret ) {
+ ret._ltree = stop._ltree;
+ } else {
+ // Post-order work: find innermost loop
+ LoopTree inner = null, ltree;
+ for( Node use : _outputs ) {
+ if( !(use instanceof CFGNode usecfg) ) continue;
+ if( skip(usecfg) ) continue;
+ if( usecfg._type == Type.XCONTROL || // Do not walk dead control
+ usecfg._type == TypeTuple.IF_NEITHER ) // Nor dead IFs
+ continue;
+ // Child visited but not post-visited?
+ if( !post.get(usecfg._nid) ) {
+ // Must be a backedge to a LoopNode then
+ ltree = usecfg._ltree = new LoopTree((LoopNode)usecfg);
+ } else {
+ // Take child's loop choice, which must exist
+ ltree = usecfg._ltree;
+ // If falling into a loop, use the target loop's parent instead
+ if( ltree._head == usecfg ) {
+ if( ltree._par == null )
+ // This loop never had an If test choose to take its
+ // exit, i.e. it is a no-exit infinite loop.
+ ltree._par = ltree._head.forceExit(fun,stop)._ltree;
+ ltree = ltree._par;
+ }
+ }
+ // Sort inner loops. The decision point is some branch far removed
+ // from either loop head OR either backedge so requires pre-order
+ // numbers to figure out innermost.
+ if( inner == null ) { inner = ltree; continue; }
+ if( inner == ltree ) continue; // No change
+ LoopTree outer = ltree._head._pre > inner._head._pre ? inner : ltree;
+ inner = ltree._head._pre > inner._head._pre ? ltree : inner;
+ inner._par = outer;
+ }
+ // Set selected loop
+ if( inner!=null )
+ _ltree = inner;
}
- // Set selected loop
- if( inner!=null )
- _ltree = inner;
// Tag as post-walked
post.set(_nid);
return pre;
}
- private boolean skip(CFGNode usecfg) {
+ public boolean skip(CFGNode usecfg) {
// Only walk control users that are alive.
// Do not walk from a Call to linked Fun's.
return usecfg instanceof XCtrlNode ||
@@ -201,8 +215,13 @@ public abstract class CFGNode extends Node {
(this instanceof ReturnNode && usecfg instanceof CallEndNode);
}
-
- public String label( CFGNode target ) {
- return (target instanceof LoopNode ? "LOOP" : "L")+target._nid;
+ public static String label( CFGNode target ) {
+ return switch(target) {
+ case StartNode start -> start.label();
+ case FunNode fun -> fun.label();
+ case LoopNode loop -> "LOOP"+target._nid;
+ default -> "L"+target._nid;
+ };
}
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index d18f779b..b74619a3 100644
--- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -1,13 +1,16 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeTuple;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public class CProjNode extends CFGNode {
+public class CProjNode extends CFGNode implements Proj {
// Which slice of the incoming multipart value
public int _idx;
@@ -21,6 +24,14 @@ public class CProjNode extends CFGNode {
_label = label;
}
public CProjNode(CProjNode c) { super(c); _idx = c._idx; _label = c._label; }
+ @Override public Tag serialTag() { return Tag.CProj; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(_idx);
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs ) {
+ return new CProjNode(null, bais.packed1(), strs[bais.packed2()] );
+ }
@Override public String label() { return _label; }
@@ -40,13 +51,13 @@ public class CProjNode extends CFGNode {
public Node idealize() {
if( ctrl()._type instanceof TypeTuple tt ) {
if( tt._types[_idx]==Type.XCONTROL )
- return Parser.XCTRL; // We are dead
+ return CodeGen.CODE.XCTRL; // We are dead
if( ctrl() instanceof IfNode && tt._types[1-_idx]==Type.XCONTROL ) // Only true for IfNodes
return ctrl().in(0); // We become our input control
}
// Flip a negating if-test, to remove the not
- if( ctrl() instanceof IfNode iff && addDep(iff.pred()) instanceof NotNode not )
+ if( ctrl() instanceof IfNode iff && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not )
return new CProjNode(new IfNode(iff.ctrl(),not.in(1)).peephole(),1-_idx,_idx==0 ? "False" : "True");
// Copy of some other input
@@ -65,4 +76,9 @@ public class CProjNode extends CFGNode {
@Override
int hash() { return _idx; }
+ @Override public int idx() { return _idx; }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
index d26ea33b..05707132 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
@@ -14,13 +14,15 @@ public class CallEndMach extends CallEndNode implements MachNode {
_retMask = code._mach.retMask(call().tfp());
// Kill mask is all caller-saves, and any mirror stack slots for args
// in registers.
- RegMaskRW kills = code._callerSave.copy();
+ RegMaskRW kills = (call().external() ? code._cCallerSave : code._callerSave).copy();
// Start of stack slots
int maxReg = code._mach.regs().length;
// Incoming function arg slots, all low numbered in the RA
int fslot = fun()._maxArgSlot;
// Killed slots for this calls outgoing args
- int xslot = code._mach.maxArgSlot(call().tfp());
+ int xslot = call().external()
+ ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv)
+ : code._mach.maxArgSlot(call().tfp());
_xslot = (maxReg+fslot)+xslot;
for( int i=0; i<xslot; i++ )
kills.set((maxReg+fslot)+i);
diff --git a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index a7e50c39..8e7f47f3 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -1,9 +1,13 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.*;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* CallEnd
@@ -11,16 +15,42 @@ import java.util.BitSet;
public class CallEndNode extends CFGNode implements MultiNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- private boolean _folding;
+ boolean _folding;
public final TypeRPC _rpc;
- public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); }
+ public CallEndNode(CallNode call, TypeRPC rpc) {
+ super(new Node[]{call});
+ _rpc = rpc;
+ }
public CallEndNode(CallEndNode cend) { super(cend); _rpc = cend._rpc; }
+ public CallEndNode(TypeRPC rpc) {
+ super(new Node[0]);
+ _rpc = TypeRPC.constant(CodeGen.CODE._rpcs.next(rpc.rpc()));
+ }
+ private CallEndNode(int nIns, TypeRPC rpc) {
+ super(new Node[nIns]);
+ _rpc = rpc;
+ }
+ @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());
+ // Linked CallEnds depend on Return types which depend on CallEnds;
+ // break the cycle
+ baos.packed2(types.get(_type));
+ baos.packed2(types.get(_rpc));
+ }
+ static Node make( BAOS bais, Type[] types ) {
+ int nIns = bais.packed1();
+ Type type = types[bais.packed2()];
+ Node cend = new CallEndNode(nIns,(TypeRPC)types[bais.packed2()]);
+ cend._type = type;
+ return cend;
+ }
- @Override public String label() { return "CallEnd"; }
@Override public boolean blockHead() { return true; }
public CallNode call() { return (CallNode)in(0); }
+ boolean folding() { return _folding; }
@Override public CFGNode idom(Node dep) {
// Folding the idom is the one inlining Return
@@ -40,80 +70,100 @@ public class CallEndNode extends CFGNode implements MultiNode {
@Override
public Type compute() {
if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL )
- return TypeTuple.RET.dual();
- // Mid-fold, just take the one single callers' return type
- if( _folding ) return in(1)._type;
+ return TypeTuple.STATE.dual();
// Grab the TFP and use the functions declared return type.
+ // If the call.fptr() is a FRef, return will be BOTTOM.
Type ftype = addDep(call.fptr())._type;
- Type ret = ftype.isHigh() ? Type.TOP : Type.BOTTOM;
- if( ftype instanceof TypeFunPtr tfp ) {
- ret = tfp.ret();
- // Here, if I can figure out I've found *all* callers, then I can meet
- // across the linked returns and join with the function return type.
- if( 1+tfp.nfcns() == nIns() ) { // A linked function for every concrete function
+ if( !(ftype instanceof TypeFunPtr tfp) )
+ return ftype.isHigh() ? TypeTuple.STATE.dual() : TypeTuple.STATE;
+ // Mid-fold, just take the one single callers' return type
+ if( _folding ) {
+ TypeTuple tt = (TypeTuple)in(1)._type;
+ return tt.makeFrom(2,tt.ret().join(tfp._ret));
+ }
+
+ // Here, if I can figure out I've linked *all* callers, then I can meet
+ // across the linked returns and join with the function return type.
+
+ // External functions have an exact FIDX but deliberately no local
+ // FunNode/Return edge. They conservatively crush public memory and
+ // produce their declared return type. A missing non-external target
+ // is still an unresolved/error call.
+ int externs = 0;
+ if( !XInt.isHigh(tfp.fidxs()) )
+ for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) )
+ if( CodeGen.CODE.externFunc(fidx)!=null )
+ externs++;
+ boolean hasExtern = externs != 0;
+
+ // If we have an error-call, e.g. wrong args, then we never link.
+ // Pre-Opto this looks like a missing target fcn, and we assume it will
+ // appear later - meanwhile, we use a conservative approx of memory
+ // effects.
+ if( (nIns()-1)+externs < tfp.nfcns() ) {
+ // If before SCCP, we might call extras or also the unknown target.
+ StartNode start = CodeGen.CODE._start;
+ addDep(start);
+ Type tmem = start._type instanceof TypeTuple tt ? tt._types[1] : (start._type.isHigh() ? TypeMem.TOP : TypeMem.BOT);
+ Type ret = tfp.ret();
+ // If during Opto, assume call won't be called and thus won't return anything.
+ if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
ret = Type.TOP;
- for( int i=1; i<nIns(); i++ ) {
- Type tret = in(i)._type instanceof TypeTuple rtt ? rtt.ret() : in(i)._type;
- ret = ret.meet(tret);
- }
- }
+ return TypeTuple.make(Type.CONTROL,tmem,ret);
}
- return TypeTuple.make(call._type,TypeMem.BOT,ret);
+
+ // A linked function for every concrete function
+ TypeTuple state = hasExtern
+ ? TypeTuple.make(Type.CONTROL,TypeMem.BOT,tfp.ret())
+ : TypeTuple.STATE.dual();
+ for( int i=1; i<nIns(); i++ )
+ state = (TypeTuple)state.meet(in(i)._type);
+ // At least as good as the TFP
+ return state.makeFrom(2,state.ret().join(tfp.ret()));
}
@Override
public Node idealize() {
- // Trivial inlining: call site calls a single function; single function
- // is only called by this call site.
+ // Worklist-based inlining. Cannot inline if folding, or calling
+ // multiple targets or no CallNode (malformed because dying).
if( !_folding && nIns()==2 && in(0) instanceof CallNode call ) {
Node fptr = call.fptr();
- if( fptr.nOuts() == 1 && // Only user is this call
- fptr instanceof ConstantNode && // We have an immediate call
+ if( fptr.isConst() && // We have an immediate call
// Function is being called, and its not-null
- fptr._type instanceof TypeFunPtr tfp && tfp.notNull() &&
+ fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && !tfp.isHigh() &&
// Arguments are correct
- call.err()==null ) {
+ call.nargs()==tfp.nargs() && goodArgs(call,tfp) ) {
ReturnNode ret = (ReturnNode)in(1);
FunNode fun = ret.fun();
- // Expecting Start, and the Call
- if( fun.nIns()==3 ) {
- assert fun.in(1) instanceof StartNode && fun.in(2)==call;
- // Disallow self-recursive inlining (loop unrolling by another name).
- // Disallow if still folding other things, as it makes other
- // dependency checks carry long chains of half-folded calls.
- CFGNode idom = call;
- while( true ) {
- idom = idom.idom();
- if( idom instanceof FunNode ) break;
- if( idom instanceof CallEndNode cend && cend._folding ) break;
- }
- // Inline?
- if( idom instanceof FunNode fun2 && fun2 != fun && !fun2._folding ) {
- // Trivial inline: rewrite
- _folding = true;
- // Rewrite Fun so the normal RegionNode ideal collapses
- fun._folding = true;
- fun.setDef(1,Parser.XCTRL); // No default/unknown StartNode caller
- fun.setDef(2,call.ctrl()); // Bypass the Call;
- fun.ret().setDef(3,null); // Return is folding also
- CodeGen.CODE.addAll(fun._outputs);
- // Repeat defs 1 layer down, for users of Parm (Phis)
- for( Node parm : fun._outputs )
- if( parm instanceof ParmNode )
- CodeGen.CODE.addAll(parm._outputs);
-
- // Inlining immediately blows all cache idepth fields past the inline point.
- // Bump the global version number invalidating them en-masse.
- CodeGen.CODE.invalidateIDepthCaches();
- return this;
- } else {
- addDep(idom);
- }
- } else {
- addDep(fun);
+ int isTrivial = trivialInlining( fptr, fun );
+
+ // Encouraged inlining because small size and constructor.
+ int maxSize = fun._name!=null && fun.isInit() && !fun.isClz() ? 200 : 100;
+ if( isTrivial==1 && fun._approxUIDs < maxSize ) {
+ assert fun.sig().isa(tfp);
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ CodeGen.CODE.add(fun);
+ // Remove the existing function linkage
+ call.unlink_all();
+ // Clone the function body
+ FunNode fun2 = fun.copyBody();
+ // Call uses the unique new function
+ Node fptr2 = new FunPtrNode(fun2.sig(),CodeGen.CODE._start,fun2.ret()).peephole();
+ call.setDef(call.nIns()-1,fptr2);
+ // Link to the new function
+ call.link(fun2);
+ fun = fun2;
+ assert trivialInlining( fptr2, fun2 )==0;
+ isTrivial = 0;
}
- } else { // Function ptr has multiple users (so maybe multiple call sites)
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ if( isTrivial==0 )
+ return doTrivialInlining(fun);
+
+ } else { // Function not (yet) a constant function pointer
addDep(fptr);
}
}
@@ -121,7 +171,105 @@ public class CallEndNode extends CFGNode implements MultiNode {
return null;
}
+ boolean goodArgs( CallNode call, TypeFunPtr tfp ) {
+ for( int i=0; i<tfp.nargs(); i++ ) {
+ Node arg = call.arg(i+2);
+ // The graph's function-pointer type can still contain open forward
+ // references here. Compare against its recursively upgraded form,
+ // so deep mutability requirements participate before inlining.
+ Type formal = tfp.arg(i).upgradeType(Parser.TYPES);
+ if( !arg._type.isa(formal) )
+ { addDep(arg); return false; }
+ }
+ return true;
+ }
+
+
+ // Check for trivial inlining: call only calls fun; fun only called by
+ // call. Returns 0 for trivial, +1 for not-trivial because fptr/fun, and
+ // -1 for not-trivial because idoms.
+ public int trivialInlining( Node fptr, FunNode fun ) {
+ // Heuristic forced inlining off via name
+ if( fun._name != null &&
+ fun._name.endsWith("_noInline") )
+ return -1;
+
+ if( !fun.sig().isa(fptr._type) )
+ return -1; // Stall until these align
+
+ // Disallow self-recursive inlining (loop unrolling by another name).
+ for( int i=1; i < fun.nIns(); i++ )
+ if( fun.cfg(i).fun()==fun ) // Check for linked call input inside "fun"
+ { addDep(fun); return -1; }
+
+ CFGNode idom = call(), prior = this;
+ while( !(idom instanceof FunNode) ) {
+ if( idom==null ) {
+ addDep(prior);
+ if( prior instanceof RegionNode && !(prior instanceof StartNode) )
+ for( int i=1; i<prior.nIns(); i++ )
+ addDep(prior.in(i));
+ return -1;
+ }
+ prior = idom;
+ idom = idom.idom();
+ }
+
+
+ // If the *inlined* function is mid-collapse, also do not inline (yet)
+ idom = fun.ret();
+ while( idom != fun ) {
+ if( idom==null ) return -1; // Forced off, half-folded call
+ if( idom instanceof FunNode fun2 )
+ { addDep(fun2); return -1; }
+ if( idom instanceof CallEndNode cend && cend._folding )
+ { addDep(cend); return -1; }
+ idom = idom.idom();
+ }
+
+ // Only fun user is this call
+ if( fun.nIns() > 2 ) { addDep(fun); return 1; }
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ assert fun.in(1)==call();
+
+ return 0;
+ }
+
+
+ // Do trivial inlining. Inlining does not need to clone code, merely
+ // triggers folding the Call/Fun and Return/CallEnd away.
+ private Node doTrivialInlining( FunNode fun ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // Add the size heuristic to grow caller, preventing self-recursive
+ // functions from endlessly inlining.
+ fun()._approxUIDs += fun._approxUIDs;
+ // Trivial inline: rewrite
+ _folding = true;
+ // Rewrite Fun so the normal RegionNode ideal collapses
+ fun._folding = true;
+ fun.setDef(1,call().ctrl());// Bypass the Call;
+ fun.ret().setDef(3,null); // Return is folding also
+
+ CodeGen.CODE.addAll(fun._outputs);
+ // Repeat defs 1 layer down, for users of Parm (Phis)
+ for( Node parm : fun._outputs )
+ if( parm instanceof ParmNode )
+ CodeGen.CODE.addAll(parm._outputs);
+
+ // Inlining immediately blows all cache idepth fields past the inline point.
+ // Bump the global version number invalidating them en-masse.
+ CodeGen.CODE.invalidateIDepthCaches();
+ return this;
+ }
+
+
@Override public Node pcopy(int idx) {
return _folding ? in(1).in(idx) : null;
}
+
+ @Override public Node copy() {
+ return _folding ? super.copy() : new CallEndNode(_rpc);
+ }
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CallNode.java b/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 47dfdf19..f2825650 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -1,12 +1,16 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.type.XInt;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Call
@@ -18,8 +22,9 @@ public class CallNode extends CFGNode {
public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
public CallNode(CallNode call) { super(call); _loc = call._loc; }
-
- @Override public String label() { return "Call"; }
+ @Override public Tag serialTag() { return Tag.Call; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { return new CallNode(null,new Node[bais.packed1()]); }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
String fname = name();
@@ -33,7 +38,7 @@ public class CallNode extends CFGNode {
}
public String name() {
if( fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- FunNode fun = CodeGen.CODE.link(tfp);
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
if( fun !=null ) return fun._name;
if( fptr() instanceof ExternNode ex ) return ex._extern;
}
@@ -97,14 +102,11 @@ public class CallNode extends CFGNode {
Node progress = null;
if( fptr()._type instanceof TypeFunPtr tfp && tfp.nargs() == nargs() ) {
// If fidxs is negative, then infinite unknown functions
- long fidxs = tfp.fidxs();
- if( fidxs > 0 ) {
- // Wipe out the return which matching in the linker table
- // Walk the (63 max) bits and link
- for( ; fidxs!=0; fidxs = TypeFunPtr.nextFIDX(fidxs) ) {
- int fidx = Long.numberOfTrailingZeros(fidxs);
- TypeFunPtr tfp0 = tfp.makeFrom(fidx);
- FunNode fun = CodeGen.CODE.link(tfp0);
+ int[] fidxs = tfp.fidxs();
+ if( !XInt.isHigh(fidxs) ) {
+ // Walk the bits and link
+ for( int fidx = XInt.next(fidxs,0); fidx > 0; fidx = XInt.next(fidxs,fidx) ) {
+ FunNode fun = CodeGen.CODE.link(fidx);
if( fun!=null && !fun._folding && !linked(fun) )
progress = link(fun);
}
@@ -128,30 +130,43 @@ public class CallNode extends CFGNode {
fun.addDef(this);
for( Node use : fun._outputs )
if( use instanceof ParmNode parm )
- parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx));
+ parm.addDef(parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx));
// Call end points to function return
CodeGen.CODE.add(cend()).addDef(fun.ret());
return this;
}
+ // Unlink a single function
+ public void unlink( FunNode fun, int path ) {
+ assert linked(fun);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode )
+ use.delDef(path);
+ fun.delDef(path);
+ CodeGen.CODE.add(fun);
+ CallEndNode cend = cend();
+ ReturnNode ret = fun.ret().keep();
+ cend.delDef(cend._inputs.find(ret));
+ CodeGen.CODE.add(cend);
+ assert !linked(fun);
+ ret.unkeep();
+ }
+
+
// Unlink all linked functions
public void unlink_all() {
for( int i=0; i<_outputs._len; i++ )
if( out(i) instanceof FunNode fun ) {
- assert linked(fun);
- int idx = fun._inputs.find(this);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode )
- use.delDef(idx);
- fun.delDef(idx);
- cend().delDef(cend()._inputs.find(fun.ret()));
- assert !linked(fun);
+ int path = fun._inputs.find(this);
+ unlink(fun,path);
i--;
}
}
@Override
public Parser.ParseException err() {
+ if( fptr()._type == Type.BOTTOM )
+ return null; // Wrong, but failing earlier
if( !(fptr()._type instanceof TypeFunPtr tfp) )
throw Utils.TODO();
if( !tfp.notNull() )
@@ -162,11 +177,13 @@ public class CallNode extends CFGNode {
// Check for args
for( int i=0; i<tfp.nargs(); i++ )
if( !arg(i+2)._type.isa(tfp.arg(i)) ) {
- cend().addDep(arg(i+2)); // Upgrading arg type may allow inlining
- return Parser.error( "Argument #" + i + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
+ // Constructors return private memory and carry hidden self and
+ // selfMem arguments ahead of the source-level arguments.
+ int argno = i - (tfp.ret() instanceof TypeMem ? 2 : 0);
+ return Parser.error( "Argument #" + argno + " isa " + arg(i+2)._type + ", but must be a " + tfp.arg(i), _loc );
}
- if( tfp.fidxs() < 0 )
+ if( tfp.isHigh() )
throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
return null;
diff --git a/src/main/java/com/seaofnodes/simple/node/CastNode.java b/src/main/java/com/seaofnodes/simple/node/CastNode.java
deleted file mode 100644
index 6dc8e9fe..00000000
--- a/src/main/java/com/seaofnodes/simple/node/CastNode.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import com.seaofnodes.simple.util.Utils;
-import java.util.BitSet;
-
-// Upcast (join) the input to a t. Used after guard test to lift an input.
-// Can also be used to make a type-assertion if ctrl is null.
-public class CastNode extends Node {
- public Type _t;
- public CastNode(Type t, Node ctrl, Node in) {
- super(ctrl, in);
- _t = t;
- setType(compute());
- }
-
- public CastNode(CastNode c) {
- super(c.in(0), c.in(1)); // Call parent constructor
- this._t = c._t; // Copy the Type field
- setType(compute()); // Ensure type is recomputed
- }
-
- @Override public String label() { return "("+_t.str()+")"; }
-
- @Override
- public String uniqueName() { return "Cast_" + _nid; }
-
- @Override public boolean isPinned() { return true; }
- @Override public boolean isConst() { return true; }
-
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- return in(1)._print0(sb.append(label()), visited);
- }
-
- @Override
- public Type compute() {
- // Cast array to int
- Type t1 = in(1)._type;
- if( _t == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() )
- return _t;
- // Freeze if the join is high but both inputs are low
- Type tj = t1.join(_t);
- if( tj.isHigh() && !t1.isHigh() )
- return _type==null ? _t : _type;
-
- return tj;
- }
-
- @Override
- public Node idealize() {
- return in(1)._type.isa(_t) ? in(1) : null;
- }
-
- @Override
- public boolean eq(Node n) {
- CastNode cast = (CastNode)n; // Contract
- return _t==cast._t;
- }
-
- @Override
- int hash() { return _t.hashCode(); }
-
- @Override
- public Parser.ParseException err() {
- // Has a condition to test, so OK
- if( in(0) != null ) return null;
- // No condition to test, so this must optimize away
- return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _t.str(), null );
- }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/CastMach.java b/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java
index e45898f5..9943c7b4 100644
--- a/src/main/java/com/seaofnodes/simple/node/CastMach.java
+++ b/src/main/java/com/seaofnodes/simple/node/CheckCastMach.java
@@ -3,17 +3,18 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.Encoding;
import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.SB;
/**
* Cast a pointer to read-only
*/
-public class CastMach extends CastNode implements MachNode {
- public CastMach( CastNode n ) { super(n); }
+public class CheckCastMach extends CheckCastNode implements MachNode {
+ public CheckCastMach( CheckCastNode n ) { super(n); }
@Override public String op() { return label(); }
@Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
@Override public RegMask outregmap() { return RegMask.FULL; }
- @Override public int twoAddress( ) { return 1; }
+ @Override public int twoAddress( ) { return in(1)._type==Type.BOTTOM ? 0 : 1; }
@Override public void encoding( Encoding enc ) { }
@Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java b/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java
new file mode 100644
index 00000000..900ee70e
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/CheckCastNode.java
@@ -0,0 +1,82 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Upcast (join) the input to a t. Used after guard test to lift an input.
+// Can also be used to make a type-assertion if ctrl is null.
+public class CheckCastNode extends TypeNode {
+ public CheckCastNode(Type t, Node ctrl, Node in) { super(t,ctrl, in); }
+
+ public CheckCastNode(CheckCastNode c) {
+ super(c); // Call parent copy constructor
+ setType(compute()); // Ensure type is recomputed
+ }
+ @Override public Tag serialTag() { return Tag.CheckCast; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ }
+ static Node make( BAOS bais, Type[] types) { return new CheckCastNode(types[bais.packed2()], null, null); }
+
+ @Override public String label() { return "("+_con.str()+")"; }
+
+ @Override
+ public String uniqueName() { return "CheckCast_" + _nid; }
+
+ @Override public boolean isPinned() { return in(0)!=null; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()), visited);
+ }
+
+ @Override
+ public Type compute() {
+ // Cast array to int
+ Type t1 = in(1)._type;
+ if( _con == TypeInteger.BOT && t1 instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return _con;
+
+ // If unconditional, it must collapse (or error)
+ if( in(0)==null )
+ return _con;
+ // If conditional, return the join. The guard test ensures
+ // the join is correct.
+ return t1.join(_con);
+ }
+
+ @Override
+ public Node idealize() {
+ return in(1)._type.isa(_con) ? in(1) : null;
+ }
+
+ @Override
+ public Parser.ParseException err() {
+ // Has a condition to test, so OK
+ if( in(0) != null ) return null;
+ if( in(1)._type != Type.BOTTOM )
+ // No condition to test, so this must optimize away
+ return Parser.error( "Type " + in(1)._type.str() + " is not of declared type " + _con.str(), null );
+ // Only happens for un-initialized refs that must init.
+ // Allow uses of initializing stores, but other uses represent a
+ // partially initialized field, and should error
+ boolean bad = false;
+ StoreNode init=null;
+ for( Node use : _outputs )
+ if( (use instanceof StoreNode st && st._init && !((TypeMemPtr)st.ptr()._type)._obj._name.startsWith("class") ) )
+ init = st;
+ else if( (use instanceof StoreNode st && st.val() instanceof CheckCastNode cast && cast.in(1)._type == Type.BOTTOM) )
+ /*bogus init store in constructor*/;
+ else bad = true;
+ if( !bad ) return null;
+ if( init==null ) return null; // Error somewhere else
+ // Expecting an init store
+ TypeMemPtr ptr = (TypeMemPtr)init.ptr()._type;
+ throw Parser.error("'"+ptr._obj._name+"' is not fully initialized, field '" + init._name + "' is only partially set in the constructor", init._loc);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
index c54da2c4..11a16d28 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -1,11 +1,14 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.type.TypeStruct;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
/**
* A field offset. This becomes a constant *after* other optimizations which
@@ -14,12 +17,13 @@ import java.util.BitSet;
*/
public class ConFldOffNode extends ConstantNode {
- public final String _name; // Struct name
+ public TypeStruct _ts; // Struct holding field
public final String _fname; // Field name
- public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; }
+ public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; }
+ @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
@Override public String label() {
- return _fname == " len" ? "sizeof("+_name+")" : "#"+_fname;
+ return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname;
}
@Override public String glabel() { return label(); }
@Override public String uniqueName() { return "Off_" + _nid; }
@@ -29,15 +33,41 @@ public class ConFldOffNode extends ConstantNode {
return sb.append(label());
}
+ @Override public Node idealize() {
+ // Some array offsets are known early
+ if( _ts.isAry() ) {
+ // Length always at offset zero
+ if( _fname==" len" )
+ return CodeGen.CODE.ZERO;
+ // The shift depends on the element
+ if( _fname=="<<" )
+ return Node.con(_ts.aryScale());
+ // The base depends on the layout (alignment for large elements may
+ // involve padding) and MAY depend on the length if I go for
+ // smaller array lengths.
+ }
+ return null;
+ }
+
// Convert field offset to an integer
- public Node asOffset(TypeStruct ts) {
- return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).peephole();
+ public Node asOffset() {
+ int fldx = _fname==" len" ? _ts._fields.length : _ts.find(_fname);
+ if( fldx == -1 ) return null;
+ return ConstantNode.make(TypeInteger.constant(_ts.offset(fldx))).peephole();
}
+ // Upgrade the internal type
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ boolean progress = super._upgradeType(TYPES);
+ TypeStruct ts = (TypeStruct)_ts.upgradeType(TYPES);
+ if( ts == _ts ) return progress;
+ _ts = ts;
+ return true;
+ }
@Override public boolean eq(Node n) {
ConFldOffNode off = (ConFldOffNode)n; // Invariant
- return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n);
+ return _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n);
}
- @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); }
+ @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
index 22bf03c4..feed8c5c 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -3,8 +3,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* A Constant node represents a constant value.
@@ -16,28 +19,63 @@ import java.util.BitSet;
* The Constant's value is the value stored in it.
*/
-public class ConstantNode extends Node {
- public final Type _con;
+public class ConstantNode extends TypeNode {
public ConstantNode( Type type ) {
- super(new Node[]{CodeGen.CODE._start});
- _con = _type = type;
+ this(type,false);
+ }
+ protected ConstantNode( Type type, boolean raw ) {
+ super(type,new Node[]{CodeGen.CODE._start});
+ assert raw || !(type instanceof TypeFunPtr tfp && tfp.isConstant()) :
+ "Unique function identities require a Return-linked FunPtrNode";
+ _type = type;
}
- public ConstantNode( ConstantNode con ) { super(con); _con = con._type; }
+ public ConstantNode( ConstantNode con ) { super(con); }
+ @Override public Tag serialTag() { return Tag.Con; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ }
+ static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); }
+ // Semantic constant construction through Opto. A unique internal
+ // function identity carries a lifetime edge to the function's Return.
public static Node make( Type type ) {
if( type==Type. CONTROL ) return new CtrlNode();
if( type==Type.XCONTROL ) return new XCtrlNode();
+ if( CodeGen.CODE._phase != null &&
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ return raw(type);
+ if( type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
+ if( fun != null )
+ return new FunPtrNode(tfp,CodeGen.CODE._start,fun.ret()).init();
+ // TODO: A singleton FIDX can also emerge after merging and
+ // guarding several function pointers. Preserve that identity
+ // even if its internal Return is no longer available.
+ return raw(type);
+ }
return new ConstantNode(type);
}
+ // Exact construction for deserialization and graph-incomplete machine
+ // selection. No semantic classification, compute, idealize, or GVN.
+ public static ConstantNode raw( Type type ) { return new ConstantNode(type,true); }
+ public static ConstantNode raw( ConstantNode con ) { return new ConstantNode(con); }
+
+ // Analysis input created from whole cloth, rather than a first-class
+ // runtime value. In particular, a constructor call descriptor does not
+ // own the constructor function; the class FunPtr does.
+ public static ConstantNode seed( Type type ) { return new ConstantNode(type,true); }
+
@Override public String label() { return "#"+_con; }
@Override public String glabel() { return "#"+_con.gprint(); }
@Override public String uniqueName() { return "Con_" + _nid; }
+ @Override public Node copy() { return raw(this); }
+
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
if( _con instanceof TypeFunPtr tfp && tfp.isConstant() ) {
- FunNode fun = CodeGen.CODE.link(tfp);
+ FunNode fun = CodeGen.CODE.lookupFun(tfp);
if( fun!=null && fun._name != null )
return sb.append("{ ").append(fun._name).append("}");
}
@@ -47,6 +85,4 @@ public class ConstantNode extends Node {
@Override public boolean isConst() { return true; }
@Override public Type compute() { return _con; }
@Override public Node idealize() { return null; }
- @Override public boolean eq(Node n) { return _con==((ConstantNode)n)._con; }
- @Override int hash() { return _con.hashCode(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
new file mode 100644
index 00000000..4e8e93ce
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -0,0 +1,96 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+import java.util.BitSet;
+import java.util.HashMap;
+
+// Convert a value to an authoritative destination type. The destination is
+// known structurally from a declaration; the source family might sharpen later.
+public class ConvertNode extends Node {
+ private Type _dst;
+
+ public ConvertNode(Type dst, Node val) {
+ super(null,val);
+ // Folding this conversion would replace the Return-linked FunPtrNode
+ // with an unhooked ConstantNode.
+ assert !(dst instanceof TypeFunPtr && val instanceof FunPtrNode);
+ _dst = dst;
+ }
+
+ public Type dst() { return _dst; }
+ public Node val() { return in(1); }
+
+ @Override public String label() { return "Convert_"+_dst.str(); }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return val()._print0(sb.append(label()).append("("),visited).append(")");
+ }
+
+ // The declaration fixes the result family during pessimistic parsing.
+ // Optimistic SCCP must retain stronger facts already inside that family.
+ @Override public Type compute() {
+ Type src = val()._type;
+ if( CodeGen.CODE._phase == CodeGen.Phase.Opto ) {
+ if( src.isHigh() ) return Type.TOP;
+ if( src.isa(_dst) ) return src;
+ }
+ return _dst;
+ }
+
+ @Override public Node idealize() {
+ Type src = val()._type;
+ if( src==Type.BOTTOM || src==Type.TOP ) return null;
+ if( src.isa(_dst) ) return val();
+
+ // Managed arrays are represented by an object pointer, while C wants
+ // the address of the first element. The representation cast itself is
+ // zero-code; the Add skips the array header.
+ if( _dst==TypeInteger.BOT && src instanceof TypeMemPtr tmp && tmp._obj.isAry() )
+ return new AddNode(new PtrToIntNode(val()).peephole(),Parser.off(tmp._obj,"[]"));
+
+ // Integer (and nil-as-zero) to floating point.
+ if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat )
+ return new ToFloatNode(val());
+
+ // Narrow integers produce the declared sign/zero extension.
+ if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) {
+ if( dst._min==0 )
+ return new AndNode(null,val(),con(dst._max));
+ int shift = Long.numberOfLeadingZeros(dst._max)-1;
+ Node shf = con(shift);
+ if( shf._type==TypeInteger.ZERO ) return val();
+ return new SarNode(null,new ShlNode(null,val(),shf.keep()).peephole(),shf.unkeep());
+ }
+
+ // Narrow f64 to f32.
+ if( src instanceof TypeFloat && _dst instanceof TypeFloat )
+ return new RoundF32Node(val());
+
+ // Wait for more source information, or report an error after Opto.
+ return null;
+ }
+
+ @Override boolean _upgradeType(HashMap<String,Type> TYPES) {
+ Type dst = _dst.upgradeType(TYPES);
+ if( dst==_dst ) return false;
+ unlock();
+ _dst = dst;
+ return true;
+ }
+
+ @Override public Parser.ParseException err() {
+ if( val()._type==Type.BOTTOM ||
+ val()._type instanceof TypeNil src && src.nullable() && _dst instanceof TypeNil dst && dst.notNull() ) {
+ Node out = out(0);
+ if( out instanceof PhiNode phi ) out = phi.out(0);
+ if( out instanceof StoreNode st )
+ return Parser.error( "'" + ((TypeMemPtr)st.ptr()._type)._obj._name + "' is not fully initialized, field '" + st._name + "' is only partially set in the constructor", null );
+ }
+ return Parser.error("Type "+val()._type.str()+" is not of declared type "+_dst.str(),null);
+ }
+
+ @Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; }
+ @Override int hash() { return _dst.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
index eda7d684..39c95aa4 100644
--- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -7,6 +7,7 @@ import java.util.BitSet;
public class CtrlNode extends CFGNode {
public CtrlNode() { super(CodeGen.CODE._start); }
+ @Override public Tag serialTag() { throw com.seaofnodes.simple.util.Utils.TODO(); }
@Override public String label() { return "Ctrl"; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Cctrl"); }
@Override public boolean isConst() { return true; }
diff --git a/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/src/main/java/com/seaofnodes/simple/node/DivFNode.java
deleted file mode 100644
index 0d037a30..00000000
--- a/src/main/java/com/seaofnodes/simple/node/DivFNode.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class DivFNode extends ArithFNode {
- public DivFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "DivF"; }
-
- @Override public String glabel() { return "/"; }
-
- @Override double doOp( double x, double y ) { return x/y; }
-
- @Override
- public Node idealize() {
- // Div of constant
- if( in(2)._type instanceof TypeFloat f && f.isConstant() )
- return new MulFNode(in(1),new ConstantNode(TypeFloat.constant(1.0/f.value())).peephole());
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new DivFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/DivNode.java b/src/main/java/com/seaofnodes/simple/node/DivNode.java
index 0de1c946..dcc9a27a 100644
--- a/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -2,15 +2,19 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeFloat;
import java.util.BitSet;
public class DivNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public DivNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public DivNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Div; }
- @Override public String label() { return "Div"; }
- @Override public String op() { return "//"; }
+ @Override public String op() { return _mode==1 ? "//" : "/"; }
- @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; }
+ @Override long doOp( long x, long y ) { return y==0 ? 0 : x / y; }
+ @Override double doOp(double x, double y ) { return x / y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
return TypeInteger.BOT;
}
@@ -20,9 +24,17 @@ public class DivNode extends ArithNode {
// Div of 1.
if( in(2)._type == TypeInteger.TRUE )
return in(1);
+
+ // Div of constant
+ if( _mode==2 ) {
+ if( in(2)._type == TypeFloat.constant(1.) )
+ return in(1);
+ if( in(2)._type instanceof TypeFloat f && f.isConstant() )
+ return new MulNode(in(1),ConstantNode.make(TypeFloat.constant(1.0/f.value())).peephole(),_mode);
+ }
+
return super.idealize();
}
- @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs); }
- @Override Node copyF() { return new DivFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new DivNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
new file mode 100644
index 00000000..df654291
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -0,0 +1,114 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/**
+ * Merge public and private aliases.
+ * 0 - control
+ * 1 - private pointer
+ * 2 - private alias
+ * 3 - public alias
+ */
+public class EscapeNode extends TypeNode {
+
+ public EscapeNode(Field fld, Node self, Node priv, Node pub ) {
+ super(fld,null,self,priv,pub);
+ }
+ public EscapeNode(EscapeNode esc ) {
+ super(esc); // Copy constructor
+ // Except we do not want/need self ptr past Opto.
+ // *NOTE* the weird direct stomp; this is correct because this
+ // constructor does NOT set the backedges, because it is used to
+ // copy-construct an entire graph and ALL edges will get rewritten.
+ _inputs.set(1,null);
+ _con = esc._con;
+ }
+
+ // Pointer to some private (unescaped) memory from a NewNode
+ public Node self() { return in(1); }
+ public Node priv() { return in(2); }
+ public Node pub () { return in(3); }
+ public Field fld() { return (Field)_con; }
+
+ @Override public Tag serialTag() { return Tag.Escape; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con));
+ }
+ static Node make( BAOS bais, Type[] types) {
+ return new EscapeNode((Field)types[bais.packed2()], null,null,null);
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ sb.append("Esc#").append(fld()._alias).append(" {");
+ if( self()==null ) sb.append("---");
+ else self()._print0(sb,visited).append(",");
+ priv()._print0(sb,visited).append("}, ");
+ pub ()._print0(sb,visited);
+ return sb;
+ }
+
+ @Override
+ public Type compute() {
+ if( priv()._type.isHigh() ) return TypeMem.TOP;
+ if( pub ()._type.isHigh() ) return TypeMem.TOP;
+ TypeMem mpriv = (TypeMem)priv()._type;
+ TypeMem mpub = (TypeMem)pub ()._type;
+ // Private memory comes from an allocation or other non-aliased source.
+ // Public memory comes from anywhere, might be bulk memory with a very weak mem type.
+ Field fld = fld();
+ assert mpriv._alias==1 || mpriv._alias==fld._alias;
+ assert mpub ._alias==1 || mpub ._alias==fld._alias;
+ Type tpriv = mpriv._t instanceof TypeStruct ts && ts.findAlias(fld._alias)!=-1
+ ? ts.at(ts.findAlias(fld._alias))._t
+ : mpriv._t;
+ assert !(mpub._t instanceof TypeStruct); // Never expect this, but if it starts to happen, need to optimize
+ Type tpub = mpub._t;
+
+ // t is meet of pub & priv. Pub memory can be very weak (e.g. alias 1)
+ // and we're no worse than GLB of private
+ Type t = tpriv.meet(tpub).join(fld._t);
+ int[] fidxes = XInt.meet(mpriv._escFs, mpub._escFs);
+ int[] aliases = XInt.meet(mpriv._escAs, mpub._escAs);
+ return TypeMem.make(fld._alias, t, false, false, fld._final, fidxes, aliases).escapesFrom(t);
+ }
+
+ @Override public Node idealize() {
+ // Sharpen private memory input
+ if( priv() instanceof MemMergeNode mmm ) {
+ setDef(2,mmm.alias(fld()._alias));
+ return this;
+ }
+ // Sharpen public memory input
+ if( pub() instanceof MemMergeNode mmm ) {
+ setDef(3,mmm.alias(fld()._alias));
+ return this;
+ }
+ // New allocation is dead
+ if( self()._type.isHigh() )
+ return pub();
+ return null;
+ }
+
+// Upgrade the internal type
+@Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type t = _con.upgradeType(TYPES);
+ if( t == _con ) return false;
+ unlock(); // Unlock before changing _con
+ _con = t;
+ return true;
+}
+
+ @Override public boolean eq(Node n) {
+ if( fld()._alias!=((EscapeNode)n).fld()._alias )
+ return false;
+ assert _con == ((EscapeNode)n)._con;
+ return true;
+ }
+
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/src/main/java/com/seaofnodes/simple/node/ExternNode.java
index a10bb84a..9768f41f 100644
--- a/src/main/java/com/seaofnodes/simple/node/ExternNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -1,23 +1,40 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
- A constant with external linkage.
+ A constant with external linkage.
*/
public class ExternNode extends ConstantNode {
public final String _extern;
- public ExternNode(Type t, String ex) { super(t); _extern = ex; }
+ // An exact external function identity has no local FunNode/Return to own.
+ // Its lifetime and eventual relocation are carried by the extern symbol
+ // and CodeGen's FIDX-to-extern mapping instead.
+ public ExternNode(Type t, String ex) { super(t,true); _extern = ex; }
+ @Override public Tag serialTag() { return Tag.Extern; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_con ));
+ baos.packed2( strs.get(_extern));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ return new ExternNode(types[bais.packed2()], strs[bais.packed2()]);
+ }
@Override public String label() { return "#"+_con+":"+_extern; }
@Override public String glabel() { return _con.print(new SB().p("#"), new BitSet(), true).p(":").p(_extern).toString(); }
@Override public String uniqueName() { return "Extern_" + _nid; }
@Override public boolean eq(Node n) { return this==n; }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_extern);
+ }
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
index 6093b8a6..723e0007 100644
--- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -9,18 +9,28 @@ import java.util.BitSet;
* A Forward Reference. Its any final constant, including functions. When
* the Def finally appears its plugged into the forward reference, which then
* peepholes to the Def.
+
+ * FRefs can be external references, to other Simple defined values in ELF
+ * files, or external FFI/C libs. If external, a file system lookup needs to
+ * find it in another file, and the name (one time) change to match the found
+ * location.
+ *
+ * Example, a ambiguous name "foo" is looked up externally; it might resolve
+ * to "A.B.foo" or "libc.foo" or the like; the name is changed to match.
*/
public class FRefNode extends ConstantNode {
public static final Type FREF_TYPE = Type.BOTTOM;
- public final Var _n;
- public FRefNode( Var n ) { super(FREF_TYPE); _n = n; }
+ public String _name;
+ public final Parser.Lexer _loc; // Source location
+
+ public FRefNode( String name, Parser.Lexer loc ) { super(FREF_TYPE); _name = name; _loc = loc; }
- @Override public String label() { return "FRef"+_n; }
+ @Override public String label() { return "FRef"+_name; }
@Override public String uniqueName() { return "FRef_" + _nid; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- return sb.append("FRef_").append(_n);
+ return sb.append("FRef_").append(_name);
}
@Override public Node idealize() {
@@ -28,8 +38,8 @@ public class FRefNode extends ConstantNode {
return nIns()==1 ? null : in(1);
}
- public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); }
+ public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); }
@Override public boolean eq(Node n) { return this==n; }
- @Override int hash() { return _n._idx; }
+ @Override int hash() { return _name.hashCode(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
new file mode 100644
index 00000000..3a44f588
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -0,0 +1,50 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * A field offset. This becomes a constant *after* other optimizations which
+ * determine field sizes - some fields might be dead, or hold only zeros or
+ * small values.
+ */
+
+public class FldOffNode extends TypeNode {
+ public final String _fname; // Field name
+ public final Parser.Lexer _loc; //
+ public FldOffNode( Node base, String fname, Parser.Lexer loc ) { super(TypeInteger.BOT,base); _fname = fname; _loc=loc; }
+ @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
+
+ @Override public String label() {
+ return _fname == " len" ? "sizeof()" : "#"+_fname;
+ }
+ @Override public String glabel() { return label(); }
+ @Override public String uniqueName() { return "Off_" + _nid; }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return sb.append(label());
+ }
+
+ @Override public Type compute() {
+ return Type.BOTTOM;
+ }
+
+ @Override public Node idealize() {
+ // When base() gets typed, this xforms into a ConFldOffNode
+ if( in(0)._type instanceof TypeMemPtr tmp )
+ return new ConFldOffNode(tmp._obj,_fname);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) {
+ FldOffNode off = (FldOffNode)n; // Invariant
+ return _fname.equals(off._fname) && super.eq(n);
+ }
+ @Override int hash() { return super.hash() * _fname.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/FunNode.java b/src/main/java/com/seaofnodes/simple/node/FunNode.java
index 2931b9ea..5018f5d6 100644
--- a/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -1,41 +1,68 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Encoding;
-import com.seaofnodes.simple.codegen.RegMask;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFunPtr;
-import com.seaofnodes.simple.type.TypeTuple;
+import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
public class FunNode extends RegionNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- boolean _folding;
+ public boolean _folding;
private TypeFunPtr _sig; // Initial signature
private ReturnNode _ret; // Return pointer
public String _name; // Debug name
- public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; }
+ public int _approxUIDs; // Approximate function size, used as a inlining heuristic
+ public CompUnit _compunit; // Defined in source file
+ public final boolean _extern; // External function; no code, name only
+
+ private FunNode( Parser.Lexer loc, Node[] nodes, TypeFunPtr sig, String name, CompUnit compunit, boolean ext ) {
+ super(loc,nodes);
+ _name = name;
+ _sig = sig;
+ _extern = ext;
+ _compunit = compunit;
+ }
+ public FunNode( Parser.Lexer loc, TypeFunPtr sig, String name, CompUnit compunit, Node... nodes ) { this(loc,nodes,sig,name,compunit,false); }
+ public FunNode( TypeFunPtr sig, String name, boolean ext ) {
+ this(null,null,sig,name,null,ext);
+ assert ext; // No need for ext
+ throw Utils.TODO();
+ }
public FunNode( FunNode fun ) {
- super( fun, fun==null ? null : fun._loc );
- if( fun!=null ) {
- _sig = fun.sig();
- _name = fun._name;
- } else {
- _sig = TypeFunPtr.BOT;
- _name = "";
- }
+ super( fun, fun._loc );
+ _sig = fun.sig();
+ _name = fun._name;
+ _compunit = fun._compunit;
+ _extern = fun._extern;
+ }
+ @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.packed2(types.get(_sig)); // NPE if fails lookup
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_approxUIDs);
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ TypeFunPtr sig = (TypeFunPtr)types[bais.packed2()];
+ String name = strs[bais.packed2()];
+ FunNode fun = new FunNode(null,sig,name,null,ins);
+ fun._approxUIDs = bais.packed2();
+ return fun;
}
- @Override
- public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
+ @Override public String label() { return _name == null ? "$fun"+_sig.fidx() : _name; }
// Find the one CFG user from Fun. It's not always the Return, but always
// the Return *is* a CFG user of Fun.
@@ -47,13 +74,14 @@ public class FunNode extends RegionNode {
return null;
}
- public ParmNode rpc() {
- ParmNode rpc = null;
+ public ParmNode parm(int idx) {
+ ParmNode pidx = null;
for( Node n : _outputs )
- if( n instanceof ParmNode parm && parm._idx==0 )
- { assert rpc==null; rpc=parm; }
- return rpc;
+ if( n instanceof ParmNode parm && parm._idx==idx )
+ { assert pidx==null; pidx=parm; }
+ return pidx;
}
+ public ParmNode rpc() { return parm(0); }
// Cannot create the Return and Fun at the same time; one has to be first.
// So setting the return requires a second step.
@@ -66,13 +94,29 @@ public class FunNode extends RegionNode {
assert sig.isa(_sig);
if( _sig != sig ) {
CODE.add(this);
+ // Changing the signature can allow more inlining
+ if( _ret != null )
+ for( Node use : ret().outs() )
+ if( use instanceof CallEndNode || use instanceof FunPtrNode )
+ CODE.add(use);
_sig = sig;
+ unlock();
}
}
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ TypeFunPtr sig = (TypeFunPtr)_sig.upgradeType(TYPES);
+ if( sig == _sig ) return false;
+ unlock();
+ _sig = sig;
+ return true;
+ }
+
public void setName( String name ) {
- if( _name==null ) _name=name;
- else _name += "."+name;
+ if( _name==null )
+ _name = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1
+ ? name
+ : (_compunit._cname+"."+name).intern();
}
@Override
@@ -81,10 +125,10 @@ public class FunNode extends RegionNode {
if( unknownCallers() )
return Type.CONTROL;
Type t = Type.XCONTROL;
- for (int i = 1; i < nIns(); i++) {
- // Since no unknown callers, we are not main and the Start input
- // will be a Tuple with XControl, so ignore it. Need to be called
- // from someplace other than Start
+ for( int i = 1; i < nIns(); i++) {
+ // Since there are no unknown callers, the Start input is only a
+ // fake hook and will be a Tuple with XControl, so ignore it. Need
+ // to be called from someplace other than Start.
if( !(in(i) instanceof StartNode) )
t = t.meet(in(i)._type);
}
@@ -94,28 +138,37 @@ public class FunNode extends RegionNode {
@Override
public Node idealize() {
- // Some linked path dies, except main never kills Start
+ // Some linked path dies, except unknown caller hooks never kill Start.
Node progress = deadPath(unknownCallers());
if( progress!=null ) {
- if( nIns()==3 && in(2) instanceof CallNode call )
- CODE.add(call.cend()); // If Start and one call, check for inline
+ if( nIns()==2 && in(1) instanceof CallNode call )
+ CODE.add(call.cend()); // If no Start and one call, check for inline
return progress;
}
// Upgrade inferred or user-written return type to actual
- if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _sig.ret() ) {
+ if( _ret!=null && _ret._type instanceof TypeTuple tt &&
+ tt.ret() != _sig.ret() && tt.ret().isa(_sig.ret()) ) {
setSig(_sig.makeFrom(tt.ret()));
return this;
}
+ // Attempt to get rid of the unknown caller.
+ // - Must be past pessimistic Iter, which discovers and links calls
+ // 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 ) {
+ boolean preOpto = CodeGen.CODE._phase == null ||
+ CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) {
+ _compunit._stop.delDef(_compunit._stop._inputs.find(ret()));
+ return removeDeadPath(1);
+ } else {
+ addDep(start);
+ }
+ }
- // When can we assume no callers? Or no other callers (except main)?
- // In a partial compilation, we assume Start gets access to any/all
- // top-level public structures and recursively what they point to.
- // This in turn is valid arguments to every callable function.
- //
- // In a total compilation, we can start from Start and keep things
- // more contained.
// If no default/unknown caller, use the normal RegionNode ideal rules
// to collapse
@@ -134,24 +187,58 @@ public class FunNode extends RegionNode {
}
// Bypass Region idom, always assume depth == 1, one more than Start,
- // unless folding then just a ID on input#1
+ // unless folding
@Override public int idepth() {
- return _folding ? super.idepth() : CodeGen.CODE.iDepthAt(1);
+ if( _folding ) return super.idepth();
+ return CodeGen.CODE.iDepthAt(1);
}
+
// Bypass Region idom, always assume idom is Start
- @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); }
+ @Override public CFGNode idom(Node dep) {
+ return _folding ? cfg(1) : null;
+ }
// Always in-progress until we run out of unknown callers
public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; }
- @Override public boolean inProgress() { return unknownCallers(); }
+ public boolean isModInit( ) {
+ // The one top-level <clinit> with no internal dots:
+ // "sys.<clinit>" is a module, but "sys.io.<clinit>" is not.
+ return isClz() && _name.indexOf('.')==_name.lastIndexOf('.');
+ }
+ public boolean isClz ( ) { return isClz (_name); }
+ public boolean isInit( ) { return isInit(_name); }
+ public boolean isInstance() { return isInstance(_name); }
+ public static boolean isClz (String name ) { return name!=null && name.endsWith(".<clinit>"); }
+ public static boolean isInit(String name ) { return name!=null && name.endsWith("init>"); }
+ public static boolean isInstance(String name ) { return name!=null && name.endsWith(".<init>"); }
+
+ // Function is public (callable from Start directly).
+ public boolean isPublic( ) {
+ // Never true for anonymous functions
+ if( _name == null ) return false;
+ // Only public for <clinit> and <init>, all others
+ // are found via field loads. The <init> Start hook also supplies its
+ // conservative pre-Opto construction inputs; StopCU separately keeps
+ // its private-memory return from escaping as a public result.
+ if( !isInit() ) return false;
+ // Private class, has to be found via program text, not linker
+ if( _name.charAt(0)=='_' ) return false;
+
+ // No instances of "...clz1._clz2..." where the class name path
+ // includes a private class.
+ if( _name.indexOf("._") != -1 ) return false;
+
+ // Public class <cl/init>
+ return true;
+ }
- // Add a new function exit point.
- public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
+ @Override public boolean inProgress() {
+ return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ }
- // Build the function body
+ // Build the function body set
public BitSet body() {
-
// Reverse up (stop to start) CFG only, collect bitmap.
BitSet cfgs = new BitSet();
cfgs.set(_nid);
@@ -161,7 +248,13 @@ public class FunNode extends RegionNode {
// If data use bottoms out in wrong CFG, returns false - but tries all outputs.
// If any output hits an in-CFG use (e.g. phi), then keep node.
BitSet body = new BitSet();
- walkDown(this, cfgs, body, new BitSet());
+ BitSet visit = new BitSet();
+ int old;
+ do {
+ old = body.cardinality();
+ visit.clear();
+ walkDown(this, cfgs, body, visit);
+ } while( old != body.cardinality() );
return body;
}
@@ -175,20 +268,113 @@ public class FunNode extends RegionNode {
}
private static boolean walkDown( Node n, BitSet cfgs, BitSet body, BitSet visit ) {
+ if( n==null ) return false;
if( visit.get(n._nid) ) return body.get(n._nid);
visit.set(n._nid);
-
- if( n instanceof CFGNode && !cfgs.get(n._nid) )
+ // Visit self as CFG outside the function
+ if( n instanceof CFGNode && !cfgs.get(n._nid) && unfolded( n ) )
return false;
- if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) )
+ // Pretend the NewNode is a CFG, so its projections stay with it in loops
+ if( n instanceof NewNode nnn ) cfgs.set(n._nid);
+ // Visit n.cfg() outside of function
+ if( n.in(0)!=null && !cfgs.get(n.in(0)._nid) && unfolded( n.in( 0 ) ) )
return false;
+ // Phis inside the function must have their body flag set BEFORE
+ // recursion walks around a loop and finds them again.
+ if( n instanceof PhiNode phi && cfgs.get(phi.in(0)._nid) )
+ body.set(phi._nid); // Find data cycles
boolean in = n.in(0)!=null || n instanceof CFGNode;
- for( Node use : n._outputs )
+ for( Node use : n._outputs ) {
+ // Will hit a backedge, so 'n' MUST be in the function, and must be
+ // set before the cyclic walk finds 'n' again.
+ if( use instanceof PhiNode puse && puse.in(0) instanceof LoopNode loop && puse.in(2)==n )
+ body.set(n._nid); // Set before walk
in |= walkDown(use,cfgs,body,visit);
+ }
if( in ) body.set(n._nid);
return in;
}
+ // A CFG is folding, and so is basically Data
+ private static boolean unfolded( Node cfg) {
+ if( cfg instanceof CallNode call && call.cend().folding() ) return false;
+ if( cfg instanceof CallEndNode cend && cend.folding() ) return false;
+ if( cfg instanceof FunNode fun && fun._folding ) return false;
+ if( cfg instanceof ReturnNode ret && ret._fun._folding ) return false;
+ return true;
+ }
+
+ // Clone function body. Give function a new FIDX.
+ FunNode copyBody() {
+ // Build the function body BitSet
+ BitSet body = body();
+ assert body.cardinality() < 100;
+ // Walk the body, cloning
+ IdentityHashMap<Node,Node> map = new IdentityHashMap<>();
+ BitSet visit = CodeGen.CODE.visit();
+ bodyCopy( visit, body, map, this );
+ visit.clear();
+ bodyEdge( visit, body, map, this );
+ visit.clear();
+ assert map.size()==body.cardinality();
+
+ // New function/return cross-link each other
+ FunNode fun2 = (FunNode )map.get(this);
+ ReturnNode ret2 = (ReturnNode)map.get(_ret);
+ fun2._ret = ret2;
+ ret2._fun = fun2;
+
+ // Remove all callers - this is a private copy
+ while( fun2.nIns() > 1 )
+ if( fun2.in(1) instanceof CallNode call && body.get(call._nid) )
+ call.unlink(fun2,1); // Recursive calls unlink
+ else fun2.removeDeadPath(1); // Start and external calls can just be removed
+
+ // Flip to a new FIDX to avoid confusion with the old one.
+ // This is a non-monotonic (sideways) type move, only applicable
+ // because fun2 is new. The fidx is also a purely local fiction,
+ // since this body will inline and the fidx goes dead.
+ fun2._sig = _sig.makeFrom(CodeGen.CODE._fidxs.nextInline());
+ return fun2;
+ }
+
+ // Clone small function
+ private void bodyCopy( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
+ if( n==null ) return;
+ if( !body.get(n._nid) ) return; // Not part of the body
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid);
+ Node m = n.copy();
+ CodeGen.CODE.add(m);
+ map.put(n,m);
+ m._type = n._type;
+ for( Node x : n._outputs )
+ bodyCopy(visit,body,map,x);
+ }
+ private void bodyEdge( BitSet visit, BitSet body, IdentityHashMap<Node,Node> map, Node n ) {
+ if( n==null ) return;
+ if( !body.get(n._nid) ) return; // Not part of the body
+ if( visit.get(n._nid) ) return; // Been there, done that
+ visit.set(n._nid);
+ Node m = map.get(n);
+ // The cloned Call should not clone any linked edges.
+ if( n instanceof CallEndNode cend && !cend._folding ) {
+ m.addDef( map.get(cend.call()) );
+ } else {
+ for( Node e : n._inputs ) {
+ Node x = e;
+ if( e != null ) {
+ x = map.get(e);
+ if( x == null ) x = e;
+ }
+ m.addDef(x);
+ }
+ }
+ for( Node x : n._outputs )
+ bodyEdge(visit,body,map,x);
+ }
+
+
// FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able)
@Override public boolean eq( Node n ) {
return _sig == ((FunNode)n)._sig;
@@ -245,4 +431,7 @@ public class FunNode extends RegionNode {
return slotRotate*8;
}
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_name);
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
new file mode 100644
index 00000000..33757640
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Constant function pointer whose type follows the callee signature.
+public class FunPtrNode extends TypeNode {
+ public FunPtrNode(TypeFunPtr tfp, StartNode start, ReturnNode ret) {
+ super(tfp,start,ret);
+ }
+ public FunPtrNode(FunPtrNode fptr) { super(fptr); }
+ @Override public Tag serialTag() { return Tag.FunPtr; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed2(types.get(_con));
+ }
+ static Node make(BAOS bais, Type[] types) {
+ return new FunPtrNode((TypeFunPtr)types[bais.packed2()],null,null);
+ }
+ public ReturnNode ret() { return (ReturnNode)in(1); }
+ public FunNode fun() { return ret().fun(); }
+
+ @Override public String label() { return "#"+_con; }
+ @Override public String glabel() { return "#"+_con.gprint(); }
+ @Override public String uniqueName() { return "FunPtr_" + _nid; }
+ @Override public Node copy() {
+ FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null);
+ fptr._type = _type;
+ return fptr;
+ }
+
+ @Override
+ public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ FunNode fun = CodeGen.CODE.lookupFun(((TypeFunPtr)_con).fidx());
+ return fun!=null && fun._name!=null
+ ? sb.append("{ ").append(fun._name).append("}")
+ : sb.append(_con);
+ }
+
+ @Override public boolean isConst() { return true; }
+ @Override public boolean isPinned() { return true; }
+ @Override public Type compute() {
+ ReturnNode ret = ret();
+ return ret == null ? _con : ret.fun().sig();
+ }
+ @Override public Node idealize() {
+ ReturnNode ret = ret();
+ if( ret==null ) return null;
+ // Function died (never executed), but fcn ptr is alive.
+ // Can be checked for null, or for unequals another FunPtr.
+ FunNode fun = addDep(ret.fun());
+ TypeFunPtr sig = fun.sig();
+ if( sig != _con )
+ { liftType(sig); return this; }
+ // TODO: If the function body dies but this value remains live only as
+ // a null/equality sentinel, preserve the identity without the Return.
+ if( fun.isDead() )
+ { setDef(1,null); liftType(sig.makeFrom(Type.TOP)); return this; }
+ return null;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/GuardMach.java b/src/main/java/com/seaofnodes/simple/node/GuardMach.java
new file mode 100644
index 00000000..202b1ab8
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/GuardMach.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.SB;
+
+public class GuardMach extends GuardNode implements MachNode {
+ public GuardMach(GuardNode guard) { super(guard); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress() { return in(1)._type==Type.BOTTOM ? 0 : 1; }
+ @Override public void encoding(Encoding enc) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/src/main/java/com/seaofnodes/simple/node/GuardNode.java
new file mode 100644
index 00000000..e3c6f0e2
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/GuardNode.java
@@ -0,0 +1,61 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeScalar;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// A branch-proven refinement to zero/null or non-zero/non-null. The value
+// family is deliberately taken from the input when it becomes known.
+public class GuardNode extends Node {
+ public final boolean _nonZero;
+
+ public GuardNode(boolean nonZero, Node ctrl, Node val) {
+ super(ctrl,val);
+ _nonZero = nonZero;
+ }
+
+ public GuardNode(GuardNode guard) {
+ super(guard);
+ _nonZero = guard._nonZero;
+ }
+
+ @Override public Tag serialTag() { return Tag.Guard; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.write(_nonZero ? 1 : 0);
+ }
+ static Node make(BAOS bais) { return new GuardNode(bais.read()!=0,null,null); }
+
+ @Override public String label() { return _nonZero ? "(!=0)" : "(==0)"; }
+ @Override public String uniqueName() { return "Guard_"+_nid; }
+ @Override public boolean isPinned() { return true; }
+
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append(label()),visited);
+ }
+
+ @Override public Type compute() {
+ Type t = in(1)._type;
+ if( t==Type.BOTTOM || t==Type.TOP ) return t;
+ // The scalar envelope does not identify a value family. Preserve it
+ // until SCCP learns whether this is an int, float, pointer, or fptr;
+ // choosing generic ptr here can be weaker than a later concrete type.
+ if( t==TypeScalar.BOT || t==TypeScalar.TOP ) return t;
+ Type t2 = _nonZero ? t.nonZero() : t.makeZero();
+ return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero
+ }
+
+ @Override public Node idealize() {
+ if( in(1)._type==Type.BOTTOM || in(1)._type==Type.TOP ) return null;
+ if( _type.isHighOrConst() && in(1)._type.isa(_type) )
+ return in(1);
+ return null;
+ }
+
+ @Override public boolean eq(Node n) { return _nonZero==((GuardNode)n)._nonZero; }
+ @Override int hash() { return _nonZero ? 1 : 0; }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/IfNode.java b/src/main/java/com/seaofnodes/simple/node/IfNode.java
index 5d751602..d8d72411 100644
--- a/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -12,9 +12,7 @@ public class IfNode extends CFGNode implements MultiNode {
CodeGen.CODE.add(this); // Because idoms are complex, just add it
}
public IfNode(IfNode iff) { super(iff); }
-
- @Override
- public String label() { return "If"; }
+ @Override public Tag serialTag() { return Tag.If; }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -45,7 +43,7 @@ public class IfNode extends CFGNode implements MultiNode {
// If constant is 0 then false branch is reachable
// Else true branch is reachable
if( t.isConstant() )
- return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs== XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
// If adding a zero makes a difference, the predicate must not have a zero/null
if( !t.makeZero().isa(t) )
return TypeTuple.IF_TRUE;
@@ -58,15 +56,14 @@ public class IfNode extends CFGNode implements MultiNode {
// Hunt up the immediate dominator tree. If we find an identical if
// test on either the true or false branch, that side wins.
if( !pred()._type.isHighOrConst() )
- for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() ) {
- if( addDep(dom) instanceof IfNode iff && addDep(iff.pred())==pred() && prior instanceof CProjNode prj ) {
+ for( CFGNode dom = idom(), prior=this; dom!=null; prior = dom, dom = dom.idom() )
+ if( addDep(dom) instanceof IfNode iff &&
+ prior instanceof CProjNode prj &&
+ (addDep(iff.pred())==pred() ||
+ pred() instanceof GuardNode cast && cast._nonZero && iff.pred()==cast.in(1) ) ) {
setDef(1,con( prj._idx==0 ? 1 : 0 ));
return this;
}
- if( dom instanceof RegionNode r )
- for( Node c : r._inputs )
- if( c!= null ) addDep(c); // If region loses input, idom changes
- }
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index 58dbfc81..f67f2b97 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -2,7 +2,9 @@ package com.seaofnodes.simple.node;
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 java.util.BitSet;
@@ -20,9 +22,14 @@ public class LoadNode extends MemOpNode {
* @param ptr The ptr to the struct base from where we load a field
* @param off The offset inside the struct base
*/
- public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) {
- super(loc, name, alias, true, glb, mem, ptr, off);
+ public LoadNode(Parser.Lexer loc, String name, int alias, Node ctrl, Node mem, Node ptr, Node off) {
+ super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off);
}
+ LoadNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ super(bais,strs,types,fileAliases,aliases,true);
+ _con = Type.BOTTOM; // Serialized slot retained while MemOps share TypeNode
+ }
+ @Override public Tag serialTag() { return Tag.Load; }
// GraphVis DOT code (must be valid Java identifiers) and debugger labels
@Override public String label() { return "ld_"+mlabel(); }
@@ -34,67 +41,110 @@ public class LoadNode extends MemOpNode {
@Override
public Type compute() {
- Type tmem = mem()._type;
- if( !(tmem instanceof TypeMem mem) )
- return tmem; // No memory yet? Assume TOP/BOT
- assert !_declaredType.isFRef();
- // No lifting if ptr might null-check
- Type tptr = ptr()._type;
- if( err() != null )
- return _declaredType;
- if( !(tptr instanceof TypeMemPtr tmp) )
- return tptr; // No pointer yet? Assume TOP/BOT
+ Type mem0 = aliasMem()._type;
+ Type ptr0 = ptr()._type;
+ // Validate argument types
+ if( ptr0.isHigh() )
+ return TypeScalar.TOP;
+ if( !(mem0 instanceof TypeMem mem) )
+ return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+ if( ptr0 == Type.NIL )
+ return TypeScalar.TOP;
+ if( !(ptr0 instanceof TypeMemPtr ptr) )
+ return mem0.isHigh() ? TypeScalar.TOP : scalar(mem._t);
+
// Load field from object
- Field f = tmp._obj.field(_name);
+ TypeMemPtr tmp = ptr;
+ Field pfld = tmp._obj.field(_name);
// No field? Open objects might yet get the field when falling;
// closed objects with missing field are an error.
- if( f == null )
- return tmp.isHigh() ? Type.TOP : _declaredType;
- Type t = f._t;
+ if( pfld == null )
+ return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT;
+
+ Type t = pfld._t;
// Load member of constant array
if( t instanceof TypeConAry ary )
t = ary.elem(); // TODO: if offset is known, can peek the constant
- // Lift from declared type and memory input
- t = t.join(mem._t);
- if( _declaredType.isFinal() )
- t = t.makeRO(); // Deep final applied
- // Pinch between declared type
- t = t.join(_declaredType);
- return t;
+
+ // Now, do the same for memory
+ if( mem._t instanceof TypeStruct ts ) {
+ assert ts._name==tmp._obj._name;
+ Field mfld = ts.field(_name);
+ // Lift from declared type and memory input
+ if( mfld != null )
+ t = t.join(mfld._t);
+ } else if( mem._alias==_alias ) {
+ t = t.join(mem._t);
+ } else if( mem._t == Type.TOP ) {
+ return TypeScalar.TOP;
+ }
+
+ // A deeply read-only base produces a deeply read-only value. The
+ // generic declared field type must not cast this information away.
+ if( ptr0.isFinal() )
+ t = t.makeRO();
+ return scalar(t);
+ }
+
+ // Loads produce ordinary scalar values. Field/memory joins can still
+ // expose the enclosing global lattice bounds through an uninitialized
+ // Type.TOP/BOTTOM field; keep those results inside the scalar envelope.
+ private static Type scalar(Type t) {
+ return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t;
}
@Override
public Node idealize() {
Node ptr = ptr();
- Node mem = mem();
+ Node mem = aliasMem();
+
+ // Loads into structs do not need a ctrl edge, as null-ptr checking is
+ // baked into the type system. Loads into arrays DO need the ctrl
+ // edge, at least until proper range-checking is in place.
+ if( in(0)!=null && ptr()._type instanceof TypeMemPtr tmp &&
+ // Never can be an array
+ (!tmp._obj._open || (tmp._obj._fields.length > 1 && tmp._obj._fields[0]._fname != "#")) ) {
+ setDef(0,null);
+ return this;
+ }
- if( mem instanceof CastNode cast ) {
- setDef(1,cast.in(1));
+ // Forward-ref loads eventually sharpen to a declared type
+ Field fld;
+ if( ptr()._type instanceof TypeMemPtr tmp &&
+ (fld=tmp._obj.field(_name)) != null &&
+ _alias != fld._alias) {
+ assert _alias==1 || _alias == fld._alias;
+ unlock(); // Alias participates in GVN semantics
+ _alias = fld._alias;
+ // Bulk memory may already have inspected this user. Revisit it
+ // once the declaring shape is stable; an open forward reference
+ // can still change this Load's semantics.
+ if( !tmp._obj._open && !tmp._obj._fref )
+ CodeGen.CODE.add(mem());
return this;
}
+ // Must sharpen alias first
+ if( _alias == 1 )
+ return null;
// Simple Load-after-Store on same address.
if( mem instanceof StoreNode st &&
- ptr == st.ptr() && off() == st.off() ) { // Must check same object
+ ptr == st.nnptr() &&
+ off() == st.off() ) { // Must check same object
assert _name.equals(st._name); // Equiv class aliasing is perfect
return extend(st.val());
}
- // Simple load-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( mem instanceof MemMergeNode mem2 ) {
- Node memA = mem2.alias(_alias);
- for( Node ld : memA._outputs )
+ // Expose the same effective memory input already observed by compute.
+ if( mem != mem() ) {
+ for( Node ld : mem._outputs )
if( ld instanceof LoadNode )
CodeGen.CODE.add(ld);
- setDef(1,memA);
+ if( mem instanceof BulkMemPhiNode ) CodeGen.CODE.add(mem);
+ setDef(1,mem);
return this;
}
- // Simple Load-after-New on same address.
- if( mem instanceof ProjNode p && p.in(0) instanceof NewNode nnn &&
- ptr == nnn.proj(1) ) // Must check same object
- return zero(nnn); // Load zero from new
-
// Uplift control to a prior dominating load.
for( Node memuse : mem._outputs )
// Find a prior load, has same mem,ptr,off but higher ctrl
@@ -115,8 +165,11 @@ public class LoadNode extends MemOpNode {
return extend(castRO(st.val())); // Proved equal
// Can we prove unequal? Offsets do not overlap?
if( !off()._type.join(st.off()._type).isHigh() && // Offsets overlap
- !neverAlias(ptr,st.ptr()) ) // And might alias
- break outer; // Cannot tell, stop trying
+ !neverAlias(ptr,st.ptr()) ) { // And might alias
+ addDep( off()); // Offsets can fold, proving unequal
+ addDep(st.off()); // Offsets can fold, proving unequal
+ break outer; // Cannot tell, stop trying
+ }
// Pointers cannot overlap
mem = st.mem(); // Proved never equal
break;
@@ -124,23 +177,32 @@ public class LoadNode extends MemOpNode {
// Assume related
addDep(phi);
break outer;
- case ConstantNode top: break outer; // Assume shortly dead
+ case ConstantNode con:
+ // Load from constant memory
+ if( con._con instanceof TypeMem tmem )
+ return ConstantNode.make(tmem._t);
+ break outer; // Assume shortly dead
case ProjNode mproj: // Memory projection
switch( mproj.in(0) ) {
case NewNode nnn1:
- if( ptr instanceof ProjNode pproj && pproj.in(0) == mproj.in(0) )
- return zero(nnn1);
- if( !(ptr instanceof ProjNode pproj && pproj.in(0) instanceof NewNode) )
- break outer; // Cannot tell, ptr not related to New
- mem = nnn1.in(nnn1.findAlias(_alias));// Bypass unrelated New
- break;
+ // Direct load from e.g. new array elements
+ assert _name=="[]";
+ Type decl = declaredType();
+ return decl==Type.BOTTOM ? null : ConstantNode.make(decl.makeZero());
case StartNode start: break outer;
- case CallEndNode cend: break outer; // TODO: Bypass no-alias call
+ case CallEndNode cend: addDep(mproj); break outer; // TODO: Bypass no-alias call
default: throw Utils.TODO();
}
- break;
- case CastNode cast: mem = cast.in(1); break;
+ case CheckCastNode cast: mem = cast.in(1); break;
case MemMergeNode merge: mem = merge.alias(_alias); break;
+ case EscapeNode esc:
+ if( esc.self()==ptr ) // Proved equal
+ { mem = esc.priv(); break; }
+ // Two NewNodes are always unequal
+ if( esc.self().in(0) instanceof NewNode && ptr.in(0) instanceof NewNode )
+ { mem = esc.pub(); break; }
+ // TODO: Can we prove unequal?
+ break outer;
default:
throw Utils.TODO();
@@ -157,6 +219,8 @@ public class LoadNode extends MemOpNode {
if( mem() instanceof PhiNode memphi && memphi.region()._type == Type.CONTROL && memphi.nIns()== 3 &&
// Offset can be hoisted
off() instanceof ConstantNode &&
+ // Not control dependent
+ in(0)==null &&
// Pointer can be hoisted
hoistPtr(ptr,memphi) ) {
@@ -164,25 +228,34 @@ public class LoadNode extends MemOpNode {
if( profit(memphi,2) ||
// Else must not be a loop to count profit on LHS.
(!(memphi.region() instanceof LoopNode) && profit(memphi,1)) ) {
+ // profit() peepholes inputs and can restore a required control
+ // edge. Recheck before manufacturing control-free loads.
+ if( in(0)!=null )
+ return null;
Node ld1 = ld(1);
Node ld2 = ld(2);
- return new PhiNode(_name,_declaredType,memphi.region(),ld1,ld2);
+ PhiNode phi = new PhiNode(_name, memphi.region(),ld1,ld2);
+ phi.setType(phi.compute().join(_type));
+ return phi;
}
}
return null;
}
- // Load a flavored zero from a New
- private Node zero(NewNode nnn) {
- TypeStruct ts = nnn._ptr._obj;
- Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero();
- return castRO(new ConstantNode(zero).peephole());
+ // Semantic memory input for this Load. Compute defines behavior from
+ // this view; ideal merely exposes the same edge in the graph.
+ private Node aliasMem() {
+ Node mem = mem();
+ return _alias != 1 && mem instanceof MemMergeNode merge
+ ? merge.alias(_alias)
+ : mem;
}
private Node ld( int idx ) {
Node mem = mem(), ptr = ptr();
- return new LoadNode(_loc,_name,_alias,_declaredType,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
+ assert in(0)==null;
+ return new LoadNode(_loc,_name,_alias,null,mem.in(idx),ptr instanceof PhiNode && ptr.in(0)==mem.in(0) ? ptr.in(idx) : ptr, off()).peephole();
}
private static boolean neverAlias( Node ptr1, Node ptr2 ) {
@@ -215,8 +288,14 @@ public class LoadNode extends MemOpNode {
private boolean profit(PhiNode phi, int idx) {
Node px = phi.in(idx);
if( px==null ) return false;
- if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() ) return true;
- if( px instanceof StoreNode st1 && ptr()==addDep(st1.ptr() )&& off()==st1.off() ) return true;
+ if( px._type instanceof TypeMem mem && mem._t.isHighOrConst() )
+ // To avoid cyclic pushing a Load up then down, getting here means
+ // the load *must* replace with the high/constant.
+ return true;
+ if( px instanceof StoreNode st1 && ptr()==addDep(st1.nnptr() )&& off()==st1.off() )
+ // To avoid cyclic pushing a Load up then down, getting here means
+ // the load *must* match against the Store
+ return true;
addDep(px);
return false;
}
@@ -231,15 +310,25 @@ public class LoadNode extends MemOpNode {
// When a load bypasses a store, the store might truncate bits - and the
// load will need to zero/sign-extend.
private Node extend(Node val) {
- if( !(_declaredType instanceof TypeInteger ti) ) return val;
+ if( !(declaredType() instanceof TypeInteger ti) ) return val;
if( ti._min==0 ) // Unsigned
return new AndNode(null,val,con(ti._max));
// Signed extension
int shift = Long.numberOfLeadingZeros(ti._max)-1;
Node shf = con(shift);
- if( shf._type==TypeInteger.ZERO )
+ if( shf._type==TypeInteger.ZERO ) {
+ // A freshly exposed Store value can still be globally unknown.
+ // Revisit this Load when it settles instead of replacing a typed
+ // Load with the weaker transient value.
+ if( !val._type.isa(_type) ) {
+ addDep(val);
+ return null;
+ }
return val;
+ }
Node shl = new ShlNode(null,val,shf.keep()).peephole();
return new SarNode(null,shl,shf.unkeep());
}
+
+ @Override public int log_size() { return declaredType().log_size(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java
index fc50e17f..44ca35d8 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoopNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -3,23 +3,22 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.type.TypeMem;
-import com.seaofnodes.simple.util.Utils;
-import java.util.BitSet;
-import java.util.HashSet;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
public class LoopNode extends RegionNode {
- public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,entry,null); }
+ public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); }
public LoopNode( LoopNode loop ) { super(loop); }
+ @Override public Tag serialTag() { return Tag.Loop; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { }
public CFGNode entry() { return cfg(1); }
public CFGNode back () { return cfg(2); }
@Override
- public String label() { return "Loop"; }
-
- @Override
public Type compute() {
if( inProgress() ) return Type.CONTROL;
return entry()._type;
@@ -48,7 +47,7 @@ public class LoopNode extends RegionNode {
x = x.idom();
}
// Found a no-exit loop. Insert an exit
- NeverNode iff = (NeverNode)new NeverNode(back()).peephole(); // Ideal never-branch
+ NeverNode iff = (NeverNode)new NeverNode(1.2f,back()).peephole(); // Ideal never-branch
CProjNode t = new CProjNode(iff,0,"True" ).init();
CProjNode f = new CProjNode(iff,1,"False").init();
setDef(2,t); // True continues loop, False (never) exits loop
@@ -58,12 +57,14 @@ public class LoopNode extends RegionNode {
// Now fold control into the exit. Might have 1 valid exit, or an
// XCtrl or a bunch of prior NeverNode exits.
- Node top = new ConstantNode(Type.TOP).peephole();
+ Node top = ConstantNode.make(Type.TOP).peephole();
Node memout = new MemMergeNode(false);
memout.addDef(f); // placeholder for control
+ memout.addDef(top);
for( Node u : _outputs )
if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
memout.addDef(phi);
+ memout.init();
Node ctrl = ret.ctrl(), mem = ret.mem(), expr = ret.expr();
if( ctrl!=null && ctrl._type != Type.XCONTROL ) {
@@ -74,7 +75,7 @@ public class LoopNode extends RegionNode {
// Nope, insert an aligned exit layer
RegionNode r = new RegionNode(_loc,null,ctrl).init();
ctrl = r; r._ltree = stop._ltree;
- mem = new PhiNode(r,mem ).init();
+ mem = new BulkMemPhiNode(r,mem ).init();
expr = new PhiNode(r,expr).init();
}
// Append new Never exit
@@ -89,8 +90,12 @@ public class LoopNode extends RegionNode {
ret.setDef(0,ctrl.unkeep());
ret.setDef(1,mem .unkeep());
ret.setDef(2,expr.unkeep());
- ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr]
- ret.setType(ret.compute());
+ // Force sane types to exit; might locally break monotonicity
+ ret.init();
+ ret.out(0).init();
+ stop.init();
+ CodeGen.CODE.add(CodeGen.CODE._start);
+ CodeGen.CODE.add(mem);
return stop;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MachNode.java b/src/main/java/com/seaofnodes/simple/node/MachNode.java
index c0b3af66..025b5dc4 100644
--- a/src/main/java/com/seaofnodes/simple/node/MachNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MachNode.java
@@ -55,6 +55,6 @@ public interface MachNode {
// General form: "dst=src+src".
default void asm(CodeGen code, SB sb) { throw Utils.TODO(); }
- // Human readable opcode; e.g. "ld4" or "call".
+ // Human-readable opcode; e.g. "ld4" or "call".
default String op() { throw Utils.TODO(); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index 9ab57714..a88748b5 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -2,7 +2,10 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.*;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
+
import java.util.*;
/**
@@ -12,18 +15,21 @@ import java.util.*;
public class MemMergeNode extends Node {
/*
- * In-Progress means this is being used by the Parser to track memory
- * aliases. No optimizations are allowed. When no longer "in progress"
- * normal peeps work.
+ * In-Progress is the mutable construction form. No optimizations are
+ * allowed. If not "in progress" then normal peeps work.
*/
public final boolean _inProgress;
public MemMergeNode( boolean inProgress) { _type = TypeMem.BOT; _inProgress = inProgress; }
+ public MemMergeNode( boolean inProgress, Node ...nodes) { super(nodes); _type = TypeMem.BOT; _inProgress = inProgress; }
public MemMergeNode(MemMergeNode mem) { super(mem); _inProgress = false; }
+ @Override public Tag serialTag() { return Tag.MemMerge; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { Node mem = new MemMergeNode(false); mem.setDefX(bais.packed1()-1,null); return mem; }
- // If being used by a Scope, this is "in progress" from the Parser.
- // Otherwise, it's a memory merge
+ // The mutable construction form is "in progress"; otherwise this is a
+ // graph-semantic memory merge.
boolean inProgress() { return _inProgress; }
@Override public String label() { return "ALLMEM"; }
@@ -61,29 +67,82 @@ public class MemMergeNode extends Node {
@Override public Type compute() {
- for( Node n : _inputs )
- if( n != null && !n._type.isHigh() )
- return TypeMem.BOT;
- return TypeMem.TOP;
+ Type tmem = in(1)._type;
+ if( !(tmem instanceof TypeMem defmem) )
+ // A MemMerge is structurally memory even while its default input
+ // is a weak, not-yet-specialized Phi.
+ return tmem.isHigh() ? TypeMem.TOP : TypeMem.BOT;
+ // Is this a single private instance memory?
+ if( defmem._one ) {
+ if( !(defmem._t instanceof TypeStruct ts) )
+ return defmem;
+ // Perfect singleton memory, so all updates are parallel and
+ // independent and stack.
+ for( int i=2; i<nIns(); i++ ) {
+ Node in = in(i);
+ while( true ) {
+ if( in !=null && in._type.isHigh() )
+ return TypeMem.TOP;
+ if( in instanceof MemMergeNode mem ) {
+ in = mem.in(i);
+ } else break;
+ }
+ if( in instanceof StoreNode st ) {
+ Type val = ((TypeMem)st._type)._t;
+ Field old = ts.field(st._name);
+ // Lazy add an expected field for open structs.
+ if( old == null && ts._open ) {
+ old = Field.make(st._name,Type.BOTTOM,st._alias,st._init);
+ ts = ts.add(old);
+ }
+ Field fld = old.makeFrom(val);
+ // TODO: val leaks into perfect singleton memory, along with its fidxs and aliases
+ ts = ts.replace(fld);
+ }
+ }
+ return TypeMem.makePrivate(ts);
+
+ } else {
+ // Default mixed mem is just BOT
+ TypeMem mem = defmem;
+ boolean progress = true;
+ while( progress ) {
+ progress = false;
+ for( int i=2; i<nIns(); i++ ) {
+ // Has this alias escaped already?
+ if( XInt.bit(mem._escAs,i) ) {
+ Node n = in(i);
+ if( n != null && !n._type.isHigh() ) {
+ TypeMem mem2 = (TypeMem)mem.meet(n._type);
+ // More escape aliases
+ if( mem2 != mem )
+ { mem = mem2; progress = true; }
+ }
+ }
+ }
+ }
+ return mem;
+ }
}
@Override public Node idealize() {
if( inProgress() ) return null;
+ if( nIns()==0 ) return null;
// Fold defaults into the default
boolean progress=false, allDefault=true;
for( int i=2; i<nIns(); i++ ) {
- if( in(i) instanceof CastNode cast )
+ if( in(i) instanceof CheckCastNode cast )
{ setDef(i,cast.in(1)); progress=true; }
- if( in(1) == in(i) ) { setDef(i,null); progress=true; }
- else { allDefault=false; }
+ if( in(1) == in(i) && in(1) != null ) { setDef(i,null); progress=true; }
+ else { allDefault=false; }
}
// If not merging any memory (all memory is just the default)
if( allDefault )
return in(1); // Become default memory
- // Collapse stacked all-mem
+ // Collapse stacked merged-mem
if( in(1) instanceof MemMergeNode mem ) {
// Goal is to swap my default mem with mem's default mem
for( int i=2; i<mem.nIns(); i++ ) {
@@ -93,6 +152,7 @@ public class MemMergeNode extends Node {
setDefX(i,mem.in(i));
}
}
+ CodeGen.CODE.add(mem.in(1));
setDef(1,mem.in(1));
return this;
}
@@ -104,13 +164,12 @@ public class MemMergeNode extends Node {
public Node in( Var v ) { return in(v._idx); }
public Node alias( int alias ) {
+ assert !(in(1) instanceof BulkMemPhiNode bulk && bulk.isSplit(alias) &&
+ (alias >= nIns() || in(alias)==null));
return alias < nIns() && in(alias)!=null ? in(alias) : in(1);
}
- Node alias( int alias, Node st ) {
- while( alias >= nIns() ) addDef(null);
- return setDef(alias,st);
- }
+ public Node alias( int alias, Node st ) { return setDefX(alias,st); }
// Read or update from memory.
// A shared implementation allows us to create lazy phis both during
@@ -119,25 +178,19 @@ public class MemMergeNode extends Node {
// Memory projections are made lazily; if one does not exist
// then it must be START.proj(1)
Node old = alias(alias);
- if( old instanceof ScopeNode loop ) {
- MemMergeNode loopmem = loop.mem();
- Node memdef = loopmem.alias(alias);
- // Lazy phi!
- old = memdef instanceof PhiNode phi && loop.ctrl()==phi.region()
- // Loop already has a real Phi, use it
- ? memdef
- // Set real Phi in the loop head
- // The phi takes its one input (no backedge yet) from a recursive
- // lookup, which might have insert a Phi in every loop nest.
- : loopmem.alias(alias, new PhiNode(Parser.memName(alias), TypeMem.make(alias,Type.BOTTOM),loop.ctrl(),loopmem._mem(alias,null),null).peephole() );
- alias(alias,old);
- }
+ assert !(old instanceof ScopeNode); // Parser lazy memory is handled by ScopeNode
// Memory projections are made lazily; expand as needed
return st==null ? old : alias(alias,st); // Not lazy, so this is the answer
}
+ // Remove lazy Phis on all aliases
+ public void removeLazyAll() {
+ for( int i = 1; i < nIns(); i++)
+ _mem(i,null);
+ }
- void _merge( MemMergeNode that, RegionNode r) {
+ public void _merge( MemMergeNode that, RegionNode r) {
+ assert _inProgress;
int len = Math.max(nIns(),that.nIns());
for( int i = 1; i < len; i++)
if( alias(i) != that.alias(i) ) { // No need for redundant Phis
@@ -145,13 +198,33 @@ public class MemMergeNode extends Node {
// by alias as it will trigger a phi creation
Node lhs = this._mem(i,null);
Node rhs = that._mem(i,null);
- alias(i, new PhiNode(Parser.memName(i), lhs._type.meet(rhs._type).glb(true), r, lhs, rhs).peephole());
+ alias(i, new MemPhiNode(Parser.memName(i), i, r, lhs, rhs).peephole());
}
}
- // Fill in the backedge of any inserted Phis
+ // Fill in the backedge of any inserted Phis. Keep precise aliases for
+ // what is known at the loop head. For aliases discovered in the loop
+ // body, gather them into a actual (not inProgress) MemMerge and merge into
+ // the default alias.
void _endLoopMem( ScopeNode scope, MemMergeNode back, MemMergeNode exit ) {
+ assert _inProgress && back._inProgress && exit._inProgress;
Node exit_def = exit.alias(1);
+ MemMergeNode more = null; // Dont often need the "more discovered" merge, make it lazy
+ int len = Math.max(nIns(),back.nIns());
+ for( int i=2; i<len; i++ ) {
+ if( !(i < this.nIns() && this.in(i)!=null) && // Alias is not known to loop head
+ (i < back.nIns() && back.in(i)!=null) ) { // Alias IS known to loop back
+ // Gather
+ if( more==null ) {
+ more = new MemMergeNode(false);
+ more.alias(1,back.in(1));
+ back.alias(1,more);
+ }
+ more.alias(i,back.in(i));
+ back.setDef(i,null);
+ }
+ }
+
for( int i=1; i<nIns(); i++ ) {
if( in(i) instanceof PhiNode phi && phi.region()==scope.ctrl() ) {
assert phi.in(2)==null;
@@ -164,7 +237,7 @@ public class MemMergeNode extends Node {
// Now one-time do a useless-phi removal
void _useless( ) {
- for( int i=2; i<nIns(); i++ ) {
+ for( int i=1; i<nIns(); i++ ) {
if( in(i) instanceof PhiNode phi ) {
// Do an eager useless-phi removal
Node in = phi.peephole();
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index ead82322..d824bc3a 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -1,11 +1,15 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMemPtr;
+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 java.lang.StringBuilder;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Convenience common base for Load and Store.
@@ -16,84 +20,95 @@ import java.util.BitSet;
* 3 - Offset, integer types
* 4 - Value, for Stores only
*/
-public abstract class MemOpNode extends Node {
+public abstract class MemOpNode extends TypeNode {
// The equivalence alias class
- public final int _alias;
+ public int _alias;
// True if load-like, false if store-like.
+ // Used on CPU-specific combined memory+arithmetic ops.
//
// Stores produce memory (maybe as part of a tuple with other things),
// loads do not.
//
- // Loads might pick up anti-dependences on prior Stores, and never cause an
+ // Loads might pick up anti-dependencies on prior Stores, and never cause an
// anti-dependence themselves.
//
// Stores must maximally sink to the least dominator use. Loads can be
// opportunistically hoisted.
public final boolean _isLoad;
- // Declared type; not final because it might be a forward-reference
- // which will be lazily improved when the reference is declared.
- public Type _declaredType;
-
- // A debug name, no semantic meaning
+ // Field name.
public final String _name;
// Source location for late reported errors
public final Parser.Lexer _loc;
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) {
- super(null, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off) {
+ super(glb, ctrl, mem, ptr, off);
_name = name;
_alias = alias;
- _declaredType = glb;
_loc = loc;
_isLoad = isLoad;
}
- public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off, Node value) {
- this(loc,name, alias, isLoad, glb, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value) {
+ this(loc, name, alias, isLoad, glb, ctrl, mem, ptr, off);
addDef(value);
}
- public MemOpNode( Node mach, MemOpNode mop ) {
- super(mach);
- _name = mop==null ? null : mop._name;
- _alias = mop==null ? 0 : mop._alias;
- _loc = mop==null ? null : mop._loc;
- _isLoad= mop==null ? true : mop._isLoad;
- _declaredType = mop==null ? Type.BOTTOM : mop._declaredType;
- if( mop==null )
- throw Utils.TODO("Load or not");
+ public MemOpNode( Node ideal, MemOpNode mop ) {
+ super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con);
+ _name = mop._name;
+ _alias = mop._alias;
+ _loc = mop._loc;
+ _isLoad= mop._isLoad;
+ }
+
+ MemOpNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases, boolean isLoad ) {
+ this(null,
+ strs[bais.packed2()],
+ mapAlias(bais,fileAliases,aliases),isLoad,
+ types[bais.packed2()],null,null,null,null);
+ }
+
+ private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) {
+ int alias = bais.packed2();
+ return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias);
}
- // Used by M2 when translating graph to Simple
- public MemOpNode( boolean isLoad ) {
- super((Node)null);
- _name = null;
- _alias = 0;
- _loc = null;
- _isLoad = isLoad;
- _declaredType = Type.BOTTOM;
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(_name==null ? 0 : strs.get(_name));
+ baos.packed2(_alias);
+ baos.packed2(types.get(_con)); // NPE if fails lookup
+ assert _isLoad == this instanceof LoadNode; // No machine ops
}
- static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
+ static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); }
String mlabel() { return mlabel(_name); }
public Node mem() { return in(1); }
public Node ptr() { return in(2); }
public Node off() { return in(3); }
+ // Current declared field type. This is deliberately a live lookup:
+ // cyclic/forward types can sharpen by replacing the pointer's struct.
+ public Type declaredType() {
+ if( !(ptr()._type instanceof TypeMemPtr tmp) )
+ return Type.BOTTOM;
+ Field fld = tmp._obj.field(_name);
+ return fld == null || fld._t == null ? Type.BOTTOM : fld._t;
+ }
+
@Override public StringBuilder _print1( StringBuilder sb, BitSet visited ) { return _printMach(sb,visited); }
public StringBuilder _printMach( StringBuilder sb, BitSet visited ) { throw Utils.TODO(); }
- public int log_size() { return _declaredType.log_size(); }
+ public int log_size() { return _con.log_size(); }
@Override
public boolean eq(Node n) {
MemOpNode mem = (MemOpNode)n; // Invariant
- return _alias==mem._alias; // When comparing types error to use "equals"; always use "=="
+ return _alias==mem._alias && super.eq(mem);
}
@Override
- int hash() { return _alias; }
+ int hash() { return _alias ^ super.hash(); }
@Override
public Parser.ParseException err() {
@@ -102,8 +117,15 @@ public abstract class MemOpNode extends Node {
if( ptr == Type.BOTTOM ) return null;
if( ptr.isHigh() ) return null; // Assume it will fall to not-null
// Better be a not-nil TMP
- if( ptr instanceof TypeMemPtr tmp && tmp.notNull() )
- return null;
- return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ if( !(ptr instanceof TypeMemPtr tmp && tmp.notNull()) )
+ return Parser.error( "Might be null accessing '" + _name + "'",_loc);
+ if( tmp._obj.field(_name)==null &&
+ CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ // Sane field
+ if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
+ return null;
+
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
new file mode 100644
index 00000000..e8e2592c
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -0,0 +1,72 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+/** A Phi for one precise memory alias. */
+public class MemPhiNode extends PhiNode {
+
+ public final int _alias;
+
+ public MemPhiNode(String label, int alias, Node... inputs) {
+ super(label, inputs);
+ assert alias > 1;
+ _alias = alias;
+ }
+
+ public MemPhiNode(MemPhiNode phi) {
+ super(phi,phi._label);
+ _alias = phi._alias;
+ }
+
+ @Override public Tag serialTag() { return Tag.MemPhi; }
+
+ @Override
+ public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
+ IdentityHashMap<Node,Integer> anodes) {
+ super.packed(baos,strs,types,anodes);
+ baos.packed2(_alias);
+ }
+
+ static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ int alias = bais.packed2();
+ if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias);
+ return new MemPhiNode(label,alias,ins);
+ }
+
+ @Override
+ public Type compute() {
+ Type t = super.compute();
+ assert BulkMemPhiNode.checkMem(this,_alias);
+ if( !(t instanceof TypeMem tmem) ) return t;
+ assert tmem._alias==1 || tmem._alias==_alias;
+ // This Phi structurally knows its alias, but not whether the field is
+ // final. Do not infer slice finality from evolving input types.
+ return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs);
+ }
+
+ @Override
+ public Node idealize() {
+ // A precise memory Phi can always bypass a MemMerge and select its
+ // matching slice.
+ for( int i=1; i<nIns(); i++ )
+ if( in(i) instanceof MemMergeNode mem ) {
+ setDef(i,mem.alias(_alias));
+ return this;
+ }
+ return super.idealize();
+ }
+
+ @Override public boolean eq(Node n) {
+ return _alias==((MemPhiNode)n)._alias && super.eq(n);
+ }
+
+ @Override int hash() { return _alias; }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
deleted file mode 100644
index 5bb0889f..00000000
--- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class MinusFNode extends Node {
- public MinusFNode(Node in) { super(null, in); }
-
- @Override public String label() { return "MinusF"; }
-
- @Override public String glabel() { return "-"; }
-
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- in(1)._print0(sb.append("(-"), visited);
- return sb.append(")");
- }
-
- @Override
- public Type compute() {
- if( in(1)._type.isHigh() ) return TypeFloat.TOP;
- if( in(1)._type instanceof TypeFloat i0)
- return i0.isConstant() ? TypeFloat.constant(-i0.value()) : i0;
- return TypeFloat.F64;
- }
-
- @Override
- public Node idealize() {
- // -(-x) is x
- if( in(1) instanceof MinusFNode minus )
- return minus.in(1);
-
- return null;
- }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index 3ab74625..6101fba2 100644
--- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -1,16 +1,32 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public class MinusNode extends Node {
- public MinusNode(Node in) { super(null, in); }
+public class MinusNode extends Node implements ModeNode {
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
- @Override public String label() { return "Minus"; }
+ public MinusNode(Node in) { super(null, in); }
+ public MinusNode(Node in, byte mode) { super(null, in); _mode = mode; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes) {
+ baos.packed1(_mode);
+ }
+ @Override public Tag serialTag() { return Tag.Minus; }
@Override public String glabel() { return "-"; }
+ @Override public byte mode() { return _mode; }
+ @Override public Node setMode(byte mode) {
+ assert _mode==0 && (mode==1 || mode==2);
+ unlock(); // _mode participates in GVN hash and equality
+ _mode = mode;
+ return this;
+ }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -20,11 +36,22 @@ public class MinusNode extends Node {
@Override
public Type compute() {
- if( in(1)._type.isHigh() ) return TypeInteger.TOP;
- if( in(1)._type instanceof TypeInteger i0 &&
- i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE )
- return TypeInteger.make(-i0._max,-i0._min,i0._widen);
- return TypeInteger.BOT;
+ Type t = in(1)._type;
+ if( t.isHigh() )
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ t instanceof TypeFloat ? t : TypeFloat.F64.dual();
+ if( _mode==0 )
+ return Type.BOTTOM;
+ if( _mode==1 ) {
+ if( t instanceof TypeInteger i0 &&
+ i0._min != Long.MIN_VALUE && i0._max != Long.MIN_VALUE )
+ return TypeInteger.make(-i0._max,-i0._min,i0._widen);
+ return TypeInteger.BOT;
+ }
+ if( t instanceof TypeFloat f )
+ return f.isConstant() ? TypeFloat.constant(-f.value()) : f;
+ return TypeFloat.F64;
}
@Override
@@ -33,7 +60,19 @@ public class MinusNode extends Node {
if( in(1) instanceof MinusNode minus )
return minus.in(1);
+ // Can we decide int vs flt?
+ if( _mode==0 ) {
+ byte mode = (byte)(
+ in(1)._type instanceof TypeInteger ? 1 :
+ in(1)._type instanceof TypeFloat ? 2 : 0);
+ if( mode!=0 ) return setMode(mode).init();
+ }
+
return null;
}
- @Override Node copyF() { return new MinusFNode(null); }
+ @Override public Parser.ParseException err() {
+ return ArithNode.nodeErr(this,null,"-",_mode,true);
+ }
+ @Override public boolean eq( Node n ) { return _mode==((MinusNode)n)._mode; }
+ @Override public int hash() { return _mode; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ModeNode.java b/src/main/java/com/seaofnodes/simple/node/ModeNode.java
new file mode 100644
index 00000000..dc635447
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ModeNode.java
@@ -0,0 +1,12 @@
+package com.seaofnodes.simple.node;
+
+/**
+ * A node whose numeric input family is selected once: unknown, integer, or
+ * floating point. Implementors remain ordinary Nodes; this interface only
+ * centralizes the structural mode contract shared by arithmetic, comparisons,
+ * and unary minus.
+ */
+public interface ModeNode {
+ byte mode();
+ Node setMode(byte mode);
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/src/main/java/com/seaofnodes/simple/node/MulFNode.java
deleted file mode 100644
index 5c04af43..00000000
--- a/src/main/java/com/seaofnodes/simple/node/MulFNode.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class MulFNode extends ArithFNode {
- public MulFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "MulF"; }
-
- @Override public String glabel() { return "*"; }
-
- @Override double doOp( double x, double y ) { return x*y; }
-
- @Override
- public Node idealize() {
- Node lhs = in(1);
- Node rhs = in(2);
- Type t1 = lhs._type;
- Type t2 = rhs._type;
-
- // Mul of 1. We do not check for (1*x) because this will already
- // canonicalize to (x*1)
- if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==1 )
- return lhs;
-
- // Move constants to RHS: con*arg becomes arg*con
- if ( t1.isConstant() && !t2.isConstant() )
- return swap12();
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new MulFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/MulNode.java b/src/main/java/com/seaofnodes/simple/node/MulNode.java
index debc2c56..e773ed57 100644
--- a/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -1,17 +1,20 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import java.util.BitSet;
public class MulNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public MulNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public MulNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs, mode); }
+ @Override public Tag serialTag() { return Tag.Mul; }
- @Override public String label() { return "Mul"; }
@Override public String op() { return "*"; }
- @Override long doOp( long x, long y ) { return x * y; }
+ @Override long doOp( long x, long y ) { return x * y; }
+ @Override double doOp(double x, double y ) { return x * y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
return TypeInteger.BOT;
}
@@ -33,7 +36,7 @@ public class MulNode extends ArithNode {
// canonicalize to (x*1)
long c = i.value();
if( c==1 ) return lhs;
- if( c==0 ) return Parser.ZERO;
+ if( c==0 ) return CodeGen.CODE.ZERO;
// Mul by a power of 2, +/-1. Bit patterns more complex than this
// are unlikely to win on an X86 vs the normal "imul", and so
// become machine-specific.
@@ -58,6 +61,5 @@ public class MulNode extends ArithNode {
return super.idealize();
}
- @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs); }
- @Override Node copyF() { return new MulFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new MulNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/src/main/java/com/seaofnodes/simple/node/NeverNode.java
index 516f218b..e2c279d2 100644
--- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -10,7 +10,8 @@ import java.util.BitSet;
// "Never true" for infinite loop exits
public class NeverNode extends IfNode {
public NeverNode(NeverNode ctrl) { super(ctrl); }
- public NeverNode(Node ctrl) { super(ctrl,null); }
+ public NeverNode(float ignore, Node ctrl) { super(ctrl,null); }
+ @Override public Tag serialTag() { return Tag.Never; }
@Override public String label() { return "Never"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/NewNode.java b/src/main/java/com/seaofnodes/simple/node/NewNode.java
index 9310aa38..26a9c0fd 100644
--- a/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -2,107 +2,70 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
- * Allocation! Allocate a chunk of memory, and pre-zero it.
- * The inputs include control and size, and ALL aliases being set.
- * The output is large tuple, one for every alias plus the created pointer.
- * New is expected to be followed by projections for every alias.
- */
+ * Allocation! Allocate a chunk of memory, no init.
+ * Takes in ctrl and size, and TypeStruct.
+ * Produces a ptr and private memory.
+*/
public class NewNode extends Node implements MultiNode {
+ public TypeStruct _ts;
+ public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; }
- public final TypeMemPtr _ptr;
- public final int _len;
+ public Node ctrl() { return in(0); }
+ public Node size() { return in(1); }
- public NewNode(TypeMemPtr ptr, Node... nodes) {
- super(nodes);
- assert !ptr.nullable();
- _ptr = ptr;
- _len = ptr._obj._fields.length;
- // Control in slot 0
- assert nodes[0]._type==Type.CONTROL || nodes[0]._type == Type.XCONTROL;
- // Malloc-length in slot 1
- assert nodes[1]._type instanceof TypeInteger || nodes[1]._type==Type.NIL;
- for( int i=0; i<_len; i++ )
- assert ptr._obj._fields[i]._one || nodes[2 + i]._type.isa( TypeMem.BOT );
+ // --- Serialization
+ public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; }
+ @Override public Tag serialTag() { return Tag.New; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed2(types.get(_ts));
+ }
+ static Node make( BAOS bais, Type[] types) {
+ TypeStruct ts = (TypeStruct)types[bais.packed2()];
+ return new NewNode(ts,null,null);
}
- public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; _len = nnn._len; }
-
- public Node mem (int idx) { return in(idx+2); }
-
- @Override public String label() { return "new_"+glabel(); }
@Override public String glabel() {
- return _ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str();
+ return _ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str();
}
+ @Override public String label() { return "new_"+glabel(); }
- @Override
- public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
sb.append("new ");
- return sb.append(_ptr._obj.str());
+ return sb.append(_ts.str());
}
- int findAlias(int alias) {
- int idx = _ptr._obj.findAlias(alias);
- assert idx!= -1; // Error, caller should be calling
- return idx+2; // Skip ctrl, size
+ @Override public TypeTuple compute() {
+ if( ctrl()._type.isHigh() )
+ return TypeTuple.DEAD_NEW;
+ TypeStruct ts = _ts.isAry() ? _ts : _ts.makeInit();
+ return TypeTuple.make(TypeMemPtr.make(_ts),
+ // Arrays get pre-zeroed by constructor.
+ // Objects start with default-zero memory for
+ // fields that can be zero-initialized. This is
+ // still private memory, so no aliases escape yet.
+ TypeMem.make(1,ts,true,false,false,null,null));
}
- // 0 - ctrl
- // 1 - byte size
- // 2-len+2 - aliases, one per field
- // len+2 - 2*len+2 - initial values, one per field
- public Node size() { return in(1); }
+ @Override public Node idealize() { return null; }
- @Override
- public TypeTuple compute() {
- Field[] fs = _ptr._obj._fields;
- Type[] ts = new Type[fs.length+2];
- if( in(0)._type.isHigh() ) {
- ts[0] = Type.XCONTROL;
- ts[1] = _ptr.makeHigh();
- for( int i=0; i<fs.length; i++ )
- ts[i+2] = Type.TOP;
- } else {
- ts[0] = Type.CONTROL;
- ts[1] = _ptr;
- for( int i=0; i<fs.length; i++ ) {
- if( _ptr._obj._fields[i]._one ) {
- // Once-only fields use the declared type
- ts[i+2] = _ptr._obj._fields[i]._t;
- } else {
- // Others take from the inputs
- Type mt = in(i+2)._type;
- TypeMem mem = mt==Type.TOP ? TypeMem.TOP : (TypeMem)mt;
- //Type tfld = mem._t.meet(mem._t.makeZero());
- Type tfld = mem._t;
- Type tfld2 = tfld.join(fs[i]._t );
- ts[i+2] = TypeMem.make(fs[i]._alias,tfld2);
- }
- }
- }
- return TypeTuple.make(ts);
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts;
}
- @Override
- public Node idealize() {
- Node progress=null;
- for( int i=1; i<nIns(); i++ ) {
- if( in(i) instanceof CastNode cast )
- progress = setDef(i,cast.in(1));
- }
- return progress == null ? null : this;
- }
+ // Two NewNodes are never equal; NewNodes always make independent pointers
+ // so they are not functional - they internally bump-ptr allocate always a
+ // new pointer.
+ @Override public boolean eq(Node n) { return this==n; }
- @Override
- public boolean eq(Node n) { return this == n; }
-
- @Override
- int hash() { return _ptr.hashCode(); }
+ @Override int hash() { return _ts.hashCode(); }
// ------------
// MachNode specifics, shared across all CPUs
@@ -111,19 +74,19 @@ public class NewNode extends Node implements MultiNode {
private RegMask _retMask;
private RegMask _kills;
public void cacheRegs(CodeGen code) {
- _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg();
- _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0);
+ _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0,code._cCallingConv).firstReg();
+ _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv);
// Return mask depends on TFP (either GPR or FPR)
_retMask = code._mach.retMask(TypeFunPtr.CALLOC);
// Kill mask is all caller-saves, and any mirror stack slots for args
// in registers.
- RegMaskRW kills = code._callerSave.copy();
+ RegMaskRW kills = code._cCallerSave.copy();
// Start of stack slots
int maxReg = code._mach.regs().length;
// Incoming function arg slots, all low numbered in the RA
int fslot = cfg0().fun()._maxArgSlot;
// Killed slots for this calls outgoing args
- int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC);
+ int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv);
_xslot = (maxReg+fslot)+xslot;
for( int i=0; i<xslot; i++ )
kills.set((maxReg+fslot)+i);
@@ -132,9 +95,10 @@ public class NewNode extends Node implements MultiNode {
public String op() { return "alloc"; }
public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
public RegMask outregmap() { return null; }
- public RegMask outregmap(int idx) { return idx==1 ? _retMask : null; }
+ public RegMask outregmap(int idx) { return idx==0 ? _retMask : null; }
public RegMask killmap() { return _kills; }
public void asm(CodeGen code, SB sb) {
- sb.p("#calloc, ").p(code.reg(size()));
+ sb.p("#malloc, ").p(code.reg(size()));
}
+
}
diff --git a/src/main/java/com/seaofnodes/simple/node/Node.java b/src/main/java/com/seaofnodes/simple/node/Node.java
index 458a3407..1ce8382a 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -2,14 +2,12 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.IterPeeps;
import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.print.IRPrinter;
import com.seaofnodes.simple.print.JSViewer;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
import java.util.*;
import java.util.function.Function;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
@@ -78,8 +76,83 @@ public abstract class Node implements Cloneable {
_hash = 0;
}
+ // Disk/serialized opcode tags
+ public enum Tag {
+ Add,And,EQ,NE,LT,LE,ULT,
+ CallEnd,Call,CheckCast,ConFldOff,Con,CProj,
+ Div,Escape,Extern,Fun,FunPtr,If,Load,Loop,
+ MemMerge,Minus,Mul,
+ New,Never,Not,Or,Parm,Phi,Proj,
+ ReadOnly,Return,Region,RoundF32,
+ Sar,Shl,Shr,Start,Stop,Store,Struct,Sub,ToFloat,
+ XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt;
+ public static final Tag[] VALS = values();
+ public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ return switch(this) {
+ case Add -> new AddNode(null,null,(byte)bais.packed1());
+ case And -> { int mode=bais.packed1(); assert mode==1; yield new AndNode(null,null,null); }
+ case Div -> new DivNode(null,null,(byte)bais.packed1());
+ case If -> new IfNode(null,null);
+ case Loop -> new LoopNode(null,null,null);
+ case Minus -> new MinusNode(null,(byte)bais.packed1());
+ case Mul -> new MulNode(null,null,(byte)bais.packed1());
+ case Never -> new NeverNode(1.2f, null);
+ case Not -> new NotNode(null);
+ case Or -> { int mode=bais.packed1(); assert mode==1; yield new OrNode(null,null,null); }
+ case ReadOnly-> new ReadOnlyNode((Node)null);
+ case Return ->new ReturnNode(null,null,null,null,null);
+ case RoundF32-> new RoundF32Node(null);
+ case PtrToInt-> new PtrToIntNode((Node)null);
+ case Sar -> { int mode=bais.packed1(); assert mode==1; yield new SarNode(null,null,null); }
+ case Shl -> { int mode=bais.packed1(); assert mode==1; yield new ShlNode(null,null,null); }
+ case Shr -> { int mode=bais.packed1(); assert mode==1; yield new ShrNode(null,null,null); }
+ case Start -> new StartNode(null,null,TypeInteger.BOT);
+ case Sub -> new SubNode(null,null,(byte)bais.packed1());
+ case ToFloat-> new ToFloatNode(null);
+ case XCtrl -> new XCtrlNode();
+ case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); }
+ case Guard -> GuardNode.make(bais);
+
+ case EQ -> new BoolNode.EQ (null,null,(byte)bais.packed1());
+ case NE -> new BoolNode.NE (null,null,(byte)bais.packed1());
+ case LT -> new BoolNode.LT (null,null,(byte)bais.packed1());
+ case LE -> new BoolNode.LE (null,null,(byte)bais.packed1());
+ case ULT -> new BoolNode.ULT(null,null,(byte)bais.packed1());
+
+ case Call -> CallNode.make(bais);
+ case CallEnd-> CallEndNode.make(bais ,types);
+ case CheckCast -> CheckCastNode.make(bais,types);
+ case CProj -> CProjNode.make(bais,strs);
+ case Con -> ConstantNode.make(bais ,types);
+ case Escape-> EscapeNode.make(bais ,types);
+ case Extern-> ExternNode.make(bais,strs,types);
+ case FunPtr-> FunPtrNode.make(bais, types);
+ case Fun -> FunNode.make(bais,strs,types);
+ case Load -> new LoadNode(bais,strs,types,fileAliases,aliases);
+ case MemMerge->MemMergeNode.make(bais);
+ case MemPhi-> MemPhiNode.make(bais,strs,types,fileAliases,aliases);
+ case BulkMemPhi-> BulkMemPhiNode.make(bais,strs,types,fileAliases,aliases);
+ case New -> NewNode.make(bais ,types);
+ case Parm -> ParmNode.make(bais,strs,types);
+ case Phi -> PhiNode.make(bais,strs,types);
+ case Proj -> ProjNode.make(bais,strs);
+ case Store -> new StoreNode(bais,strs,types,fileAliases,aliases);
+ case Stop -> StopNode.make(bais);
+ case StartCU-> StartCUNode.make(bais,strs,types);
+ case StopCU -> StopCUNode.make(bais);
+ case Struct-> StructNode.make(bais, types);
+ case Region-> RegionNode.make(bais);
+
+ default -> throw Utils.TODO();
+ };
+ }
+ };
+ public Tag serialTag() { throw Utils.TODO(); }
+ // Serialize extra data, including input counts
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {}
+
// Easy reading label for debugger, e.g. "Add" or "Region" or "EQ"
- public abstract String label();
+ public String label() { return serialTag().toString(); }
// Unique label for graph visualization, e.g. "Add12" or "Region30" or "EQ99"
public String uniqueName() {
@@ -115,7 +188,7 @@ public abstract class Node implements Cloneable {
// This is the common print: check for repeats, check for DEAD and print
// "DEAD" else call the per-Node print1.
public final StringBuilder _print0(StringBuilder sb, BitSet visited) {
- if (visited.get(_nid) && !(this instanceof ConstantNode) )
+ if( visited.get(_nid) && !(this instanceof ConstantNode) )
return sb.append(label());
visited.set(_nid);
return isDead()
@@ -229,7 +302,8 @@ public abstract class Node implements Cloneable {
}
// Breaks the edge invariants, used temporarily
- protected <N extends Node> void addUse(N n) { _outputs.add(n); }
+ @SuppressWarnings("unchecked")
+ protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
// Remove node 'use' from 'def's (i.e. our) output list, by compressing the list in-place.
// Return true if the output list is empty afterward.
@@ -250,6 +324,12 @@ public abstract class Node implements Cloneable {
old_def.kill(); // Kill old def
}
}
+ public Node removeLast() {
+ Node old_def = _inputs.pop();
+ if( old_def != null )
+ old_def.delUse(this);
+ return old_def;
+ }
/**
* Kill a Node with no <em>uses</em>, by setting all of its <em>defs</em>
@@ -284,11 +364,13 @@ public abstract class Node implements Cloneable {
// Shortcuts to stop DCE mid-parse
// Add bogus null use to keep node alive
- @SuppressWarnings("unchecked")
- public <N extends Node> N keep() { addUse(null); return (N)this; }
+ public <N extends Node> N keep() { return addUse(null); }
// Remove bogus null.
@SuppressWarnings("unchecked")
- public <N extends Node> N unkeep() { delUse(null); return (N)this; }
+ public <N extends Node> N unkeep() {
+ delUse(null);
+ return (N)this;
+ }
// Test "keep" status
public boolean iskeep() { return _outputs.find(null) != -1; }
public void unkill() {
@@ -415,7 +497,15 @@ public abstract class Node implements Cloneable {
// node. If peeps are disabled, still allow high Phis to collapse;
// they typically come from dead Regions, and we want the Region to
// collapse, which requires the Phis to die first.
- if( !isConst() && _type.isHighOrConst() )
+ if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
+ // A high Load through a bad pointer still owns the source error;
+ // do not replace it with an unlocated constant before typeCheck.
+ !(this instanceof LoadNode && err()!=null) &&
+ // A CallEnd can be temporarily high while optimistic call-graph
+ // linking is still discovering its target Returns. Its result
+ // projections must remain attached so they can sharpen later.
+ !(this instanceof ProjNode proj && proj.in(0) instanceof CallEndNode && _type.isHigh()) &&
+ _type.isHighOrConst() )
return ConstantNode.make(_type).peephole();
// Global Value Numbering
@@ -483,7 +573,8 @@ public abstract class Node implements Cloneable {
// If changing, add users to worklist.
public Type setType(Type type) {
Type old = _type;
- assert old == null || type.isa(old) : "Monotonicity test failed";
+ assert old == null || type.isa(old) : "Monotonicity test failed: "+this+"#"+_nid+" old="+old+" new="+type;
+ //assert !type.hasClosedBuilder0(); // New types should not have closed builders
if( old == type ) return old;
_type = type; // Set _type late for easier assert debugging
CODE.addAll(_outputs);
@@ -549,19 +640,30 @@ public abstract class Node implements Cloneable {
// revisit them if `this` changes.
Ary<Node> _deps;
+ private boolean addDepImpl( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return false;
+ if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
+ if( dep._deps .find(this) != -1 ) return false; // Already on list
+ if( dep._outputs.find(this) != -1 ) return false;
+ return true;
+ }
+
/**
* Add a node to the list of dependencies. Only add it if it's not an input
* or output of this node, that is, it is at least one step away. The node
* being added must benefit from this node being peepholed.
*/
- <N extends Node> N addDep( N dep ) {
- // Running peepholes during the big assert cannot have side effects
- // like adding dependencies.
- if( CODE._midAssert ) return dep;
- if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
- if( dep._deps .find(this) != -1 ) return dep; // Already on list
+ public <N extends Node> N addDep( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
if( dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
- if( dep._outputs.find(this) != -1 ) return dep;
+ dep._deps.add(this);
+ return dep;
+ }
+
+ public <N extends Node> N addDepForwards( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
dep._deps.add(this);
return dep;
}
@@ -574,6 +676,7 @@ public abstract class Node implements Cloneable {
_deps.clear();
}
+
// Two nodes are equal if they have the same inputs and the same "opcode"
// which means the same Java class, plus same internal parts.
@Override public final boolean equals( Object o ) {
@@ -630,27 +733,6 @@ public abstract class Node implements Cloneable {
/** Pinned in the schedule; these are data nodes whose input#0 is not allowed to change */
public boolean isPinned() { return false; }
- // Semantic change to the graph (so NOT a peephole), used by the Parser.
- // If any input is a float, flip to a float-flavored opcode and widen any
- // non-float input.
- public final Node widen() {
- if( !hasFloatInput() ) return this;
- Node flt = copyF();
- if( flt==null ) return this;
- for( int i=1; i<nIns(); i++ )
- flt.setDef(i, in(i)._type instanceof TypeFloat ? in(i) : new ToFloatNode(in(i)).peephole());
- kill();
- return flt;
- }
- private boolean hasFloatInput() {
- for( int i=1; i<nIns(); i++ )
- if( in(i)._type instanceof TypeFloat )
- return true;
- return false;
- }
- Node copyF() { return null; }
-
-
// ------------------------------------------------------------------------
// Peephole utilities
@@ -699,7 +781,7 @@ public abstract class Node implements Cloneable {
public Parser.ParseException err() { return null; }
// Common integer constants
- public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); }
+ public static ConstantNode con(long x) { return (ConstantNode)ConstantNode.make(TypeInteger.constant(x)).peephole(); }
// Utility to walk the entire graph applying a function; return the first
// not-null result.
@@ -720,6 +802,20 @@ public abstract class Node implements Cloneable {
return null;
}
+ public void gather(HashMap<String,Integer> strs ) { }
+
+ // If a Type upgrade happens, put on worklist
+ boolean _upgradeType( HashMap<String,Type> TYPES ) { return false; }
+ public final void upgradeType( HashMap<String,Type> TYPES ) {
+ Type old = _type; _type = _type.upgradeType(TYPES);
+ boolean progress = _upgradeType(TYPES);
+ if( progress || old != _type ) {
+ CODE.add(this);
+ CODE.addAll(_outputs);
+ if( _deps!=null ) CODE.addAll(_deps);
+ }
+ }
+
/**
* Debugging utility to find a Node by index
*/
diff --git a/src/main/java/com/seaofnodes/simple/node/NotNode.java b/src/main/java/com/seaofnodes/simple/node/NotNode.java
index 4b80e61c..22b5bffe 100644
--- a/src/main/java/com/seaofnodes/simple/node/NotNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -6,8 +6,7 @@ import java.util.BitSet;
public class NotNode extends Node {
public NotNode(Node in) { super(null, in); }
-
- @Override public String label() { return "Not"; }
+ @Override public Tag serialTag() { return Tag.Not; }
@Override public String glabel() { return "!"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java
index ac0d8454..ba250c26 100644
--- a/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -5,7 +5,8 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class OrNode extends ArithNode {
- public OrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Or; }
@Override public String label() { return "Or"; }
@Override public String op() { return "|"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 880f936c..6488b827 100644
--- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -1,30 +1,59 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.Parser;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
public class ParmNode extends PhiNode {
// Argument indices are mapped one-to-one on CallNode inputs
public final int _idx; // Argument index
+ public Type _declaredType;
public ParmNode(String label, int idx, Type declaredType, Node... inputs) {
- super(label,declaredType,inputs);
+ super(label, inputs);
_idx = idx;
+ _declaredType = declaredType;
+ _type = declaredType;
+ }
+ 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.packed2(_label==null ? 0 : strs.get(_label));
+ baos.packed2(types.get(_declaredType)); // NPE if fails lookup
+ baos.packed1(_idx);
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ String label = strs[bais.packed2()];
+ Type minType = types[bais.packed2()];
+ return new ParmNode(label, bais.packed1(), minType, ins);
}
- public ParmNode(ParmNode parm) { super(parm, parm._label, parm._minType ); _idx = parm._idx; }
@Override public String label() { return MemOpNode.mlabel(_label); }
@Override public String glabel() { return _label; }
+ @Override protected Type declaredType() { return _declaredType; }
+ @Override protected Type constrain(Type t) { return t.join(_declaredType); }
+
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type t = _declaredType.upgradeType(TYPES);
+ if( t == _declaredType ) return false;
+ unlock();
+ _declaredType = t;
+ return true;
+ }
+
public FunNode fun() { return (FunNode)in(0); }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
- if( "main".equals(fun()._name) && _label.equals("arg") )
+ if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
return sb.append("arg");
sb.append("Parm_").append(_label).append("(");
for( Node in : _inputs ) {
@@ -48,14 +77,6 @@ public class ParmNode extends PhiNode {
// If function is folding, do all possible peeps
if( fun()._folding ) return super.idealize();
- // Can upgrade minType even while in-progress
- if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
- TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
- if( tmp2!=null && tmp2 != _minType ) {
- _minType = tmp2;
- return this;
- }
- }
// Skip most phi optimizations on parms
return null;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index bdc0d0fa..2c57eb8b 100644
--- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -1,39 +1,51 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
public class PhiNode extends Node {
public final String _label;
- // The Phi type we compute must stay within the domain of the Phi. Example
- // Int stays Int, Ptr stays Ptr, Control stays Control, Mem stays Mem.
- Type _minType;
-
- int lattice_drop;
-
- public PhiNode(String label, Type minType, Node... inputs) {
+ public PhiNode( String label, Node... inputs) {
super(inputs);
_label = label;
- assert minType!=null;
- _minType = minType;
+ }
+ public static PhiNode make(String label, Type minType, Node... inputs) {
+ return minType instanceof TypeMem mem
+ ? mem._alias==1
+ ? new BulkMemPhiNode(label,inputs)
+ : new MemPhiNode(label,mem._alias,inputs)
+ : new PhiNode(label, inputs);
}
// Used by ParmNode
- public PhiNode(PhiNode phi, String label, Type minType) { super(phi); _label = label; _type = _minType = minType; }
+ public PhiNode(PhiNode phi, String label) { super(phi); _label = label; }
// Used by instruction Selection
- public PhiNode(PhiNode phi) { this(phi,phi._label,phi._minType ); }
+ public PhiNode(PhiNode phi) { this(phi,phi._label); }
// Used by the infinite-loop exit breaker
public PhiNode(RegionNode r, Node sample) {
super(new Node[]{r});
_label = "";
- _minType = sample._type;
+ _type = sample._type;
while( nIns() < r.nIns() )
addDef(sample);
}
+ @Override public Tag serialTag() { return Tag.Phi; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(nIns());
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types) {
+ Node[] ins = new Node[bais.packed1()];
+ return new PhiNode(strs[bais.packed2()], ins);
+ }
@Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
@@ -55,7 +67,7 @@ public class PhiNode extends Node {
}
public CFGNode region() { return (CFGNode)in(0); }
- @Override public boolean isMem() { return _minType instanceof TypeMem; }
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
@Override public boolean isPinned() { return true; }
boolean isRPC() { return false; }
@@ -65,14 +77,10 @@ public class PhiNode extends Node {
return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
// During parsing Phis have to be computed type pessimistically.
if( r.inProgress() )
- // Loop-Phis must lift to the declared type, because that is how
- // the Parser keeps precise types until the loop finishes parsing.
- // Similar, ParmNodes use precise minType until all calls are
- // linked (post opto).
- return r instanceof LoopNode || (this instanceof ParmNode) ? _minType : Type.BOTTOM;
+ return declaredType();
// Set type to local top of the starting type
Type t = Type.TOP;
- for (int i = 1; i < nIns(); i++) {
+ for( int i = 1; i < nIns(); i++ ) {
// If the region's control input is live, add this as a dependency
// to the control because we can be peeped should it become dead.
Type ctrl = addDep(r.in(i))._type;
@@ -82,7 +90,8 @@ public class PhiNode extends Node {
t = t.meet(in(i)._type);
}
}
- Type newt = t.join( _minType );
+ Type declared = declaredType();
+ Type newt = constrain(t);
// phi loop widening part
if( region() instanceof LoopNode && // Only around loops
@@ -90,60 +99,45 @@ public class PhiNode extends Node {
// Types changed and are falling (the optimistic case, expected to fall forever)
newi != _type ) {
if( !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) )
- return newi.same_but_slightly_wider_than(_minType);
+ return newi.same_but_slightly_wider_than(
+ declared instanceof TypeInteger ? declared : TypeInteger.BOT);
}
return newt;
}
+ // Ordinary Phis have no parser-supplied type constraint.
+ protected Type declaredType() { return Type.BOTTOM; }
+ protected Type constrain(Type t) { return t; }
+
@Override
public Node idealize() {
if( !(region() instanceof RegionNode r ) )
return in(1); // Input has collapse to e.g. starting control.
- // Can upgrade minType even while in-progress
- if( _minType instanceof TypeMemPtr tmp && _minType.isFRef() ) {
- TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
- if( tmp2!=null && tmp2 != _minType ) {
- _minType = tmp2;
- return this;
- }
- }
- if( r.inProgress() || r.nIns()<=1 )
+ if( r.inProgress() || r.nIns()<=1 || nOuts()==0 )
return null; // Input is in-progress
// If we have only a single unique input, become it.
Node live = singleUniqueInput();
- if( live != null ) {
- if( live._type.isa(_type) )
- return live;
- // Keep the Phi upcast
- return new CastNode(_type,null,live);
- }
+ if( live != null )
+ return live;
// No bother if region is going to fold dead paths soon
for( int i=1; i<nIns(); i++ )
if( r.in(i)._type == Type.XCONTROL )
return null;
- // Simple Phi-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( _type instanceof TypeMem tmem && tmem._alias!=1 ) {
- for( int i=1; i<nIns(); i++ )
- if( in(i) instanceof MemMergeNode mem ) {
- setDef(i,mem.alias(tmem._alias));
- return this;
- }
- }
-
// Generic "pull down op"
Node progress;
if( same_op() && (progress = drop_same_op()) != null )
return progress;
- // If merging Phi(N, cast(N)) - we are losing the cast JOIN effects, so just remove.
+ // If merging Phi(ZERO, guardNZ(N)) - we are losing the cast JOIN effects, so just remove.
if( nIns()==3 ) {
- if( in(1) instanceof CastNode cast && addDep(cast.in(1))==in(2) ) return in(2);
- if( in(2) instanceof CastNode cast && addDep(cast.in(1))==in(1) ) return in(1);
+ if( in(1) instanceof GuardNode cast && cast._nonZero && in(2)._type.makeZero()==in(2)._type && cast.in(1)!=this ) return cast.in(1);
+ if( in(2) instanceof GuardNode cast && cast._nonZero && in(1)._type.makeZero()==in(1)._type && cast.in(1)!=this ) return cast.in(1);
}
+
// If merging a null-checked null and the checked value, just use the value.
// if( val ) ..; phi(Region,False=0/null,True=val);
// then replace with plain val.
@@ -153,12 +147,12 @@ public class PhiNode extends Node {
if( in(2)._type == in(2)._type.makeZero() ) nullx = 2;
if( nullx != -1 ) {
Node val = in(3-nullx);
- if( val instanceof CastNode cast )
+ if( val instanceof GuardNode cast && cast._nonZero )
val = cast.in(1);
Node ridom = r.idom(this);
if( ridom instanceof IfNode iff && addDep(iff.pred())==val ) {
// Must walk the idom on the null side to make sure we hit False.
- CFGNode idom = (CFGNode)r.in(nullx);
+ CFGNode idom = r.cfg(nullx);
while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom();
if( idom instanceof CProjNode proj && proj._idx==1 )
return val;
@@ -172,17 +166,37 @@ public class PhiNode extends Node {
// Same op on all Phi paths; all ops have only the Phi as a use.
// None have a control input.
private boolean same_op() {
+ Node busy=null;
for( int i=1; i<nIns(); i++ ) {
Node op = in(i);
+ if( op.nOuts()==0 )
+ { op.kill(); return false; }
if( in(1).getClass() != op.getClass() || op.in(0)!=null || in(1).nIns() != op.nIns() )
return false; // Wrong class or CFG bound or mismatched inputs
- if( in(1) instanceof MemOpNode mem && mem._alias != ((MemOpNode)op)._alias )
- return false;
- if( op.nOuts() > 1 ) { // Too many users, but addDep in case lose users
- for( Node out : op._outputs )
- if( out!=null && out!=this )
- addDep(out);
+ if( in(1) instanceof MemOpNode mem ) {
+ // Mismatched aliases
+ if( mem._alias != ((MemOpNode)op)._alias ) return false;
+ // Load is clobbered somewhere, and can not be pulled forward past the Phi?
+ if( mem instanceof LoadNode )
+ for( Node use : op.outs() )
+ if( use instanceof StoreNode )
+ return false;
+ }
+ if( in(1) instanceof EscapeNode )
return false;
+ if( in(1) instanceof MemMergeNode )
+ return false; // Have to keep aliases straight
+ if( op.nOuts() > 1 ) {
+ if( busy==null ) busy = op;
+ else { // Too many users, but addDep in case lose users
+ for( Node out : op._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ for( Node out : busy._outputs )
+ if( out!=null && out!=this )
+ addDep(out);
+ return false;
+ }
}
for( int j=1; j<in(1).nIns(); j++ )
if( op.in(j) instanceof ScopeNode || (op.in(j)==null ^ in(1).in(j)==null) )
@@ -205,7 +219,7 @@ public class PhiNode extends Node {
if( in(i).in(j) != x )
{ needsPhi=true; break; }
if( needsPhi ) {
- x = new PhiNode(_label,op.in(j)._type.glb(false));
+ x = make(_label,op.in(j)._type.glb(false));
x.addDef(region());
for( int i=1; i<nIns(); i++ )
x.addDef(in(i).in(j));
@@ -253,39 +267,28 @@ public class PhiNode extends Node {
}
// Never equal if inProgress.
- // Also, joins
@Override public boolean eq( Node n ) {
- if( inProgress() ) return false;
- Type min = ((PhiNode)n)._minType;
- if( _minType==min ) return true;
- Type mt = min.meet(_minType);
- if( min!=mt && _minType!=mt ) return false;
- //// Theory says these 2 Phis CAN be merged/GVNd, but I need to pick the
- //// most general minType.
- //_minType = ((PhiNode)n)._minType = mt;
- //return true;
- return false;
+ return !inProgress() && super.eq(n);
}
@Override
public Parser.ParseException err() {
- if( _type != Type.BOTTOM ) return null;
+ if( _type != Type.BOTTOM && _type != TypeScalar.BOT ) return null;
- // BOTTOM means we mixed e.g. int and ptr
+ // Scalar BOT means we mixed scalar families, e.g. int and ptr.
+ // Global BOTTOM retains the same role for non-scalar families.
for( int i=1; i<nIns(); i++ )
// Already an error, but better error messages come from elsewhere
- if( in(i)._type == Type.BOTTOM )
+ if( in(i)._type == Type.BOTTOM || in(i)._type == TypeScalar.BOT )
return null;
- // Gather a minimal set of types that "cover" all the rest
- boolean ti=false, tf=false, tp=false, tn=false;
- for( int i=1; i<nIns(); i++ ) {
- Type t = in(i)._type;
- ti |= t instanceof TypeInteger x;
- tf |= t instanceof TypeFloat x;
- tp |= t instanceof TypeMemPtr x;
- tn |= t==Type.NIL;
- }
- return ReturnNode.mixerr(ti,tf,tp,tn, ((RegionNode)region())._loc);
+ SB sb = new SB().p("No common type amongst ");
+ for( int i=1; i<nIns(); i++ )
+ sb.p(in(i)._type.toString()).p(" and ");
+ return Parser.error(sb.unchar(5).toString(),null);
+ }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/Proj.java b/src/main/java/com/seaofnodes/simple/node/Proj.java
new file mode 100644
index 00000000..c8b9378f
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/Proj.java
@@ -0,0 +1,2 @@
+package com.seaofnodes.simple.node;
+public interface Proj { int idx(); }
diff --git a/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index 586527c8..97b9ed45 100644
--- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -1,12 +1,15 @@
package com.seaofnodes.simple.node;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeMem;
import com.seaofnodes.simple.type.TypeTuple;
-
+import com.seaofnodes.simple.util.BAOS;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
-public class ProjNode extends Node {
+public class ProjNode extends Node implements Proj {
// Which slice of the incoming multipart value
public final int _idx;
@@ -20,6 +23,14 @@ public class ProjNode extends Node {
_label = label;
}
public ProjNode(ProjNode p) { super(p); _idx = p._idx; _label = p._label; }
+ @Override public Tag serialTag() { return Tag.Proj; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ baos.packed1(_idx);
+ baos.packed2(_label==null ? 0 : strs.get(_label));
+ }
+ static Node make( BAOS bais, String[] strs ) {
+ return new ProjNode(null, bais.packed1(), strs[bais.packed2()] );
+ }
@Override public String label() { return _label; }
@@ -51,4 +62,10 @@ public class ProjNode extends Node {
@Override
int hash() { return _idx; }
+
+ @Override public int idx() { return _idx; }
+
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_label);
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
new file mode 100644
index 00000000..9a9b94fd
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
@@ -0,0 +1,17 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/** Zero-code machine form of {@link PtrToIntNode}. */
+public class PtrToIntMach extends PtrToIntNode implements MachNode {
+ public PtrToIntMach(PtrToIntNode ptr) { super(ptr); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress() { return 1; }
+ @Override public void encoding(Encoding enc) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
new file mode 100644
index 00000000..d781620b
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
@@ -0,0 +1,40 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
+
+import java.util.BitSet;
+
+/**
+ * Reinterpret a non-null managed pointer as its raw integer address.
+ *
+ * This node changes representation families but emits no code. In particular,
+ * it is not a {@link CheckCastNode}: no control-dependent proof can turn a
+ * pointer into an integer. Array-to-C-pointer conversion adds the array body
+ * offset separately.
+ */
+public class PtrToIntNode extends Node {
+ public PtrToIntNode(Node ptr) { super(null,ptr); }
+ public PtrToIntNode(PtrToIntNode ptr) { super(ptr); }
+
+ @Override public Tag serialTag() { return Tag.PtrToInt; }
+ @Override public String glabel() { return "(i64*)"; }
+ @Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
+ return in(1)._print0(sb.append("(i64*)"),visited);
+ }
+
+ @Override public Type compute() {
+ return in(1)._type.isHigh() ? TypeInteger.TOP : TypeInteger.BOT;
+ }
+
+ @Override public Node idealize() { return null; }
+
+ @Override public Parser.ParseException err() {
+ Type t = in(1)._type;
+ if( t instanceof TypeMemPtr ptr )
+ return ptr.notNull() ? null : Parser.error("Cannot convert a nullable pointer to an integer",null);
+ return Parser.error("Cannot convert "+t.str()+" to an integer pointer",null);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
index d8fe6286..c36be85a 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
@@ -10,7 +10,7 @@ import java.util.BitSet;
public class ReadOnlyNode extends Node {
public ReadOnlyNode( Node n ) { super(null,n); }
public ReadOnlyNode( ReadOnlyNode n ) { super(n); }
- @Override public String label() { return "ReadOnly"; }
+ @Override public Tag serialTag() { return Tag.ReadOnly; }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index b115a260..dc48f6f2 100644
--- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -1,9 +1,9 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.*;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
import java.util.*;
public class RegionNode extends CFGNode {
@@ -13,9 +13,9 @@ public class RegionNode extends CFGNode {
public RegionNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
public RegionNode(RegionNode r, Parser.Lexer loc) { super(r); _loc = loc; }
public RegionNode(RegionNode r) { super(r); if( r!=null ) _loc = r._loc; }
-
- @Override
- public String label() { return "Region"; }
+ @Override public Tag serialTag() { return Tag.Region; }
+ public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) { return new RegionNode(null,new Node[bais.packed1()]); }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -75,7 +75,7 @@ public class RegionNode extends CFGNode {
// Fold Region
for( int j=1; j<region.nIns(); j++ )
addDef(region.in(j));
- setDef(i,Parser.XCTRL);
+ setDef(i,CodeGen.CODE.XCTRL);
return this;
}
}
@@ -111,7 +111,7 @@ public class RegionNode extends CFGNode {
CodeGen.CODE.addAll(phi.delDef(path)._outputs);
}
}
- return isDead() ? Parser.XCTRL : delDef(path);
+ return isDead() ? CodeGen.CODE.XCTRL : delDef(path);
}
private int findDeadInput(int startIdx) {
diff --git a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 0aa70214..b60d27d7 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -27,6 +27,7 @@ public class ReturnNode extends CFGNode {
_fun = fun;
}
public ReturnNode( ReturnNode ret, FunNode fun ) { super(ret); _fun = fun; }
+ @Override public Tag serialTag() { return Tag.Return; }
public Node ctrl() { return in(0); }
public Node mem () { return in(1); }
@@ -35,9 +36,6 @@ public class ReturnNode extends CFGNode {
@Override public FunNode fun() { return _fun; }
@Override
- public String label() { return "Return"; }
-
- @Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
sb.append("return ");
if( expr()==null ) sb.append("----");
@@ -50,12 +48,10 @@ public class ReturnNode extends CFGNode {
@Override
public Type compute() {
- if( inProgress () ) return TypeTuple.RET; // In progress
return TypeTuple.make(ctrl()._type,mem()._type,expr()._type);
}
@Override public Node idealize() {
- if( inProgress () ) return null;
if( _fun.isDead() ) return null;
// Upgrade signature based on return type
@@ -64,87 +60,44 @@ public class ReturnNode extends CFGNode {
if( ret != fcn.ret() && ret.isa(fcn.ret()) )
_fun.setSig(fcn.makeFrom(ret));
- // If dead (cannot be reached; infinite loop), kill the exit values
- if( ctrl()._type==Type.XCONTROL &&
- !(mem() instanceof ConstantNode && expr() instanceof ConstantNode) ) {
- Node top = new ConstantNode(Type.TOP).peephole();
- setDef(1,top);
- setDef(2,top);
- return this;
- }
-
return null;
}
- public boolean inProgress() {
- return ctrl().getClass() == RegionNode.class && ((RegionNode)ctrl()).inProgress();
- }
-
- // Gather parse-time return types for error reporting
- private Type mt = Type.TOP;
- private boolean ti=false, tf=false, tp=false, tn=false;
-
- // Add a return exit to the current parsing function
- void addReturn( Node ctrl, Node rmem, Node expr ) {
- assert inProgress();
-
- // Gather parse-time return types for error reporting
- Type t = expr._type;
- mt = mt.meet(t);
- ti |= t instanceof TypeInteger x;
- tf |= t instanceof TypeFloat x;
- tp |= t instanceof TypeMemPtr x;
- tn |= t==Type.NIL;
-
- // Merge path into the One True Return
- RegionNode r = (RegionNode)ctrl();
- // Assert that the Phis are in particular outputs; not reordered or shuffled
- PhiNode mem = (PhiNode)r.out(0); assert mem._minType == TypeMem.BOT;
- PhiNode rez = (PhiNode)r.out(1); assert rez._minType == Type.BOTTOM;
- // Pop "inProgress" null off
- r ._inputs.pop();
- mem._inputs.pop();
- rez._inputs.pop();
- // Add new return point
- r .addDef(ctrl);
- mem.addDef(rmem);
- rez.addDef(expr);
- // Back to being inProgress
- r .addDef(null);
- mem.addDef(null);
- rez.addDef(null);
- }
-
@Override public Parser.ParseException err() {
- if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead
- return expr()._type==Type.BOTTOM || expr()._type==Type.TOP ? mixerr(ti,tf,tp,tn,_fun._loc) : null;
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == Type.TOP )
+ return Parser.error("No defined return type",null);
+ // With no user constructor, <init> doubles as the public no-arg
+ // constructor. Required fields remain TOP in its private memory: a
+ // typed parser poison which deliberately emitted no Store or code.
+ if( ctrl()._type == Type.CONTROL && _fun.isInstance() ) {
+ TypeStruct self = ((TypeMemPtr)_fun.sig().arg(0))._obj;
+ if( !CodeGen.hasUserConstructor(self) && expr()._type instanceof TypeMem mem )
+ for( Field fld : self._fields ) {
+ if( !(fld._t instanceof TypeNil tn && tn.notNull()) ) continue;
+ Type actual = mem._alias==1 && mem._t instanceof TypeStruct ts
+ ? ts.field(fld._fname)._t
+ : mem._alias==fld._alias ? mem._t : Type.TOP;
+ if( actual==Type.TOP || actual instanceof TypeNil atn && atn.nullable() )
+ return Parser.error("'"+self._name+"' is not fully initialized, field '"+
+ fld._fname+"' is only partially set in the constructor",null);
+ }
+ }
+ return null;
}
- static Parser.ParseException mixerr( boolean ti, boolean tf, boolean tp, boolean tn, Parser.Lexer loc ) {
- if( !ti && !tf && !tp && !tn )
- // Hit when the function never returns. e.g. `f = { -> return f() };`
- return Parser.error("No defined return type", loc);
- // Rather ugly way to print conflicting return types
- int cnt = (ti?1:0) + (tf?1:0) + (tp||tn?1:0);
- if( cnt==1 ) return null; // Uniform return type, something else errored and will report
- SB sb = new SB().p("No common type amongst ");
- if( ti ) sb.p("int and ");
- if( tf ) sb.p("f64 and ");
- if( tp || tn ) sb.p("reference and ");
- return Parser.error(sb.unchar(5).toString(),loc);
+ @Override public boolean eq( Node n ) {
+ return !_fun.isDead() && !((ReturnNode)n)._fun.isDead();
}
-
// ------------
// MachNode specifics, shared across all CPUs
public String op() {
- return _fun._frameAdjust > 0 ? "addi" : "ret";
+ return _fun._frameAdjust > 0 ? "epilog" : "ret ";
}
// Correct Nodes outside the normal edges
public void postSelect(CodeGen code) {
- FunNode fun = (FunNode)rpc().in(0);
- _fun = fun;
- fun.setRet(this);
+ _fun.setRet(this);
}
public RegMask regmap(int i) {
return i==2
@@ -156,13 +109,14 @@ public class ReturnNode extends CFGNode {
public void asm(CodeGen code, SB sb) {
int frameAdjust = fun()._frameAdjust;
if( frameAdjust>0 )
- sb.p("rsp += #").p(frameAdjust).p("\nret");
+ sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret ");
// Post code-gen, just print the "ret"
- if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
+ if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() ||
+ (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) )
// Prints return reg (either RAX or XMM0), RPC and then the
// callee-save registers.
for( int i=2; i<nIns(); i++ )
- sb.p(code.reg(in(i),fun())).p(" ");
+ sb.p(code.reg(in(i))).p(" ");
// If we did not get the expected rpc, print which one we got
else if( code._regAlloc.regnum(rpc()) != code._mach.rpc() )
sb.p("[").p(code.reg(rpc())).p("]");
diff --git a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
index 7936086d..322a6bc0 100644
--- a/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -7,8 +7,7 @@ import java.util.BitSet;
public class RoundF32Node extends Node {
public RoundF32Node(Node lhs) { super(null, lhs); }
-
- @Override public String label() { return "RoundF32"; }
+ @Override public Tag serialTag() { return Tag.RoundF32; }
@Override public String glabel() { return "(f32)"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java
index ee15fa65..8ad5988d 100644
--- a/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class SarNode extends ArithNode {
- public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public SarNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Sar; }
- @Override public String label() { return "Sar"; }
@Override public String op() { return ">>"; }
@Override public String glabel() { return "&gt;&gt;"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 36746a81..58508a1c 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -3,11 +3,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.Var;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.Utils;
-
import java.util.*;
/**
@@ -31,21 +31,23 @@ public class ScopeNode extends MemMergeNode {
// Lexical scope is typed:
public static class Kind {
// Basic block scoping
- public static class Block extends Kind { }
+ public static class Block extends Kind { }
+ // Constructor/initializer block for an allocated struct.
+ public static class Constructor extends Block {
+ public Constructor(TypeStruct self) { _self = self; }
+ public final TypeStruct _self;
+ }
// Function scope
- public static class Func extends Kind { }
- // Struct definition, mapping types to fields
- public static class Define extends Kind { public Define(TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
- // Struct allocation, mapping values to fields
- public static class Alloc extends Kind { public Alloc (TypeMemPtr tmp) { _tmp=tmp; } public final TypeMemPtr _tmp; }
+ public static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; }
public int _lexSize; // Number of Vars in this scope
}
public final Ary<Kind> _kinds;
// Lexical scope nesting depth
public int depth() { return _kinds._len; }
+ public Kind klast() { return _kinds.last(); }
- // Extra guards; tested predicates and casted results
+ // Extra guards; tested predicates and cast results
private final Ary<Node> _guards;
// A new ScopeNode
@@ -64,8 +66,8 @@ public class ScopeNode extends MemMergeNode {
int j=1;
for( int i=0; i<nIns(); i++ ) {
if( j < depth() && i == _kinds.at(j)._lexSize ) { sb.append("| "); j++; }
- Var v = _vars.at(i);
- sb.append(v.type());
+ Var v = var(i);
+ sb.append(v._type());
sb.append(" ");
if( v._final ) sb.append("!");
sb.append(v._name);
@@ -85,7 +87,7 @@ public class ScopeNode extends MemMergeNode {
public Node ctrl() { return in(0); }
- public MemMergeNode mem() { return (MemMergeNode)in(1); }
+ public Node mem() { return in(1); }
public Var var(int i) { return _vars.at(i); }
/**
@@ -108,7 +110,11 @@ public class ScopeNode extends MemMergeNode {
// Pop a lexical scope
public void pop() {
- promote(); // Promote forward references to the next outer scope
+ promote(null); // Promote forward references to the next outer scope
+ _pop();
+ }
+ // Pop a scope, no promote FRefs
+ public void _pop() {
int n = _kinds.pop()._lexSize;
popUntil(n); // Pop off inputs going out of scope
_vars.setLen(n); // Pop off variables going out of scope
@@ -117,59 +123,85 @@ public class ScopeNode extends MemMergeNode {
// Look for forward references in the last lexical scope and promote to the
// next outer lexical scope. At the last scope declare them an error.
- public void promote() {
- Kind kind = _kinds.last();
+ public void promote(CodeGen code) {
+ Kind kind = klast();
int n = kind._lexSize;
for( int i=n; i<nIns(); i++ ) {
Var v = var(i);
+ Node vn = in(i);
if( !v.isFRef() ) continue;
- if( depth()==1 )
- throw Parser.error("Undefined name '" + v._name + "'",v._loc);
- _vars .swap(n,i);
- _inputs.swap(n,i);
- v._idx = n;
+ // Shuffle vars and inputs to move the forward reference to
+ // *before* the start of the using scope ("as if" it was not
+ // actually a forward reference).
+ for( int j=n; j<=i; j++ ) {
+ Var vtmp = var(j);
+ Node ntmp = in (j);
+ v._idx = j;
+ _vars .set(j,v );
+ _inputs.set(j,vn);
+ v = vtmp;
+ vn = ntmp;
+ }
kind._lexSize = ++n;
}
}
- public boolean inConstructor() { return _kinds.last() instanceof Kind.Define; }
- public boolean inAllocation () { return _kinds.last() instanceof Kind.Alloc ; }
- public boolean inFunction () { return _kinds.last() instanceof Kind.Func ; }
-
- public Kind kind( Var v ) {
+ public boolean inFunction () { return klast() instanceof Kind.Func; }
+ // Is any enclosing scope a constructor?
+ public boolean inConstructor() { return constructorSelf() != null; }
+ // Nearest constructor self type, if any.
+ public TypeStruct constructorSelf() {
for( int i=depth()-1; i>=0; i-- )
- if( v._idx >= _kinds.at(i)._lexSize )
- return _kinds.at(i);
- throw Utils.TODO();
+ if( _kinds.at(i) instanceof Kind.Constructor ctor )
+ return ctor._self;
+ else if( _kinds.at(i) instanceof Kind.Func func && FunNode.isInstance(func._name) )
+ return ((TypeMemPtr)var(_kinds.at(i)._lexSize).type())._obj;
+ return null;
}
- // Return the kind for the defining scope, or a function scope if found
- // first. Block scopes means the variable can be r/w directly in the
- // scope, Function scopes require final constants (no capture), and
- // Constructor scopes mean an instance variable reference.
-
- // Walk up-lexical scope looking for defining lexical Kind.
- // - Skip any amount of nested block scopes
- // - Walking out of a function requires v be a final constant (for now) -
- // and we can stop walking.
+ // Find nearest enclosing function scope
+ public int enclosingFunction() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
- // - Walking out of a method and (skipping Blocks) into the matching struct
- // is OK, and this is a instance var load.
+ // Find nearest enclosing function or declaration scope
+ public int enclosingFuncOrDecl() {
+ for( int i=depth()-1; i>=0; i-- )
+ if( _kinds.at(i) instanceof Kind.Func func )
+ return i;
+ throw Utils.TODO("Should not reach here");
+ }
- // - Walking out of a method and into the wrong struct is an error;
- // including nested structs containing (no nested classes yet)
+ // Which lexical scope contains this Var?
+ public int kindx( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize )
+ return i;
+ return -1;
+ }
- // Returns error, forward-ref, ok containing struct, ok containing function/block.
+ // Which lexical scope contains this Var?
+ public Kind kind( Var v ) {
+ int kx = kindx(v);
+ return kx == -1 ? null : _kinds.at(kx);
+ }
- public String outOfFunction( Var v ) {
- //if( v==null ) return null; // Prolly forward reference
- //int i; for( i=_lexSize._len-1; i>=0 && v._idx<_lexSize.at(i); i-- )
- // if( _kinds.at(i)=="{->}" ) return "{->}";
- //return _kinds.at(i);
- throw Utils.TODO();
+ public int kindFcnx( Var v ) {
+ for( int i=depth()-1; i>=0; i-- )
+ if( v._idx >= _kinds.at(i)._lexSize && _kinds.at(i) instanceof Kind.Func )
+ return i;
+ throw Utils.TODO("Should not reach here");
}
+ // Which function scope contains this Var?
+ public Kind kindFcn( Var v ) {
+ int kx = kindFcnx(v);
+ return kx == -1 ? null : _kinds.at(kx);
+ }
// Find name in reverse, return an index into _vars or -1. Linear scan
// instead of hashtable, but probably doesn't matter until the scan
@@ -185,9 +217,9 @@ public class ScopeNode extends MemMergeNode {
* Create a new variable name in the current scope
*/
public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) {
- assert _kinds.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory
+ assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory
if( depth() > 0 )
- for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) {
+ for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) {
Var n = var(i);
if( n._name.equals(name) ) {
if( !n.isFRef() ) return false; // Double define
@@ -195,21 +227,47 @@ public class ScopeNode extends MemMergeNode {
if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant
n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location
setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also
+ return true;
}
}
- Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL);
+ Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL);
_vars.add(v);
// Creating a forward reference
- if( init==Parser.XCTRL )
- init = new FRefNode(v).init();
+ if( init==CodeGen.CODE.XCTRL )
+ //init = new FRefNode(v).init();
+ throw Utils.TODO();
addDef(init);
return true;
}
- // Read from memory
- public Node mem( int alias ) { return mem()._mem(alias,null); }
- // Write to memory
- public void mem( int alias, Node st ) { mem()._mem(alias,st); }
+ // Normal lookup was tried and failed. Insert a forward ref outside any
+ // enclosing Kind.Func scope, skipping any nested Blocks or Allocs and
+ // return the Var. May insert at the outermost scope, which means this
+ // must be defined externally, or it's an error.
+
+ public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) {
+ // Kind/Lexical scope index
+ int kidx = enclosingFunction();
+ int idx = _kinds.at(kidx)._lexSize;
+ Var var = new Var(idx,id,t,xfinal,loc,true);
+ FRefNode fref = new FRefNode(id,loc).init();
+ fref._type = fref._con = t;
+ // Insert in the lex scope just prior to kidx
+ insert(var,fref,kidx);
+ return var;
+ }
+
+
+ // Parser state carries one complete, conservative memory value. Precise
+ // alias partitions belong to the graph and are introduced by peepholes.
+ public Node mem( int alias ) {
+ assert alias == 1;
+ return in(update(var(1),null));
+ }
+ public void mem( int alias, Node st ) {
+ assert alias == 1;
+ update(var(1),st);
+ }
/**
@@ -247,7 +305,7 @@ public class ScopeNode extends MemMergeNode {
// Set real Phi in the loop head
// The phi takes its one input (no backedge yet) from a recursive
// lookup, which might have insert a Phi in every loop nest.
- : loop.setDef(v._idx,new PhiNode(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
+ : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init());
setDef(v._idx,old);
}
//assert !v._final || st==null;
@@ -285,15 +343,9 @@ public class ScopeNode extends MemMergeNode {
n.keep();
dup.addDef(ctrl()); // Control input is just copied
- // Memory input is a shallow copy
- MemMergeNode memdup = new MemMergeNode(true), mem = mem();
- memdup.addDef(null);
- memdup.addDef(loop ? this : mem.in(1));
- for( int i=2; i<mem.nIns(); i++ )
- // For lazy phis on loops we use a sentinel
- // that will trigger phi creation on update
- memdup.addDef(loop ? this : mem.in(i));
- dup.addDef(memdup);
+ // Memory is one ordinary parser-state value. At loop heads the
+ // Scope sentinel triggers the same lazy-Phi machinery as variables.
+ dup.addDef(loop ? this : mem());
// Copy of other inputs
for( int i=2; i<nIns(); i++ )
@@ -313,39 +365,49 @@ public class ScopeNode extends MemMergeNode {
*/
public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
- mem()._merge(that.mem(),r);
- this ._merge(that ,r);
+ this._merge(that,r);
that.kill(); // Kill merged scope
CodeGen.CODE.add(r);
return r.unkeep();
}
- private void _merge(ScopeNode that, RegionNode r) {
- for( int i = 2; i < nIns(); i++)
+ public void _merge(ScopeNode that, RegionNode r) {
+ _merge(that,r,nIns());
+ }
+ public void _merge(ScopeNode that, RegionNode r, int max ) {
+ for( int i = 1; i < max; i++)
if( in(i) != that.in(i) ) { // No need for redundant Phis
// If we are in lazy phi mode we need to a lookup
// by name as it will trigger a phi creation
Var v = var(i);
Node lhs = this.in(this.update(v,null));
Node rhs = that.in(that.update(v,null));
- setDef(i, new PhiNode(v._name, v.type(), r, lhs, rhs).peephole());
+ setDef(i, PhiNode.make(v._name, v.type(), r, lhs, rhs).peephole());
}
}
// Balance arms of an IF. Extra lonely defs are thrown: "if(pred) int x;".
// Forward refs are copied to the other side, "as if" they were there all along.
+ // 'this' scope is the default, 'scope' might have frefs.
public void balanceIf( ScopeNode scope ) {
- for( int i = nIns(); i < scope.nIns(); i++ ) {
+ if( nIns()==scope.nIns() )
+ return; // Fast-path cutout
+ int kidx = enclosingFunction();
+ assert _kinds.at(kidx) == scope._kinds.at(kidx);
+ if( kidx==0 ) kidx = 1;
+ for( int i = _kinds.at(kidx-1)._lexSize; i < scope.nIns(); i++ ) {
Var n = scope.var(i);
+ if( i<nIns() && var(i)==n ) continue;
if( n.isFRef() ) { // RHS has forward refs
- _vars.add(n); // Copy to LHS
- addDef(scope.in(i));
+ _vars.insert(n,i);
+ insertDef(i,scope.in(i));
} else
throw Parser.error("Cannot define a '"+n._name+"' on one arm of an if",n._loc);
}
}
+ // ------------------------------------------------
// peephole the backedge scope into this loop head scope
// We set the second input to the phi from the back edge (i.e. loop body)
public void endLoop(ScopeNode back, ScopeNode exit ) {
@@ -353,34 +415,30 @@ public class ScopeNode extends MemMergeNode {
assert ctrl instanceof LoopNode loop && loop.inProgress();
ctrl.setDef(2,back.ctrl());
- mem()._endLoopMem( this, back.mem(), exit.mem() );
- this ._endLoop ( this, back , exit );
+ this._endLoop( this, back, exit);
back.kill(); // Loop backedge is dead
// Now one-time do a useless-phi removal
- mem()._useless();
- this ._useless();
-
- // The exit mem's lazy default value had been the loop top,
- // now it goes back to predating the loop.
- exit.mem().setDef(1,mem().in(1));
+ this._useless();
}
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
- for( int i=2; i<nIns(); i++ ) {
+ for( int i=1; i<nIns(); i++ ) {
if( var(i)._final ) continue; // Final vars did not get modified in the loop
- if( var(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
- if( back.in(i) != scope ) {
- PhiNode phi = (PhiNode)in(i);
+ Node n = in(i);
+ if( var(i).type().isHighOrConst() ) { // Cannot lift higher than a constant, so no Phi
+ n.subsume(n=ConstantNode.make(var(i).type()).init());
+ } else if( back.in(i) != scope ) {
+ PhiNode phi = (PhiNode)n;
assert phi.region()==scope.ctrl() && phi.in(2)==null;
phi.setDef(2,back.in(i)); // Fill backedge
}
if( exit.in(i) == scope ) // Replace a lazy-phi on the exit path also
- exit.setDef(i,in(i));
+ exit.setDef(i,n);
}
}
-
+ // ------------------------------------------------
// Up-casting: using the results of an If to improve a value.
// E.g. "if( ptr ) ptr.field;" is legal because ptr is known not-null.
public void addGuards( Node ctrl, Node pred, boolean invert ) {
@@ -392,33 +450,30 @@ public class ScopeNode extends MemMergeNode {
// Invert the If conditional
if( invert )
pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
+ Node zeroPred = pred instanceof NotNode not ? not.in(1) : null;
// This is a zero/null test.
// Compute the positive test type.
- Type tnz = pred._type.nonZero();
- if( tnz!=null )
- _addGuard(tnz,ctrl,pred);
+ _addGuard(true,ctrl,pred);
// Compute the negative test type.
- if( pred instanceof NotNode not ) {
- Node npred = not.in(1);
- Type tzero = npred._type.makeZero();
- _addGuard(tzero,ctrl,npred);
- }
+ if( zeroPred != null )
+ _addGuard(false,ctrl,zeroPred);
}
- private void _addGuard(Type guard, Node ctrl, Node pred) {
- Type tcast = guard.join(pred._type);
- if( tcast != pred._type && !tcast.isHigh() ) {
- Node cast = new CastNode(tcast,ctrl,pred.keep()).peephole().keep();
+ private void _addGuard(boolean nonZero, Node ctrl, Node pred) {
+ Node guard = new GuardNode(nonZero,ctrl,pred).peephole();
+ if( guard != pred ) {
+ pred.keep();
+ guard.keep();
_guards.add(pred);
- _guards.add(cast);
- replace(pred,cast);
+ _guards.add(guard);
+ replace(pred,guard);
}
}
// Remove matching pred/cast pairs from this guarded region.
- public ScopeNode removeGuards( Node ctrl ) {
+ public void removeGuards( Node ctrl ) {
assert ctrl instanceof CFGNode;
// 0,1 or 2 guards
while( true ) {
@@ -428,7 +483,6 @@ public class ScopeNode extends MemMergeNode {
g .unkill(); // Pop/kill cast
_guards.pop().unkill(); // Pop/kill pred
}
- return this;
}
// If we find a guarded instance of pred, replace with the upcasted version
@@ -465,4 +519,44 @@ public class ScopeNode extends MemMergeNode {
setDef(i,cast);
}
+ // ------------------------------------------------------------------------
+ // Insert a top-level public external symbol, "as-if" some #include/import
+ // was executed at the top of tile.
+ public Var insertExtern( ExternNode ext ) {
+ // Size of top-level scope
+ int lexSize = _kinds.at(0)._lexSize;
+ Var var = new Var(lexSize,ext._extern,ext._con,true,null);
+ insert(var,ext,0);
+ return var;
+ }
+
+ // ------------------------------------------------------------------------
+ // Insert a Var with Node n in the lex scope just prior to kidx
+ private void insert( Var var, Node n, int kidx ) {
+ // Insert just prior to scope kidx
+ int nidx = _kinds.at(kidx)._lexSize;
+ _vars.insert(var,nidx);
+ insertDef(nidx,n);
+ // All outer Kinds lexical offsets bump by one
+ for( int i = kidx; i < depth(); i++ )
+ _kinds.at(i)._lexSize++;
+ // All Vars outside the innermost lexical scope bump out one
+ for( int i=nidx+1; i<_vars._len; i++ )
+ var(i)._idx++;
+ }
+
+ // Save the existing Node state to an array
+ public Node[] save() {
+ Node[] olds = new Node[nIns()];
+ for( int i=0; i<nIns(); i++ )
+ olds[i] = in(i)==null ? null : in(i).keep();
+ return olds;
+ }
+
+ // Restore saved Node state back into scope
+ public void restore( Node[] olds ) {
+ assert olds.length<=nIns();
+ for( int i=0; i<olds.length; i++ )
+ setDef(i,olds[i]==null ? null : olds[i].unkeep());
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index 2557bf6b..459b26eb 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -1,13 +1,14 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class ShlNode extends ArithNode {
- public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public ShlNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Shl; }
- @Override public String label() { return "Shl"; }
@Override public String op() { return "<<"; }
@Override public String glabel() { return "&lt;&lt;"; }
@@ -38,7 +39,7 @@ public class ShlNode extends ArithNode {
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
long sum = c.value() << shl.value();
if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
- return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), Parser.con(sum) );
+ return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) );
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index 269f6b7f..5e4870d1 100644
--- a/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class ShrNode extends ArithNode {
- public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public ShrNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Shr; }
- @Override public String label() { return "Shr"; }
@Override public String op() { return ">>>"; }
@Override public String glabel() { return "&gt;&gt;&gt;"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java
index d061cdbd..4b58187f 100644
--- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -1,14 +1,17 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.LRG;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.SB;
import java.util.BitSet;
public abstract class SplitNode extends MachConcreteNode {
+ public final LRG _lrg; // Live range for split
public final String _kind; // Kind of split
- public final byte _round;
- public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; }
+ 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; }
@Override public String op() { return "mov"; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) {
sb.append("mov(");
@@ -16,6 +19,7 @@ public abstract class SplitNode extends MachConcreteNode {
else in(1)._print0(sb,visited);
return sb.append(")");
}
+ public FunNode fun(Encoding enc) { return enc._code._regAlloc.lrg(this)._fun; }
@Override public Type compute() { return in(0)._type; }
@Override public Node idealize() { return null; }
@Override public void asm(CodeGen code, SB sb) {
diff --git a/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
new file mode 100644
index 00000000..d828c5f1
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Start of a single compilation unit, distinct from the outer whole-program Start.
+public class StartCUNode extends StartNode {
+ public final String _fname;
+ public StartCUNode(StartNode start, StopCUNode stop, Type arg, String fname) { super(start,stop,arg); _fname = fname; }
+ public StartCUNode(StartCUNode start) { super(start); _fname = start._fname; }
+ @Override public Tag serialTag() { return Tag.StartCU; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> anodes ) {
+ baos.packed2(types.get(_arg));
+ baos.packed2(strs.get(_fname));
+ }
+ static Node make( BAOS bais, String[] strs, Type[] types ) {
+ return new StartCUNode(null,null,types[bais.packed2()],strs[bais.packed2()]);
+ }
+
+ @Override public CFGNode cfg0() { return (CFGNode)in(0); }
+
+ // IDom is cfg0() and depth is always Start+1 which is 0+1 or 1
+ @Override public int idepth() { return CodeGen.CODE.iDepthAt(1); }
+ @Override public CFGNode idom(Node dep) { return cfg0(); }
+ @Override public void gather( HashMap<String,Integer> strs ) {
+ Serialize.gather(strs,_fname);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/StartNode.java b/src/main/java/com/seaofnodes/simple/node/StartNode.java
index e3806de7..7d2b2773 100644
--- a/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -3,48 +3,78 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.Utils;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
-import static com.seaofnodes.simple.util.Utils.TODO;
/**
- * The Start node represents the start of the function.
+ * The Start node represents the external world calling, be it calling the
+ * entry <clinit> or just some linked caller calling.
* <p>
* Start initially has 1 input (arg) from outside and the initial control.
* In ch10 we also add mem aliases as structs get defined; each field in struct
* adds a distinct alias to Start's tuple.
+ * <p>
+ * By ch25, Start is the external world (or linker) calling with everything
+ * that has escaped. Start is NOT the entry <clinit> itself, which can assume
+ * no memory nor fidxs exist nor escaped. Start gets escaped things from Stop;
+ * together these close the Grand Cycle between the code we know and the code
+ * we don't.
*/
public class StartNode extends LoopNode implements MultiNode {
final Type _arg;
- public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); }
+ public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; }
public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
-
- @Override public String label() { return "Start"; }
+ @Override public Tag serialTag() { return Tag.Start; }
+ public Type arg() { return _arg; }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
return sb.append(label());
}
- @Override public CFGNode cfg0() { return null; }
+ @Override public CFGNode cfg0() { return null; }
// Get the one control following; error to call with more than one e.g. an
- // IfNode or other multi-way branch. For Start, its "main"
+ // IfNode or other multi-way branch. For Start, it's the class init for
+ // this compilation unit.
@Override public CFGNode uctrl() {
- // Find "main", it's the start.
+ // Find module.<clinit>, it's the start.
CFGNode C = null;
for( Node use : _outputs )
- if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) )
+ if( use instanceof FunNode fun && fun.isModInit() )
{ assert C==null; C = fun; }
+ if( C==null )
+ for( FunNode fun : CodeGen.CODE._linker )
+ if( fun!=null && !fun.isDead() && fun.isModInit() )
+ { assert C==null; C = fun; }
return C;
}
+ boolean escapedFIDX(int fidx) {
+ // Public class fields are callable by the unknown external world
+ // regardless of the current SCCP approximation of Start memory.
+ if( CodeGen.CODE.publicFIDX(fidx) ) return true;
+ if( !(_type instanceof TypeTuple tt) )
+ return !_type.isHigh(); // TOP never escapes, BOTTOM always escapes
+ TypeMem tmem = (TypeMem)tt._types[1];
+ if( tmem == TypeMem.BOT ) return false;
+ if( tmem == TypeMem.TOP ) return false;
+ if( XInt.isHigh(tmem._escFs) )
+ return false;
+ return XInt.bit(tmem._escFs,fidx);
+ }
@Override public TypeTuple compute() {
- return TypeTuple.make(Type.CONTROL,TypeMem.TOP,_arg);
+ StopNode stop = (StopNode)in(1);
+ TypeMem tmem = stop._type instanceof TypeTuple tt
+ ? tt.mem()
+ : stop._type.isHigh() ? TypeMem.START : TypeMem.BOT;
+ assert tmem._alias==1 && !tmem._one;
+ return TypeTuple.make(Type.CONTROL,tmem,_arg);
}
@Override public Node idealize() { return null; }
@@ -53,4 +83,5 @@ public class StartNode extends LoopNode implements MultiNode {
@Override public int idepth() { return CodeGen.CODE.iDepthAt(0); }
@Override public CFGNode idom(Node dep) { return null; }
+ @Override public boolean eq( Node n ) { return true; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
new file mode 100644
index 00000000..ab0f5da6
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
@@ -0,0 +1,122 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// Stop of a single compilation unit, distinct from the outer whole-program Stop.
+public class StopCUNode extends StopNode {
+ public StopCUNode() { super(); }
+ public StopCUNode(StopNode stop) { super(stop); }
+ @Override public Tag serialTag() { return Tag.StopCU; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
+ static Node make( BAOS bais ) {
+ StopCUNode stop = new StopCUNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
+ }
+
+ public StartCUNode start() {
+ for( Node use : _outputs )
+ if( use instanceof StartCUNode start )
+ return start;
+ return null;
+ }
+
+ @Override
+ public TypeTuple compute() {
+ // During Parsing, new Stops and Returns are being added. This Stop
+ // cannot know if more are coming, so must assume the worst.
+ if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
+ return TypeTuple.STATE;
+
+ StartCUNode start = start();
+ // Memory start from all returns
+ TypeMem xmem = TypeMem.START;
+ // Escape summaries are monotone discovery facts. In Opto, a Return
+ // can widen from a precise set to FULL; the public-only normalization
+ // below must not forget private bits already discovered precisely from
+ // that Return on an earlier SCCP step.
+ if( CodeGen.CODE._phase == CodeGen.Phase.Opto &&
+ _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem )
+ xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs),
+ XInt.meet(xmem._escAs,omem._escAs));
+
+ for( Node def : _inputs ) {
+ ReturnNode ret = (ReturnNode)def;
+ if( ret._type == Type.TOP ) continue;
+ if( !(ret._type instanceof TypeTuple tret) )
+ return TypeTuple.STATE; // Must be Type.BOTTOM
+
+ FunNode fun = ret.fun();
+ boolean noUserCtor = fun.isInstance() &&
+ !CodeGen.hasUserConstructor(((TypeMemPtr)fun.sig().arg(0))._obj);
+ // Instance <init> is an allocation helper, never an externally
+ // callable constructor when a user constructor exists. Otherwise
+ // the same no-arg <init> is the public constructor.
+ boolean pub = fun.isPublic() && (fun.isClz() || noUserCtor);
+ TypeFunPtr sig = fun.sig();
+ // If public init, the function escapes and the related class escapes
+ if( pub ) {
+ xmem = xmem.escapes(sig);
+ // Classes also escape their public clazz pointer, which then
+ // escapes all their public fields. Private fields do not
+ // escape, and are only available from the local class code.
+ if( fun.isClz() )
+ xmem = xmem.escapesAliases(sig._sig[0]);
+ }
+
+ // The instance <init> Start edge supplies conservative pre-Opto
+ // inputs, but its private-memory result is consumed only by the
+ // allocation sequence and is never an externally visible return.
+ if( fun.isInstance() && !noUserCtor ) continue;
+
+ // Does this function escape? If not, then no need to MEET
+ // here as its effects are not visible to the outside world.
+ addDepForwards(start); // Check again if function escapes
+ if( pub || start.escapedFIDX(sig.fidx()) ) {
+ if( tret.ctl().isHigh() ) continue; // Return not reachable
+ // Capture (precisely?) all escaping pointer aliases and fidxs.
+ // Escaped fidxs means the linked world can call that function;
+ // escaped aliases means the linked world can R/W those aliases.
+ xmem = xmem.escapes(tret.ret());
+ xmem = publicBottom(xmem);
+ xmem = (TypeMem)xmem.meet(publicBottom(tret.mem(),xmem));
+ }
+ }
+ assert xmem._alias==1 && !xmem._one;
+ return TypeTuple.make(Type.CONTROL,xmem,Type.BOTTOM);
+ }
+
+ // FULL escape sets arise from global BOTTOM. Across a compilation-unit
+ // boundary global BOTTOM exposes every public field, while retaining any
+ // private escapes already discovered precisely.
+ private TypeMem publicBottom(TypeMem mem) { return publicBottom(mem,mem); }
+ private TypeMem publicBottom(TypeMem mem, TypeMem prior) {
+ // The pessimistic Iter pass naturally lifts transient FULL inputs to
+ // precise finite sets. Replacing FULL early there would let a later
+ // input refinement add bits, reversing Iter's monotonic direction.
+ // This normalization is needed only for Opto's optimistic grand cycle.
+ if( CodeGen.CODE._phase != CodeGen.Phase.Opto ) return mem;
+ int[] escFs = XInt.isHigh(mem._escFs)
+ ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
+ : mem._escFs;
+ int[] escAs = XInt.isHigh(mem._escAs)
+ ? XInt.meet(prior._escAs,CodeGen.CODE.publicAliases())
+ : mem._escAs;
+ return escFs==mem._escFs && escAs==mem._escAs ? mem : mem.makeFrom(escFs,escAs);
+ }
+
+ @Override
+ public Node idealize() {
+ int len = nIns();
+ for( int i=0; i<nIns(); i++ )
+ if( in(i) instanceof ReturnNode ret && addDep(ret.fun()).isDead() )
+ delDef(i--);
+ if( len != nIns() ) return this;
+ return null;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/StopNode.java b/src/main/java/com/seaofnodes/simple/node/StopNode.java
index 4b03bf4e..45f5519e 100644
--- a/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -1,34 +1,45 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+// A collection of function ReturnNodes for a compilation unit.
public class StopNode extends CFGNode {
- public final String _src;
-
- public StopNode(String src) {
+ public StopNode() {
super();
- _src = src;
_type = compute();
}
- public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; }
-
- @Override
- public String label() {
- return "Stop";
+ 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()); }
+ static Node make( BAOS bais ) {
+ StopNode stop = new StopNode();
+ stop.setDefX(bais.packed1()-1,null);
+ return stop;
}
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
// For the sake of many old tests, and single value prints as "return val"
- ReturnNode ret1 = ret();
+ ReturnNode ret1 = null;
+ for( Node n : _inputs ) {
+ if( n instanceof ReturnNode ret ) {
+ if( ret1 != null ) { ret1=null; break; }
+ ret1 = ret;
+ }
+ }
if( ret1!=null ) return ret1._print0(sb,visited);
+
sb.append("Stop[ ");
- for( Node ret : _inputs )
- if( ret!=null ) {
- String name = ((ReturnNode)ret).fun()._name;
+ for( Node n : _inputs )
+ if( n instanceof ReturnNode ret ) {
+ String name = ret.fun()._name;
if( name== null || !name.startsWith("sys.") )
ret._print0(sb, visited).append(" ");
}
@@ -41,25 +52,36 @@ public class StopNode extends CFGNode {
// If a single Return, return it.
// Otherwise, null because ambiguous.
public ReturnNode ret() {
- Node ret1 = this;
- for( Node ret : _inputs ) {
- String name = ((ReturnNode)ret).fun()._name;
- if( name==null || !name.startsWith("sys.") )
- ret1 = ret1==this ? ((ReturnNode)ret) : null;
+ ReturnNode ret1 = null;
+ for( Node n : _inputs ) {
+ // TODO: remove isPublic
+ if( n instanceof ReturnNode ret && ret.fun().isPublic() ) {
+ if( ret1 != null ) return null; // Ambiguous
+ ret1 = ret;
+ }
}
- return ret1==this ? null : (ReturnNode)ret1;
+ return ret1;
}
@Override
- public Type compute() {
- return Type.BOTTOM;
+ public TypeTuple compute() {
+ // During Parsing, new Stops and Returns are being added. This Stop
+ // cannot know if more are coming, so must assume the worst.
+ if( CodeGen.CODE._phase ==null || CodeGen.CODE._phase == CodeGen.Phase.Parse )
+ return TypeTuple.STATE;
+
+ // Just meet-over-inputs.
+ TypeTuple tt = TypeTuple.STOP_HIGH;
+ for( Node def : _inputs )
+ tt = (TypeTuple)tt.meet(def._type);
+ assert tt.mem()._alias==1 && !tt.mem()._one;
+ return tt;
}
- @Override
- public Node idealize() {
+ @Override public Node idealize() {
int len = nIns();
for( int i=0; i<nIns(); i++ )
- if( addDep(((ReturnNode)in(i)).fun()).isDead() )
+ if( in(i).nIns()==0 )
delDef(i--);
if( len != nIns() ) return this;
return null;
@@ -69,7 +91,10 @@ public class StopNode extends CFGNode {
if( _idepth!=0 ) return _idepth;
int d=0;
for( Node ret : _inputs )
- d = Math.max(d,((ReturnNode)ret).idepth()+1);
+ // All public results are hooked on the Stop, including data
+ // values, so must check CFG
+ if( ret instanceof CFGNode cfg )
+ d = Math.max(d,cfg.idepth()+1);
return _idepth=d;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 8e33512a..c0006511 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -1,8 +1,14 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.*;
+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 java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Store represents setting a value to a memory based object, in chapter 10
@@ -10,7 +16,10 @@ import java.util.BitSet;
*/
public class StoreNode extends MemOpNode {
- private final boolean _init; // Initializing writes are allowed to write null
+ final boolean _init; // Initializing writes are allowed to write null
+ // Semantic store width in bytes. Zero is the sole undecided state;
+ // otherwise hardware and ideal Stores are the 1/2/4/8-byte variants.
+ private byte _size;
/**
* @param name The struct field we are assigning to
@@ -19,9 +28,25 @@ public class StoreNode extends MemOpNode {
* @param off The offset inside the struct base
* @param value Value to be stored
*/
- public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off, Node value, boolean init) {
- super(loc, name, alias, false, glb, mem, ptr, off, value);
+ public StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
+ this(loc,name,alias,glb,ctrl,mem,ptr,off,value,init,(byte)0);
+ }
+ private StoreNode(Parser.Lexer loc, String name, int alias, Type glb, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
+ super(loc, name, alias, false, glb, ctrl, mem, ptr, off, value);
_init = init;
+ _size = size;
+ }
+ StoreNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) {
+ super(bais,strs,types,fileAliases,aliases,false);
+ addDef(null);
+ _init = bais.read() != 0;
+ _size = (byte)bais.read();
+ }
+ @Override public Tag serialTag() { return Tag.Store; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ super.packed(baos,strs,types, anodes );
+ baos.write(_init ? 1 : 0);
+ baos.write(_size);
}
// GraphVis DOT code and debugger labels
@@ -30,7 +55,10 @@ public class StoreNode extends MemOpNode {
@Override public String glabel() { return "." +_name+"="; }
@Override public boolean isMem() { return true; }
+ public Node nnptr() { return ptr() instanceof GuardNode cast && cast._nonZero ? cast.in(1) : ptr(); }
public Node val() { return in(4); }
+ public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; }
+ @Override public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
@@ -39,28 +67,108 @@ public class StoreNode extends MemOpNode {
@Override
public Type compute() {
- Type val = val()._type;
- Type mem0 = mem()._type;
- if( mem0 == Type.TOP ) return TypeMem.TOP;
- TypeMem mem = (TypeMem)mem0; // Invariant
- if( mem == TypeMem.TOP ) return TypeMem.TOP;
- Type t = Type.BOTTOM; // No idea on field contents
- // Same alias, lift val to the declared type and then meet into other fields
- if( mem._alias == _alias ) {
- assert !_declaredType.isFRef();
- // Sharpen memory value; required for narrowing stores where the parser inserts
- // zero/sign masking and somebody reads the TypeMem type.
- val = val.join(_declaredType);
- t = val.meet(mem._t);
- }
- return TypeMem.make(_alias,t);
+ Type val = val()._type;
+ Type mem0 = aliasMem()._type;
+ Type ptr0 = ptr()._type;
+ // Validate argument types
+ if( ptr0.isHigh() )
+ return TypeMem.TOP;
+ if( !(mem0 instanceof TypeMem mem) ||
+ !(ptr0 instanceof TypeMemPtr ptr) )
+ return mem0.isHigh() || ptr0==Type.NIL ? TypeMem.TOP : TypeMem.BOT;
+
+ // Sharpen memory value; required for narrowing stores where the parser
+ // inserts zero/sign masking and somebody reads the TypeMem type.
+ Type decl = declaredType();
+ if( decl != Type.BOTTOM && decl != Type.TOP )
+ val = val.join(decl);
+ //if( err()!=null )
+ // val = Type.BOTTOM;
+ // 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.
+ if( !_name.equals("[]") && mem._one )
+ // Just track the stored value
+ return TypeMem.make(_alias,val,true,false,_init,null,null).escapesFrom(val);
+
+ // Normal aliasing Store.
+ assert mem._alias==1 || mem._alias==_alias; // Perfect aliasing
+
+ // Single alias store. If memory is fat, attempt to get a narrow slice.
+ Type tfld = mem._alias==1 && mem._t instanceof TypeStruct ts
+ ? ts._fields[ts.findAlias(_alias)]._t
+ : mem._t;
+
+ // Same alias, meet into other fields.
+ 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);
}
@Override
public Node idealize() {
+ assert !(mem() instanceof CheckCastNode);
- if( mem() instanceof CastNode cast ) {
- setDef(1,cast.in(1));
+ // Freeze the semantic Store variant as soon as the pointer exposes a
+ // canonical field declaration. Waiting until after structural memory
+ // peeps can lose the declaration to a flow-sensitive constant field
+ // state. Imported Stores already carry a serialized non-zero width.
+ Type storage = storageType();
+ if( _size==0 && storage!=Type.BOTTOM && storage!=Type.TOP ) {
+ unlock(); // Width participates in GVN identity.
+ _size = storeSize(storage);
+ return init();
+ }
+
+ // A precise Store can confirm its alias from a precise memory input.
+ // Do not sharpen an alias-#1 (whole-memory) Store in place: users of
+ // that Store already treat it as the complete memory state. The
+ // pointer/field path below replaces it with a precise Store wrapped in
+ // a MemMerge, preserving any other precise aliases in its input.
+ if( mem()._type instanceof TypeMem mem ) {
+ assert mem._alias > 0;
+ if( _alias != 1 && mem._alias != 1 )
+ assert _alias == mem._alias;
+ }
+
+ // Forward-ref loads eventually sharpen to a declared type
+ Field fld;
+ if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null ) {
+ // Expand Store(bulkMem,alias#1) into MemMerge(bulkMem,#N:Store(bulkMem,alias#N)).
+
+ // Normally I'd like to just drop in this xform and move on, but
+ // users of the bulk Store memory might nontheless depend on the
+ // sharp memory type (e.g. I64) from the stores memory. Using the
+ // MemMerge means using the bulk memory which e.g. might be BOT.
+ // Check for store users with the same alias, and use the new store
+ // directly, preserving the sharper graph.
+ Type decl = storageType();
+ byte size = _size==0 && decl!=Type.BOTTOM && decl!=Type.TOP ? storeSize(decl) : _size;
+ Node st = new StoreNode(_loc,_name,fld._alias,fld._t,in(0),mem(),ptr(),off(),val(),_init,size).peephole();
+ for( int i=0; i<nOuts(); i++ ) {
+ Node use = out(i);
+ if( useAlias(use)==fld._alias ) {
+ int idx = use._inputs.find(this);
+ assert idx != -1;
+ use.setDef(idx,st);
+ i--; // setDef removed use from this Store's outputs
+ }
+ }
+ MemMergeNode mmm = mem() instanceof BulkMemPhiNode bulk
+ ? bulk.aggregate(mem(),fld._alias,st)
+ : new MemMergeNode(false,null,mem());
+ if( !(mem() instanceof BulkMemPhiNode) )
+ mmm.alias(fld._alias,st);
+ return mmm.init();
+ }
+ // Expose the same effective memory input already observed by compute.
+ Node aliasMem = aliasMem();
+ if( aliasMem != mem() ) {
+ setDef(1,aliasMem);
+ CodeGen.CODE.add(aliasMem);
return this;
}
@@ -73,20 +181,26 @@ public class StoreNode extends MemOpNode {
// Must have exactly one use of "this" or you get weird
// non-serializable memory effects in the worse case.
checkOnlyUse(st) ) {
- assert _name.equals(st._name); // Equiv class aliasing is perfect
+ assert _name==st._name; // Equiv class aliasing is perfect
setDef(1,st.mem());
return this;
}
// Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
- if( mem() instanceof MemMergeNode mem ) {
+ if( _alias != 1 && mem() instanceof MemMergeNode mem ) {
setDef(1,mem.alias(_alias));
return this;
}
+ // Resolve the semantic Store variant exactly once, after structural
+ // memory/alias peeps. This avoids publishing a half-sharpened Store
+ // while BulkMemPhi is constructing precise slices.
+ if( storage != Type.BOTTOM && storage != Type.TOP )
+ assert _size==storeSize(storage) : "Store width changed for '"+_name+"': "+_size+" from "+storage;
+
// Value is automatically truncated by narrow store
- if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
- int log = _declaredType.log_size();
+ if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ int log = log_size();
if( log<3 ) { // And-mask vs narrow store
long mask = ((TypeInteger)and.in(2)._type).value();
long bits = (1L<<(8<<log))-1;
@@ -98,7 +212,7 @@ public class StoreNode extends MemOpNode {
}
// Store will chop high order bits off; math to change those bits can be dropped.
- if( val() instanceof SarNode shr &&
+ if( _size!=0 && val() instanceof SarNode shr &&
shr.in(1) instanceof ShlNode shl &&
shr.in(2)._type.isConstant() &&
shl.in(2)._type.isConstant() ) {
@@ -114,9 +228,68 @@ public class StoreNode extends MemOpNode {
return this;
}
}
+
return null;
}
+ private static byte storeSize(Type decl) {
+ // `decl` is the target field declaration, not the stored value. In
+ // particular a constant zero stored into u32 is still a 4-byte Store;
+ // taking the constant's GLB here would incorrectly select i64.
+ byte size = (byte)(1 << decl.log_size());
+ assert size==1 || size==2 || size==4 || size==8;
+ return size;
+ }
+
+ // Flow-sensitive pointer types can carry a current field value (e.g. the
+ // constant zero in a u32 field). Storage width comes from the canonical
+ // struct declaration. If the pointer/definition is not available yet,
+ // leave the Store undecided instead of consulting the value or `_con`.
+ private Type storageType() {
+ // Freshly parsed field stores retain the declaring structure on their
+ // symbolic offset even after the pointer's flow type sharpens.
+ if( off() instanceof ConFldOffNode coff ) {
+ Field fld = coff._ts.field(_name);
+ if( fld != null && fld._t != null ) return fld._t;
+ }
+ if( !(ptr()._type instanceof TypeMemPtr tmp) ) return Type.BOTTOM;
+ Type base = Parser.TYPES.get(tmp._obj._name);
+ TypeStruct obj = base instanceof TypeStruct ts ? ts : tmp._obj;
+ Field fld = obj.field(_name);
+ // Imported optimized structs can expose the current constant field
+ // value instead of its declaration. A constant has no storage width;
+ // an imported Store already carries its serialized, frozen `_size`.
+ return fld == null || fld._t == null || fld._t.isConstant() ? Type.BOTTOM : fld._t;
+ }
+
+ // Alias required by a direct consumer of this Store's memory result.
+ // Keep in sync with BulkMemPhiNode.outputAlias.
+ private int useAlias(Node use) {
+ return switch( use ) {
+ case MemOpNode mem -> mem._alias;
+ case MemPhiNode phi -> phi._alias;
+ case EscapeNode esc -> esc.pub()==this ? esc.fld()._alias : 0;
+ default -> 0;
+ };
+ }
+
+ // Select a precise alias from a whole-memory partition. Alias #1 is the
+ // bulk remainder itself.
+ private static Node memSlice( Node mem, int alias ) {
+ return alias != 1 && mem instanceof MemMergeNode merge
+ ? merge.alias(alias)
+ : mem;
+ }
+
+ // Semantic memory input for this Store. Compute defines behavior from
+ // this view; ideal exposes the same edge in the graph.
+ private Node aliasMem() {
+ Node mem = mem();
+ if( _alias != 1 && mem instanceof MemMergeNode merge )
+ mem = merge.alias(_alias);
+ return mem;
+ }
+
// Check that "mem" has no uses except "this"
private boolean checkOnlyUse(Node mem) {
if( mem.nOuts()==1 ) return true;
@@ -132,11 +305,17 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
+ if( _size==0 && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Opto.ordinal() )
+ throw Utils.TODO("Failed to decide Store size");
if( ptr()._type == Type.TOP )
return null; // This means we have an error input, report elsewhere
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- if( tmp._obj.field(_name)._final && !_init )
+ Field f = tmp._obj.field(_name);
+ if( f!=null && f._final && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
return null;
}
+
+ @Override public boolean eq(Node n) { return _size==((StoreNode)n)._size && super.eq(n); }
+ @Override int hash() { return super.hash() ^ _size; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StructNode.java b/src/main/java/com/seaofnodes/simple/node/StructNode.java
index b5b59b81..4dcc695f 100644
--- a/src/main/java/com/seaofnodes/simple/node/StructNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -1,8 +1,11 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.BitSet;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Build a compound object
@@ -11,13 +14,23 @@ public class StructNode extends Node {
public final TypeStruct _ts;
public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; }
-
- @Override public String label() { return _ts==null ? "STRUCT?" : _ts.str(); }
+ @Override public Tag serialTag() { return Tag.Struct; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
+ //baos.packed1(nIns());
+ //baos.packed2(_label==null ? 0 : strs.get(_label));
+ //baos.packed2(types.get(_type)); // Write _type not _minType, which can be higher
+ throw Utils.TODO();
+ }
+ static Node make( BAOS bais, Type[] types) {
+ //Node[] ins = new Node[bais.packed1()];
+ //return new StructNode(types[bais.packed2()], ins);
+ throw Utils.TODO();
+ }
@Override
public StringBuilder _print1(StringBuilder sb, BitSet visited) {
if( _ts==null ) return sb.append("STRUCT?");
- sb.append(_ts._name).append(" {");
+ sb.append(_ts._name).append(" { ");
for( int i=0; i<nIns(); i++ ) {
sb.append(_ts._fields[i]._fname).append(":");
sb.append(in(i)==null ? Type.BOTTOM : in(i)._type);
diff --git a/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/src/main/java/com/seaofnodes/simple/node/SubFNode.java
deleted file mode 100644
index 3fc4f7ca..00000000
--- a/src/main/java/com/seaofnodes/simple/node/SubFNode.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-import java.util.BitSet;
-
-public class SubFNode extends ArithFNode {
- public SubFNode(Node lhs, Node rhs) { super(lhs, rhs); }
-
- @Override public String label() { return "SubF"; }
-
- @Override public String glabel() { return "-"; }
-
- @Override double doOp( double x, double y ) { return x-y; }
-
- @Override
- public Node idealize() {
- // Sub of 0.
- Type t2 = in(2)._type;
- if ( t2.isConstant() && t2 instanceof TypeFloat i && i.value()==0 )
- return in(1);
-
- return null;
- }
- @Override Node copy(Node lhs, Node rhs) { return new SubFNode(lhs,rhs); }
-}
diff --git a/src/main/java/com/seaofnodes/simple/node/SubNode.java b/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 8e4f6f79..aba1df90 100644
--- a/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -6,12 +6,15 @@ import com.seaofnodes.simple.type.TypeInteger;
import java.util.BitSet;
public class SubNode extends ArithNode {
+ @Override boolean allowFloat() { return true; }
public SubNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
+ public SubNode(Node lhs, Node rhs, byte mode) { super(null, lhs, rhs,mode); }
+ @Override public Tag serialTag() { return Tag.Sub; }
- @Override public String label() { return "Sub"; }
@Override public String op() { return "-"; }
- @Override long doOp( long x, long y ) { return x - y; }
+ @Override long doOp( long x, long y ) { return x - y; }
+ @Override double doOp(double x, double y ) { return x - y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
// Sub of same is 0
if( in(1)==in(2) )
@@ -38,6 +41,5 @@ public class SubNode extends ArithNode {
return super.idealize();
}
- @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs); }
- @Override Node copyF() { return new SubFNode(null,null); }
+ @Override Node copy(Node lhs, Node rhs) { return new SubNode(lhs,rhs,_mode); }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
index e02183a0..2d8cc788 100644
--- a/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -8,8 +8,7 @@ import java.util.BitSet;
public class ToFloatNode extends Node {
public ToFloatNode(Node lhs) { super(null, lhs); }
-
- @Override public String label() { return "ToFloat"; }
+ @Override public Tag serialTag() { return Tag.ToFloat; }
@Override public String glabel() { return "(flt)"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/src/main/java/com/seaofnodes/simple/node/TypeNode.java
new file mode 100644
index 00000000..3ab2d6f9
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/TypeNode.java
@@ -0,0 +1,33 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashMap;
+
+// Node carries an extra internal type
+public abstract class TypeNode extends Node {
+ public Type _con;
+ public TypeNode(Type t, Node... nodes) { super(nodes); assert t!=null; _con = t; }
+ public TypeNode(TypeNode tn) { super(tn); _con = tn._con; }
+ // Copy edges from ideal, but 'this' is a Type-Mach-Node (e.g. MemOpX86) and needs a _con
+ public TypeNode(Node ideal, Type con) { super(ideal); _con = con; }
+
+ // Upgrade the internal type
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ return liftType(_con.upgradeType(TYPES));
+ }
+
+ // Lift type
+ public boolean liftType( Type t ) {
+ if( t == _con ) return false;
+ unlock(); // Unlock before changing _con
+ _con = t;
+ return true;
+ }
+
+ @Override public boolean eq(Node n) { return _con==((TypeNode)n)._con; }
+ @Override int hash() { return _con.hashCode(); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
index 8c620a04..a8c06b37 100644
--- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -7,7 +7,7 @@ import java.util.BitSet;
public class XCtrlNode extends CFGNode {
public XCtrlNode() { super(new Node[]{CodeGen.CODE._start}); }
- @Override public String label() { return "Xctrl"; }
+ @Override public Tag serialTag() { return Tag.XCtrl; }
@Override public StringBuilder _print1(StringBuilder sb, BitSet visited) { return sb.append("Xctrl"); }
@Override public boolean isConst() { return true; }
@Override public Type compute() { return Type.XCONTROL; }
diff --git a/src/main/java/com/seaofnodes/simple/node/XorNode.java b/src/main/java/com/seaofnodes/simple/node/XorNode.java
index fdb4863e..95d4461e 100644
--- a/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -5,9 +5,9 @@ import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
public class XorNode extends ArithNode {
- public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs); }
+ public XorNode(Parser.Lexer loc, Node lhs, Node rhs) { super(loc, lhs, rhs, (byte)1); }
+ @Override public Tag serialTag() { return Tag.Xor; }
- @Override public String label() { return "Xor"; }
@Override public String op() { return "^"; }
@Override public String glabel() { return "^"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
index b9ede544..5cf8a89c 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -8,9 +8,11 @@ import com.seaofnodes.simple.util.Utils;
// Jump on flags, uses flags
public class BranchARM extends IfNode implements MachNode, RIPRelSize {
String _bop;
- BranchARM(IfNode iff, String bop ) {
+ final boolean _fp;
+ BranchARM(IfNode iff, String bop, boolean fp ) {
super(iff);
_bop = bop;
+ _fp = fp;
}
@Override public String op() { return "j"+_bop; }
@Override public String label() { return op(); }
@@ -35,7 +37,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize {
// by comparison (or sub). No need for regs because it uses flags
if( in(1)!=null ) {
// B.cond
- enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop)) );
+ enc.add4( arm.b_cond(arm.OP_BRANCH, 0, arm.make_condition(_bop, _fp)) );
} else {
if( _bop=="!=" ) return; // Inverted, no code
enc.add4(arm.b(arm.OP_UJMP, 0));
@@ -55,7 +57,7 @@ public class BranchARM extends IfNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
assert !( in(1)==null && _bop=="!=" ); // Inverted never-node, no code no patch
if( opLen==4 ) {
- enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop)));
+ enc.patch4(opStart,arm.b_cond(arm.OP_BRANCH, delta, arm.make_condition(_bop, _fp)));
} else {
throw Utils.TODO();
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
index 427cf2e2..8f070525 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -13,8 +13,7 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize {
_inputs.pop(); // Pop constant target
assert tfp.isConstant();
_tfp = tfp;
- FunNode fun = CodeGen.CODE.link(tfp);
- _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ _name = CodeGen.CODE.funcName(tfp.fidx());
}
@Override public String op() { return "call"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
index b47b65ad..42553868 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
@@ -3,21 +3,22 @@ package com.seaofnodes.simple.node.cpus.arm;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.ConstantNode;
import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.type.TypeFloat;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
//FMOV (scalar, immediate)
//Floating-point move immediate.
-public class FloatARM extends ConstantNode implements MachNode, RIPRelSize {
- FloatARM(ConstantNode con) { super(con); }
+public class FltARM extends ConstantNode implements MachNode, RIPRelSize {
+ final String _ext;
+ FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.DMASK; }
@Override public boolean isClone() { return true; }
- @Override public FloatARM copy() { return new FloatARM(this); }
+ @Override public FltARM copy() { return new FltARM(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
enc.largeConstant(this,_con,0,-1/*TODO: ARM-style ELF patching*/);
short dst = (short)(enc.reg(this) - arm.D_OFFSET);
enc.add4(arm.load_pc(arm.OPF_ARM, 0, dst));
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
index ee74b95e..dbed9487 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -6,19 +6,22 @@ import com.seaofnodes.simple.node.MachNode;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Integer constants
public class IntARM extends ConstantNode implements MachNode {
- IntARM( ConstantNode con ) { super(con); }
+ final String _ext;
+ IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldi"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public IntARM copy() { return new IntARM(this); }
+ @Override public IntARM copy() { return new IntARM(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
short self = enc.reg(this);
long x = _con==Type.NIL ? 0 : ((TypeInteger)_con).value();
int nb0 = 0;
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index 1e5d2094..f8c9bfef 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
@@ -21,16 +21,16 @@ public class LoadARM extends MemOpARM {
private static final int[] OP_LOADS = new int[]{ arm.OP_LOAD_IMM_8, arm.OP_LOAD_IMM_16, arm.OP_LOAD_IMM_32, arm.OP_LOAD_IMM_64, };
private int imm_op() {
- return _declaredType == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32
- : _declaredType == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64
- : OP_LOADS[_declaredType.log_size()];
+ return _con == TypeFloat.F32 ? arm.OPF_LOAD_IMM_32
+ : _con == TypeFloat.F64 ? arm.OPF_LOAD_IMM_64
+ : OP_LOADS[_con.log_size()];
}
private static final int[] OP_LOAD_RS = new int[]{ arm.OP_LOAD_R_8 , arm.OP_LOAD_R_16 , arm.OP_LOAD_R_32 , arm.OP_LOAD_R_64, };
// ldr(immediate - unsigned offset) | ldr(register)
@Override public void encoding( Encoding enc ) {
- ldst_encode(enc, imm_op(), OP_LOAD_RS[_declaredType.log_size()], this, size());
+ ldst_encode(enc, imm_op(), OP_LOAD_RS[_con.log_size()], this, size());
}
@Override public void asm(CodeGen code, SB sb) {
sb.p(code.reg(this)).p(",");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
index e877931d..08bdf974 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
@@ -11,7 +11,8 @@ import java.util.BitSet;
public abstract class MemOpARM extends MemOpNode implements MachNode {
final int _off; // Limit 9 bits sized, or (13 bits<<logsize) unsigned
final int _imm; // Limit ? bits
- final char _sz = (char)('0'+(1<<_declaredType.log_size()));
+ final int _bytes;
+ final char _sz;
MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) {
super(mop,mop);
assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode);
@@ -20,6 +21,8 @@ public abstract class MemOpARM extends MemOpNode implements MachNode {
_inputs.setX(3, idx);
_off = off;
_imm = imm;
+ _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+_bytes);
}
// Store-based flavors have a value edge
@@ -36,7 +39,7 @@ public abstract class MemOpARM extends MemOpNode implements MachNode {
@Override public Type compute() { throw Utils.TODO(); }
@Override public Node idealize() { throw Utils.TODO(); }
- int size() { return 1<<_declaredType.log_size(); }
+ int size() { return _bytes; }
// Wider mask to store both GPRs and FPRs
@Override public RegMask regmap(int i) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
index c1aad15f..df2a6bc9 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -31,7 +31,8 @@ public class NewARM extends NewNode implements MachNode, RIPRelSize {
}
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("ldi x0=#1\n");
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
sb.p("call #calloc");
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
index e1b3b3b6..c597b9dd 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -8,12 +8,14 @@ import com.seaofnodes.simple.util.SB;
// e.g CSET, stores flag info into GPRS after compare
public class SetARM extends MachConcreteNode implements MachNode {
final String _bop; // One of <,<=,==
- SetARM(Node cmp, String bop) {
+ final boolean _fp; // FP compare (unsigned encoding)
+ SetARM(Node cmp, String bop, boolean fp) {
super(cmp);
_inputs.setLen(1); // Pop the cmp inputs
// Replace with the matched cmp
_inputs.push(cmp);
_bop = bop;
+ _fp = fp;
}
@Override public String op() { return "set"+_bop; }
@Override public RegMask regmap(int i) { assert i==1; return arm.FLAGS_MASK; }
@@ -21,7 +23,7 @@ public class SetARM extends MachConcreteNode implements MachNode {
@Override public void encoding( Encoding enc ) {
// Using RM==RN==31 implies a *zero* value, not R31.
- int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop), 0b011111, enc.reg(this));
+ int body = arm.cset(arm.OP_CSET,0b11111, arm.make_condition(_bop, _fp), 0b011111, enc.reg(this));
enc.add4(body);
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
index 5af45892..be975fb6 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
@@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitARM extends SplitNode {
- SplitARM(String kind, byte round) { super(kind,round,new Node[2]);}
+ SplitARM(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);}
@Override public RegMask regmap(int i) { return arm.SPLIT_MASK; }
@Override public RegMask outregmap() { return arm.SPLIT_MASK; }
@@ -24,7 +24,7 @@ public class SplitARM extends SplitNode {
if(src >= arm.MAX_REG) {
throw Utils.TODO();
}
- int off = enc._fun.computeStackOffset(enc._code,dst);
+ int off = fun(enc).computeStackOffset(enc._code,dst);
if( srcX ) src -= arm.D_OFFSET;
enc.add4(arm.load_str_imm(arm.OP_STORE_IMM_64, off, arm.RSP, src, 8));
return;
@@ -32,7 +32,7 @@ public class SplitARM extends SplitNode {
if(src >= arm.MAX_REG) {
// Load from SP
- int off = enc._fun.computeStackOffset(enc._code,src);
+ int off = fun(enc).computeStackOffset(enc._code,src);
if( dstX ) dst -= arm.D_OFFSET;
enc.add4(arm.load_str_imm(arm.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
return;
@@ -56,10 +56,4 @@ public class SplitARM extends SplitNode {
enc.add4(arm.f_mov_general(arm.OP_FMOV, 0b01, 0, 0b110, src - arm.D_OFFSET, dst));
}
}
-
- // General form: "mov dst = src"
- @Override public void asm(CodeGen code, SB sb) {
- FunNode fun = code._encoding==null ? null : code._encoding._fun;
- sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
index 91385ac9..b11a2c89 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
@@ -25,15 +25,15 @@ public class StoreARM extends MemOpARM {
private static final int[] OP_STORES = new int[]{ arm.OP_STORE_IMM_8, arm.OP_STORE_IMM_16, arm.OP_STORE_IMM_32, arm.OP_STORE_IMM_64, };
private int imm_op() {
- return _declaredType == TypeFloat.F32 ? arm.OPF_STORE_IMM_32
- : _declaredType == TypeFloat.F64 ? arm.OPF_STORE_IMM_64
- : OP_STORES[_declaredType.log_size()];
+ return val()!=null && val()._type instanceof TypeFloat
+ ? (_bytes==4 ? arm.OPF_STORE_IMM_32 : arm.OPF_STORE_IMM_64)
+ : OP_STORES[Integer.numberOfTrailingZeros(_bytes)];
}
private static final int[] OP_STORE_RS = new int[]{ arm.OP_STORE_R_8, arm.OP_STORE_R_16, arm.OP_STORE_R_32, arm.OP_STORE_R_64, };
@Override public void encoding( Encoding enc ) {
- ldst_encode(enc, imm_op(), OP_STORE_RS[_declaredType.log_size()], val(), size());
+ ldst_encode(enc, imm_op(), OP_STORE_RS[Integer.numberOfTrailingZeros(_bytes)], val(), size());
}
@Override public void asm(CodeGen code, SB sb) {
asm_address(code,sb).p(",");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
index 29d490ef..9d490c8b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -1,28 +1,30 @@
package com.seaofnodes.simple.node.cpus.arm;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
- TFPARM( ConstantNode con ) { super(con); }
+public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize {
+ final String _ext;
+ // Pointer to a Simple function
+ TFPARM( FunPtrNode fptr ) { super(fptr); _ext = null; }
+ // Pointer to an Extern "C" function
+ TFPARM( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
@Override public String op() { return "ldx"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TFPARM copy() { return new TFPARM(this); }
+ @Override public TFPARM copy() { return new TFPARM((TypeFunPtr)_con,_ext); }
@Override public void encoding( Encoding enc ) {
- enc.relo(this);
- short self = enc.reg(this);
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
+ short dst = enc.reg(this);
// adrp x0, 0
- int adrp = arm.adrp(1,0, arm.OP_ADRP, 0,self);
+ enc.add4(arm.adrp(1,0, arm.OP_ADRP, 0,dst));
// add x0, x0, 0
- enc.add4(adrp);
- arm.imm_inst(enc,arm.OPI_ADD,0, 0);
+ arm.imm_inst(enc,arm.OPI_ADD,0, dst);
}
@Override public byte encSize(int delta) {
@@ -33,15 +35,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short rpc = enc.reg(this);
if(opLen == 8 ) {
- // ARM encoding delta is from PC & 0xFFF
- int target = opStart+delta;
- int base = opStart & ~0xFFF;
- delta = target-base;
- int adrp_delta = delta >> 12;
- // patch upper 20 bits via adrp
- enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc));
- // low 12 bits via add
- enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, rpc));
+ arm.patch_adrp_add(enc, opStart, delta, rpc);
} else {
// should not happen as one instruction is 4 byte, and TFP arm encodes 2.
throw Utils.TODO();
@@ -50,7 +44,7 @@ public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
@Override public void asm(CodeGen code, SB sb) {
String reg = code.reg(this);
- _con.print(sb.p(reg).p(" #"));
+ _type.print(sb.p(reg).p(" #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
index cdcaa433..f86d85dc 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
@@ -9,13 +9,15 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
- TMPARM( ConstantNode con ) { super(con); }
+ final String _ext;
+ TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldp"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return arm.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TMPARM copy() { return new TMPARM(this); }
+ @Override public TMPARM copy() { return new TMPARM(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
short dst = enc.reg(this);
// adrp x0, 0
@@ -32,15 +34,7 @@ public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short dst = enc.reg(this);
if(opLen == 8 ) {
- // ARM encoding delta is from PC & 0xFFF
- int target = opStart+delta;
- int base = opStart & ~0xFFF;
- delta = target-base;
- int adrp_delta = delta >> 12;
- // patch upper 20 bits via adrp
- enc.patch4(opStart, arm.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst));
- // low 12 bits via add
- enc.patch4(opStart+4, arm.imm_inst_l(arm.OPI_ADD, delta & 0xfff, dst));
+ arm.patch_adrp_add(enc, opStart, delta, dst);
} else {
throw Utils.TODO();
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index a5dac751..852fbdb1 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -466,9 +466,19 @@ public class arm extends Machine {
// int l
public static int adrp(int op, int imlo,int opcode, int imhi, int rd) {
assert 0 <= rd && rd < 32;
+ assert 0 <= imlo && imlo <= 0x3; // 2 bits
+ assert 0 <= imhi && imhi <= 0x7FFFF; // 19 bits
return (op << 31) | (imlo << 29) |(opcode << 24) | (imhi << 5) | rd;
}
+ public static void patch_adrp_add(Encoding enc, int opStart, int delta, int rd) {
+ int target = opStart + delta;
+ int pageDelta = ((target & ~0xFFF) - (opStart & ~0xFFF)) >> 12;
+ int imm21 = pageDelta & 0x1FFFFF;
+ enc.patch4(opStart , adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd));
+ enc.patch4(opStart+4, imm_inst_l(OPI_ADD, target & 0xFFF, rd));
+ }
+
// [Rptr+Roff]
public static int indr_adr(int opcode, int off, STORE_LOAD_OPTION option, int s, int ptr, int rt) {
assert 0 <= ptr && ptr < 32;
@@ -514,19 +524,31 @@ public class arm extends Machine {
public static void f_cmp(Encoding enc, Node n) {
short reg1 = (short)(enc.reg(n.in(1))-D_OFFSET);
short reg2 = (short)(enc.reg(n.in(2))-D_OFFSET);
- int body = f_cmp(0b00011110, 3, reg1, reg2);
+ int body = f_cmp(0b00011110, 3, reg2, reg1);
enc.add4(body);
}
- public static COND make_condition(String bop) {
- return switch (bop) {
- case "==" -> COND.EQ;
- case "!=" -> COND.NE;
- case "<" -> COND.LT;
- case "<=" -> COND.LE;
- case ">=" -> COND.GE;
- case ">" -> COND.GT;
- default -> throw Utils.TODO();
+ public static COND make_condition(String bop, boolean fp) {
+ // One of {N,Z,C} and !V
+ // OR { !N, !Z, C, V }
+ return fp
+ ? switch (bop) { // FP uses unsigned encodings
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.MI; // N
+ case "<=" -> COND.LS; // Z || !C
+ case ">=" -> COND.PL; // !N
+ case ">" -> COND.HI; // !Z && C
+ default -> throw Utils.TODO();
+ }
+ : switch (bop) {
+ case "==" -> COND.EQ; // Z
+ case "!=" -> COND.NE; // !Z
+ case "<" -> COND.LT; // N != V
+ case "<=" -> COND.LE; // Z || N != V
+ case ">=" -> COND.GE; // N == V
+ case ">" -> COND.GT; // !Z && N == V
+ default -> throw Utils.TODO();
};
}
@@ -594,10 +616,16 @@ public class arm extends Machine {
if( idx==1 ) return null;
// Count floats in signature up to index
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ boolean hidden = hiddenSelf(tfp);
+ int sigidx = idx-2;
+ if( hidden ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
// Count floats in signature up to index
int fcnt=0;
- for( int i=2; i<idx; i++ )
- if( tfp.arg(i-2) instanceof TypeFloat)
+ for( int i=hidden ? 1 : 0; i<idx-2; i++ )
+ if( tfp.arg(i) instanceof TypeFloat)
fcnt++;
// Floats up to XMMS in XMM registers
if( tfp.arg(idx-2) instanceof TypeFloat ) {
@@ -605,24 +633,27 @@ public class arm extends Machine {
return XMMS[fcnt];
} else {
RegMask[] cargs = CALLINMASK;
- if( idx-2-fcnt < cargs.length )
- return cargs[idx-2-fcnt];
+ if( sigidx-fcnt < cargs.length )
+ return cargs[sigidx-fcnt];
}
// Pass on stack slot (8 and higher)
if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + (idx - 2));
+ return new RegMask(MAX_REG + 1 + sigidx);
}
// Return the max stack slot used by this signature, or 0
@Override public short maxArgSlot( TypeFunPtr tfp ) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=0; i<tfp.nargs(); i++ ) {
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
return (short)nstk;
}
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
private static final long CALLEE_SAVE =
1L<<X19 |
@@ -640,7 +671,7 @@ 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(String kind, byte round, LRG lrg) { return new SplitARM(kind,round); }
+ @Override public SplitNode split(LRG lrg, String kind, byte round) { return new SplitARM(lrg,kind,round); }
// Return a MachNode unconditional branch
@Override public CFGNode jump() {
@@ -650,40 +681,47 @@ public class arm extends Machine {
// Instruction selection
@Override public Node instSelect(Node n ) {
return switch( n ) {
- case AddFNode addf -> new AddFARM(addf);
case AddNode add -> add(add);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CallNode call -> call(call);
- case CastNode cast -> new CastMach(cast);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend-> new CallEndMach(cend);
case CProjNode c -> new CProjNode(c);
case ConstantNode con-> con(con);
- case DivFNode divf -> new DivFARM(divf);
- case DivNode div -> new DivARM(div);
+ case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
+ case EscapeNode esc -> new EscapeNode(esc);
case FunNode fun -> new FunARM(fun);
+ case FunPtrNode fptr -> fptr(fptr);
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem-> new MemMergeNode(mem);
- case MinusNode neg -> new NegARM(neg);
- case MulFNode mulf -> new MulFARM(mulf);
- case MulNode mul -> new MulARM(mul);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegARM(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul);
case NewNode nnn -> new NewARM(nnn);
case NotNode not -> new NotARM(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmARM(parm);
+ case BulkMemPhiNode phi -> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
- case ProjNode prj -> new ProjARM(prj);
+ case ProjNode prj -> prj(prj);
case ReadOnlyNode read -> new ReadOnlyMach(read);
case ReturnNode ret -> new RetARM(ret,ret.fun());
case SarNode sar -> asr(sar);
case ShlNode shl -> lsl(shl);
case ShrNode shr -> lsr(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubFNode subf -> new SubFARM(subf);
- case SubNode sub -> sub(sub);
+ case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub);
case ToFloatNode tfn -> new I2F8ARM(tfn);
case XorNode xor -> xor(xor);
@@ -695,7 +733,7 @@ public class arm extends Machine {
private Node cmp(BoolNode bool){
Node cmp = _cmp(bool);
- return new SetARM(cmp, bool.op());
+ return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM);
}
private Node _cmp(BoolNode bool) {
if( bool.isFloat() )
@@ -714,13 +752,18 @@ public class arm extends Machine {
// Most general arith ops will also set flags, which the Jmp needs directly.
// Loads do not set the flags, and will need an explicit TEST
String op = "!=";
- if( iff.in(1) instanceof BoolNode bool ) op = bool.op();
+ boolean fp = false;
+ if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); }
else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
- return new BranchARM(iff, op);
+ else iff.setDef(1, new BoolNode.NE(iff.in(1),
+ ConstantNode.raw(TypeInteger.ZERO),
+ (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
+ return new BranchARM(iff, op, fp);
}
private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFARM(add);
return add.in(2) instanceof ConstantNode off && off._con instanceof TypeInteger ti && imm12(ti)
? new AddIARM(add, (int)ti.value())
: new AddARM(add);
@@ -733,22 +776,30 @@ public class arm extends Machine {
}
private Node con( ConstantNode con ) {
- if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
return switch( con._con ) {
- case TypeInteger ti -> new IntARM(con);
- case TypeFloat tf -> new FloatARM(con);
- case TypeFunPtr tfp -> new TFPARM(con);
- case TypeMemPtr tmp -> new TMPARM(con);
+ case TypeInteger ti -> new IntARM(con,ext);
+ case TypeFloat tf -> new FltARM(con,ext);
+ case TypeMemPtr tmp -> new TMPARM(con,ext);
+ case TypeFunPtr tfp -> new TFPARM(tfp,ext);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con);
+ case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con);
};
}
+ private Node fptr( FunPtrNode con ) {
+ return new TFPARM(con);
+ }
+
private Node call(CallNode call){
- return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
- ? new CallARM(call, tfp)
- : new CallRRARM(call);
+ return call.fptr() instanceof FunPtrNode fptr
+ ? new CallARM(call, (TypeFunPtr)fptr._type)
+ : call.fptr() instanceof ConstantNode con
+ ? new CallARM(call, (TypeFunPtr)con._type)
+ : new CallRRARM(call);
}
private Node or(OrNode or) {
@@ -790,6 +841,10 @@ public class arm extends Machine {
: new LsrARM(lsr);
}
+ private Node prj(ProjNode prj) {
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
+ }
+
private static int off;
private static Node idx;
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
index 2cd00845..1c157bc1 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
@@ -1,19 +1,19 @@
package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.FunPtrNode;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
// Add upper 20bits to PC. Immediate comes from the relocation info.
-public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
- AUIPC( Type tfp ) { super(tfp); }
+public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize {
+ AUIPC( FunPtrNode fptr ) { super(fptr); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public AUIPC copy() { return new AUIPC(_con); }
+ @Override public AUIPC copy() { return new AUIPC(this); }
@Override public String op() { return "auipc"; }
@Override public void encoding( Encoding enc ) {
enc.relo(this);
@@ -36,7 +36,6 @@ public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
@Override public void asm(CodeGen code, SB sb) {
String reg = code.reg(this);
sb.p(reg).p(" = PC+#");
- if( _con == null ) sb.p("---");
- else _con.print(sb);
+ _type.print(sb);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
index bd5dabe5..8e1b4a48 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -14,8 +14,7 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize {
assert tfp.isConstant();
_inputs.pop(); // Pop constant target
_tfp = tfp;
- FunNode fun = CodeGen.CODE.link(tfp);
- _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ _name = CodeGen.CODE.funcName(tfp.fidx());
}
@Override public String op() { return "call"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
index 298789e8..7369f9f5 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
@@ -13,7 +13,7 @@ public class CallRRISC extends CallNode implements MachNode {
? riscv.RMASK // Function call target
: riscv.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument
}
- @Override public RegMask outregmap() { return null; }
+ @Override public RegMask outregmap() { return riscv.RPC_MASK; }
@Override public void encoding( Encoding enc ) {
short rpc = enc.reg(this);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
index 81726200..c1a9b018 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -8,12 +8,13 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class FltRISC extends ConstantNode implements MachNode, RIPRelSize {
- FltRISC(ConstantNode con) { super(con); }
+ final String _ext;
+ FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.FMASK; }
@Override public boolean isClone() { return true; }
- @Override public FltRISC copy() { return new FltRISC(this); }
+ @Override public FltRISC copy() { return new FltRISC(this,_ext); }
@Override public void encoding( Encoding enc ) {
enc.largeConstant(this,_con, 0, -1);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
index edd1850f..e6b4c4ed 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
@@ -4,21 +4,23 @@ import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.ConstantNode;
import com.seaofnodes.simple.node.MachNode;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Special instruction for loading 8 byte constants from the constant pool.
public class Int8RISC extends ConstantNode implements MachNode, RIPRelSize {
- public Int8RISC(ConstantNode con) { super(con); }
-
+ final String _ext;
+ Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public Int8RISC copy() { return new Int8RISC(this); }
+ @Override public Int8RISC copy() { return new Int8RISC(this,_ext); }
@Override public byte encSize(int delta) { return 8; }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
short dst = enc.reg(this);
short tmp = (short)riscv.T6;
enc.largeConstant(this,_con, 0, -1);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
index f8af913a..b795ba96 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
@@ -10,7 +10,7 @@ import com.seaofnodes.simple.util.SB;
// 12-bit integer constant. Larger constants are made up in the instruction
// selection by adding with a LUI.
public class IntRISC extends ConstantNode implements MachNode {
- public IntRISC(ConstantNode con) { super(con); }
+ IntRISC(ConstantNode con) { super(con); }
@Override public String op() { return "ldi"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
index 7e08a01d..592cdb84 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -2,7 +2,9 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Load memory addressing on RISC
// Support imm, reg(direct), or reg+off(indirect) addressing
@@ -21,6 +23,39 @@ public class LoadRISC extends MemOpRISC {
if( dst >= riscv.F_OFFSET ) dst -= riscv.F_OFFSET;
enc.add4(riscv.i_type(op, dst, func3(), ptr, _off));
}
+ @Override int func3() {
+ int func3 = -1;
+ // no unsigned flavour for store, so both signed and unsigned trigger the same
+ Type declType = _con;
+ if( declType == TypeInteger. I8 ) func3=0; // LB
+ if( declType == TypeInteger.I16 ) func3=1; // LH
+ if( declType == TypeInteger.I32 ) func3=2; // LW
+ if( declType == TypeInteger.BOT ) func3=3; // LD
+ if( declType == TypeInteger. U8 ) func3=4; // LBU
+ if( declType == TypeInteger.BOOL) func3=4; // LBU
+ if( declType == TypeInteger.U16 ) func3=5; // LHU
+ if( declType == TypeInteger.U32 ) func3=6; // LWU
+ if( declType instanceof TypeInteger ti ) {
+ if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB
+ }
+
+ // float
+ if( declType == TypeFloat.F32) func3 = 2; // fLW fSW
+ if( declType == TypeFloat.F64) func3 = 3; // fLD fSD
+
+ if( declType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld)
+ if( declType instanceof TypeFunPtr) func3 = 3; // 8 byte function pointers
+ if( declType instanceof TypeConAry ary )
+ func3 = switch( ary.log_size() ) {
+ case 0 -> 4; // LBU
+ case 1 -> 5; // LHU
+ case 2 -> 2; // LW
+ case 3 -> 3; // LD
+ default -> throw Utils.TODO("RISC-V constant-array load for "+declType);
+ };
+ if( func3 == -1 ) throw Utils.TODO("RISC-V load for "+declType);
+ return func3;
+ }
@Override public void asm(CodeGen code, SB sb) {
sb.p(code.reg(this)).p(",");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
index b9e6e5ba..79f22fb8 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -10,6 +10,7 @@ import java.util.BitSet;
public abstract class MemOpRISC extends MemOpNode implements MachNode {
final int _off; // Limit 12 bits
final char _sz;
+ final int _bytes;
MemOpRISC(MemOpNode mop, Node base, int off, Node val) {
super(mop,mop);
assert base._type instanceof TypeMemPtr;
@@ -17,7 +18,8 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode {
_inputs.setX(3, null); // Never an index
_inputs.setX(4, val );
_off = off;
- _sz = (char)('0'+(1<<_declaredType.log_size()));
+ _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+_bytes);
}
@Override public String label() { return op(); }
@@ -29,38 +31,7 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode {
@Override public Node idealize() { throw Utils.TODO(); }
// func3 is based on load/store size and extend
- int func3() {
- int func3 = -1;
- // no unsigned flavour for store, so both signed and unsigned trigger the same
- if(this instanceof StoreRISC) {
- if( _declaredType == TypeInteger. I8 || _declaredType == TypeInteger.U8 || _declaredType == TypeInteger.BOOL) func3=0; // SB
- if( _declaredType == TypeInteger.I16 || _declaredType == TypeInteger.U16 ) func3=1; // SH
- if( _declaredType == TypeInteger.I32 || _declaredType == TypeInteger.U32 ) func3=2; // SW
- if( _declaredType instanceof TypeMemPtr) func3=3; // SD
- if( _declaredType == TypeInteger.BOT ) func3=3; // SD
- if( func3 == -1 ) throw Utils.TODO();
- return func3;
- }
- if( _declaredType == TypeInteger. I8 ) func3=0; // LB
- if( _declaredType == TypeInteger.I16 ) func3=1; // LH
- if( _declaredType == TypeInteger.I32 ) func3=2; // LW
- if( _declaredType == TypeInteger.BOT ) func3=3; // LD
- if( _declaredType == TypeInteger. U8 ) func3=4; // LBU
- if( _declaredType == TypeInteger.BOOL) func3=4; // LBU
- if( _declaredType == TypeInteger.U16 ) func3=5; // LHU
- if( _declaredType == TypeInteger.U32 ) func3=6; // LWU
- if( _declaredType instanceof TypeInteger ti ) {
- if( -128 <= ti._min && ti._max < 128 ) func3 = 0; // LB
- }
-
- // float
- if( _declaredType == TypeFloat.F32) func3 = 2; // fLW fSW
- if( _declaredType == TypeFloat.F64) func3 = 3; // fLD fSD
-
- if( _declaredType instanceof TypeMemPtr) func3 = 3; // 8 byte pointers (pick ld)
- if( func3 == -1 ) throw Utils.TODO();
- return func3;
- }
+ abstract int func3();
// Register mask allowed on input i.
@Override public RegMask regmap(int i) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
index 6c03bda6..4a6583ce 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -40,8 +40,9 @@ public class NewRISC extends NewNode implements MachNode, RIPRelSize {
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("ldi a1=#1\n");
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
sb.p("auipc a2=#calloc\n");
- sb.p("call a2+#calloc, a0");
+ sb.p("call a2+#calloc");
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
index 6082cbbf..58fa8a4b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
@@ -8,7 +8,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitRISC extends SplitNode {
- SplitRISC( String kind, byte round ) { super(kind,round, new Node[2]); }
+ SplitRISC( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); }
@Override public RegMask regmap(int i) { return riscv.SPLIT_MASK; }
@Override public RegMask outregmap() { return riscv.SPLIT_MASK; }
@Override public void encoding( Encoding enc ) {
@@ -24,7 +24,7 @@ public class SplitRISC extends SplitNode {
if( src >= riscv.MAX_REG ) {
throw Utils.TODO(); // Very rare stack-stack move
}
- int off = enc._fun.computeStackOffset(enc._code,dst);
+ int off = fun(enc).computeStackOffset(enc._code,dst);
int op = srcX ? riscv.OP_STOREFP : riscv.OP_STORE;
if( srcX ) src -= riscv.F_OFFSET;
enc.add4(riscv.s_type(op, 0b011, riscv.RSP, src, off));
@@ -32,7 +32,7 @@ public class SplitRISC extends SplitNode {
}
if( src >= riscv.MAX_REG ) {
// Load from SP
- int off = enc._fun.computeStackOffset(enc._code,src);
+ int off = fun(enc).computeStackOffset(enc._code,src);
int op = dstX ? riscv.OP_LOADFP : riscv.OP_LOAD;
if( dstX ) dst -= riscv.F_OFFSET;
enc.add4(riscv.i_type(op, dst, 0b011, riscv.RSP, off));
@@ -57,10 +57,4 @@ public class SplitRISC extends SplitNode {
enc.add4(riscv.r_type(riscv.OP_FP, dst, 0, src - riscv.F_OFFSET, 0, 0b1110001));
}
}
-
- // General form: "mov dst = src"
- @Override public void asm(CodeGen code, SB sb) {
- FunNode fun = code._encoding==null ? null : code._encoding._fun;
- sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
index d06c0c35..ebd6a5ae 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -3,6 +3,7 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.codegen.RegMask;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
@@ -39,6 +40,11 @@ public class StoreRISC extends MemOpRISC {
enc.add4(riscv.s_type(op, func3()&7, ptr, val == -1 ? riscv.ZERO : val, _off));
}
+ // func3 is based on load/store size and extend
+ @Override int func3() {
+ return Integer.numberOfTrailingZeros(_bytes);
+ }
+
@Override public void asm(CodeGen code, SB sb) {
asm_address(code,sb).p(",");
if( val()==null ) sb.p("#").p("0");
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
index 7e057507..1f269152 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -1,46 +1,46 @@
package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.ConstantNode;
-import com.seaofnodes.simple.node.MachNode;
-import com.seaofnodes.simple.node.Node;
+import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
- TFPRISC(ConstantNode con) { super(con); }
+public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize {
+ final String _ext;
+ // Pointer to a Simple function
+ TFPRISC( FunPtrNode fptr ) { super(fptr); _ext = null; }
+ // Pointer to an Extern "C" function
+ TFPRISC( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
@Override public String op() { return "ldx"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TFPRISC copy() { return new TFPRISC(this); }
+ @Override public TFPRISC copy() { return new TFPRISC((TypeFunPtr)_con,_ext); }
@Override public void encoding( Encoding enc ) {
- enc.relo(this);
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
// TODO: 1 op encoding, plus a TODO if it does not fit
short dst = enc.reg(this);
- TypeFunPtr tfp = (TypeFunPtr)_con;
// auipc t0,0
enc.add4(riscv.u_type(riscv.OP_AUIPC, dst, 0));
// addi t1,t0 + #0
enc.add4(riscv.i_type(riscv.OP_IMM, dst, 0, dst, 0));
}
- @Override public byte encSize(int delta) {
- if( -(1L<<11) <= delta && delta < (1L<<11) ) return 4;
- throw Utils.TODO();
- }
+ @Override public byte encSize(int delta) { return 8; }
// Delta is from opcode start
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short rpc = enc.reg(this);
if( opLen==8 ) {
+ int hi20 = (delta + 0x800) >> 12;
+ int lo12 = delta - (hi20 << 12);
// AUIPC (upper 20 bits)
// opstart of add
- int next = opStart + opLen;
- enc.patch4(opStart,riscv.u_type(riscv.OP_AUIPC, rpc, delta>>12));
+ enc.patch4(opStart ,riscv.u_type(riscv.OP_AUIPC, rpc, hi20));
// addi(low 12 bits)
- enc.patch4(next,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, delta & 0xFFF));
+ enc.patch4(opStart+4,riscv.i_type(riscv.OP_IMM, rpc, 0, rpc, lo12 & 0xFFF));
// addi
} else {
// should not happen as one instruction is 4 byte, and TFP arm encodes 2.
@@ -49,7 +49,7 @@ public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
}
@Override public void asm(CodeGen code, SB sb) {
- _con.print(sb.p(code.reg(this)).p(" #"));
+ _type.print(sb.p(code.reg(this)).p(" #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
index 44e73d1d..aa001e1e 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
@@ -6,15 +6,18 @@ import com.seaofnodes.simple.node.MachNode;
import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize {
- TMPRISC(ConstantNode con) { super(con); }
+ final String _ext;
+ TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldp"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return riscv.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public TMPRISC copy() { return new TMPRISC(this); }
+ @Override public TMPRISC copy() { return new TMPRISC(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
short dst = enc.reg(this);
// AUIPC dst,#hi20_constant_pool
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 7a6361bc..078a7693 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -13,7 +13,6 @@ public class riscv extends Machine {
}
@Override public String name() {return "riscv";}
- //@Override public int maxReg() { return MAX_REG; }
// Using ABI names instead of register names
public static int ZERO = 0, RPC= 1, RSP= 2, S12= 3, S13= 4, T0 = 5, T1 = 6, T2 = 7;
@@ -32,24 +31,24 @@ public class riscv extends Machine {
public static final int F_OFFSET = 32;
static final String[] REGS = new String[] {
- "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
- "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
- "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
- "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
- "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
- "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
- "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
- "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
- // Register-based naming hella easier to debug than "official" register
- // names when single-stepping in debugger.
- //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
- //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
- //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
- //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
+ //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
+ //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
+ //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
//"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
//"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
//"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
//"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ // Register-based naming hella easier to debug than "official" register
+ // names when single-stepping in debugger.
+ "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
+ "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
+ "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
+ "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ "f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ "fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ "fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ "fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
};
@Override public String[] regs() { return REGS; }
@@ -91,7 +90,7 @@ public class riscv extends Machine {
public static RegMask FA6_MASK = new RegMask(FA6);
public static RegMask FA7_MASK = new RegMask(FA7);
- // Int arguments calling conv
+ // Int/ptr arguments calling convention
static RegMask[] CALLINMASK = new RegMask[] {
A0_MASK,
A1_MASK,
@@ -257,10 +256,16 @@ public class riscv extends Machine {
if( idx==0 ) return RPC_MASK;
if( idx==1 ) return null;
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
+ boolean hidden = hiddenSelf(tfp);
+ int sigidx = idx-2;
+ if( hidden ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
// Count floats in signature up to index
int fcnt=0;
- for( int i=2; i<idx; i++ )
- if( tfp.arg(i-2) instanceof TypeFloat )
+ for( int i=hidden ? 1 : 0; i<idx-2; i++ )
+ if( tfp.arg(i) instanceof TypeFloat )
fcnt++;
// Floats up to XMMS in XMM registers
if( tfp.arg(idx-2) instanceof TypeFloat ) {
@@ -268,23 +273,26 @@ public class riscv extends Machine {
return XMMS[fcnt];
} else {
RegMask[] cargs = CALLINMASK;
- if( idx-2-fcnt < cargs.length )
- return cargs[idx-2-fcnt];
+ if( sigidx-fcnt < cargs.length )
+ return cargs[sigidx-fcnt];
}
// Pass on stack slot(8 and higher)
if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + (idx - 2));
+ return new RegMask(MAX_REG + 1 + sigidx);
}
@Override public short maxArgSlot( TypeFunPtr tfp ) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=0; i<tfp.nargs(); i++ ) {
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-8,0)+Math.max(fcnt-8,0);
return (short)nstk;
}
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
static final long CALLER_SAVE =
(1L<<RPC) |
@@ -306,7 +314,7 @@ public class riscv extends Machine {
@Override public CFGNode jump() { return new UJmpRISC(); }
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); }
+ @Override public SplitNode split(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); }
// True if signed 12-bit immediate
public static boolean imm12(TypeInteger ti) {
@@ -323,28 +331,34 @@ public class riscv extends Machine {
@Override public Node instSelect( Node n ) {
return switch (n) {
- case AddFNode addf -> addf(addf);
case AddNode add -> add(add);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CallNode call -> call(call);
- case CastNode cast -> new CastMach(cast);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
case CallEndNode cend -> new CallEndMach(cend);
case CProjNode c -> new CProjNode(c);
case ConstantNode con -> con(con);
- case DivFNode divf -> new DivFRISC(divf);
- case DivNode div -> new DivRISC(div);
+ case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
+ case EscapeNode esc -> new EscapeNode(esc);
case FunNode fun -> new FunRISC(fun);
+ case FunPtrNode fptr -> fptr(fptr);
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> new NegRISC(neg);
- case MulFNode mulf -> new MulFRISC(mulf);
- case MulNode mul -> new MulRISC(mul);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegRISC(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul);
case NewNode nnn -> nnn(nnn);
case NotNode not -> new NotRISC(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmRISC(parm);
+ case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read-> new ReadOnlyMach(read);
@@ -352,11 +366,12 @@ public class riscv extends Machine {
case SarNode sar -> sra(sar);
case ShlNode shl -> sll(shl);
case ShrNode shr -> srl(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubFNode subf -> new SubFRISC(subf);
- case SubNode sub -> sub(sub);
+ case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
case XorNode xor -> xor(xor);
@@ -366,11 +381,9 @@ public class riscv extends Machine {
};
}
- private Node addf(AddFNode addf) {
- return new AddFRISC(addf);
- }
-
private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFRISC(add);
if( add.in(2) instanceof ConstantNode off2 && off2._con instanceof TypeInteger ti && imm12(ti) )
return new AddIRISC(add, (int)ti.value(),true);
return new AddRISC(add);
@@ -388,8 +401,10 @@ public class riscv extends Machine {
}
private Node call(CallNode call) {
- return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
- ? new CallRISC(call, tfp)
+ return call.fptr() instanceof FunPtrNode fptr
+ ? new CallRISC(call, (TypeFunPtr)fptr._type)
+ : call.fptr() instanceof ConstantNode con
+ ? new CallRISC(call, (TypeFunPtr)con._type)
: new CallRRISC(call);
}
private Node nnn(NewNode nnn) {
@@ -428,9 +443,13 @@ public class riscv extends Machine {
}
private Node con( ConstantNode con ) {
- if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
+ if( !con._con.isConstant() )
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
return switch( con._con ) {
case TypeInteger ti -> {
+ // External; without knowing the size assume we have to load something
+ if( ext!=null ) yield new Int8RISC(con,ext);
if( imm12(ti) ) yield new IntRISC(con);
long x = ti.value();
if( imm20Exact(ti) ) yield new LUI((int)x);
@@ -443,18 +462,22 @@ public class riscv extends Machine {
}
// Need more complex sequence for larger constants... or a load
// from a constant pool, which does not need an extra register
- yield new Int8RISC(con);
+ yield new Int8RISC(con,null);
}
// Load from constant pool
- case TypeFloat tf -> new FltRISC(con);
- case TypeFunPtr tfp -> new TFPRISC(con);
- case TypeMemPtr tmp -> new TMPRISC(con);
+ case TypeFloat tf -> new FltRISC(con,ext);
+ case TypeMemPtr tmp -> new TMPRISC(con,ext);
+ case TypeFunPtr tfp -> new TFPRISC(tfp,ext);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(con);
+ case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con);
};
}
+ private Node fptr( FunPtrNode con ) {
+ return new TFPRISC(con);
+ }
+
private Node jmp( IfNode iff ) {
if( iff.in(1) instanceof BoolNode bool && !bool.isFloat() ) {
// if less than or equal switch inputs
@@ -509,7 +532,7 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
- return new ProjRISC(prj);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
}
private Node ld(LoadNode ld) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
index 2513de37..093e2bd2 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class AddFMemX86 extends MemOpX86 {
- AddFMemX86( AddFNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
+ AddFMemX86( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
super(add,ld, base, idx, off, scale, 0, val );
}
@Override public String op() { return "addf"+_sz; }
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
index 460f568b..14b8e4da 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java
@@ -20,7 +20,7 @@ public class CallRX86 extends CallNode implements MachNode {
// calls the function in the register
short src = enc.reg(fptr());
enc.add1(0xFF);
- enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT,2,src));
+ enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT,2,src));
}
@Override public void asm(CodeGen code, SB sb) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
index d74e3833..989895b2 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -8,21 +8,27 @@ import com.seaofnodes.simple.util.SB;
public class CallX86 extends CallNode implements MachNode, RIPRelSize {
final TypeFunPtr _tfp;
final String _name;
+ final boolean _extern;
CallX86( CallNode call, TypeFunPtr tfp ) {
super(call);
_inputs.pop(); // Pop constant target
assert tfp.isConstant();
_tfp = tfp;
- FunNode fun = CodeGen.CODE.link(tfp);
- _name = fun==null ? ((ExternNode)call.fptr())._extern : fun._name; // Can be null for extern calls
+ _name = CodeGen.CODE.funcName(tfp.fidx());
+ _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null;
}
@Override public String op() { return "call"; }
@Override public String label() { return op(); }
@Override public String name() { return _name; }
@Override public TypeFunPtr tfp() { return _tfp; }
- @Override public RegMask regmap(int i) { return x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot); }
+ @Override public RegMask regmap(int i) {
+ return _extern
+ ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv)
+ : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot);
+ }
@Override public RegMask outregmap() { return null; }
@Override public int nargs() { return nIns()-2; } // Minus control, memory, fptr
+ @Override public boolean external() { return _extern; }
@Override public void encoding( Encoding enc ) {
FunNode fun = CodeGen.CODE.link(_tfp);
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
index 11f900ed..24812fea 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
@@ -25,7 +25,7 @@ public class CmpMemX86 extends MemOpX86 {
if(src == -1) {
assert !_swap;
- int log = _declaredType.log_size();
+ int log = _con.log_size();
x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
switch(log) {
case 0: enc.add1(0x80); break;
@@ -41,7 +41,7 @@ public class CmpMemX86 extends MemOpX86 {
case 3: enc.add8(_imm); break;
}
} else {
- encVal(enc, _declaredType, ptr, idx, src, _off, _scale, _swap);
+ encVal(enc, _con, ptr, idx, src, _off, _scale, _swap);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
index 9bcbac8f..15e95374 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -3,17 +3,20 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
public class FltX86 extends ConstantNode implements MachNode, RIPRelSize{
private byte _encSize;
- FltX86(ConstantNode con ) { super(con); }
+ final String _ext;
+ FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ld8"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.XMASK; }
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new FltX86(this); }
+ @Override public Node copy() { return new FltX86(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
// Load the constant into a FPR
// movsd xmm, [rip + 0]
// F2 0F 10 /r MOVSD xmm1, m64
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
index cbd5fdbf..1724109a 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -10,6 +10,7 @@ public class FunX86 extends FunNode implements MachNode {
FunX86( FunNode fun ) { super(fun); }
@Override public void postSelect(CodeGen code) {
super.postSelect(code);
+ _maxArgSlot = code._mach.maxArgSlot(sig(),code._callingConv);
// One more for RPC pre-pushed; X86 special
_maxArgSlot++;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
index 3f2f696c..1973ea4e 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -5,15 +5,17 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Integer constants
public class IntX86 extends ConstantNode implements MachNode {
- IntX86( ConstantNode con ) { super(con); }
+ final String _ext;
+ IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() {
return _con == Type.NIL || _con == TypeInteger.ZERO ? "xor" : "ldi";
}
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new IntX86(this); }
+ @Override public Node copy() { return new IntX86(this,_ext); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
// Zero-set uses XOR kills flags
@@ -33,6 +35,7 @@ public class IntX86 extends ConstantNode implements MachNode {
return;
}
+ if( _ext!=null ) throw Utils.TODO();
// Simply move the constant into a GPR
// Conditional encoding based on 64 or 32 bits
//REX.W + C7 /0 id MOV r/m64, imm32
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
index 441cf693..c55002c1 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
@@ -12,7 +12,7 @@ public class LoadX86 extends MemOpX86 {
super(ld,ld, base, idx, off, scale, 0);
}
@Override public String op() { return "ld"+_sz; }
- @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; }
+ @Override public RegMask outregmap() { return _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; }
@Override public void encoding( Encoding enc ) {
// REX.W + 8B /r MOV r64, r/m64
// Zero extension for u8, u16 and u32 but sign extension i8, i16, i32
@@ -20,7 +20,7 @@ public class LoadX86 extends MemOpX86 {
short dst = enc.reg(this );
short ptr = enc.reg(ptr());
short idx = enc.reg(idx());
- enc(enc, _declaredType, dst, ptr, idx, _off, _scale);
+ enc(enc, _con, dst, ptr, idx, _off, _scale);
}
static void enc( Encoding enc, Type decl, short dst, short ptr, short idx, int off, int scale ) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
index 5f090717..6069c07b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -41,7 +41,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode {
_off = off;
_scale = scale;
_imm = imm;
- _sz = (char)('0'+(1<<_declaredType.log_size()));
+ int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
+ _sz = (char)('0'+size);
}
// Store-based flavors have a value edge
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
index 280b8185..1e9e708f 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MulIX86.java
@@ -19,7 +19,7 @@ public class MulIX86 extends MachConcreteNode implements MachNode {
@Override public final void encoding( Encoding enc ) {
short dst = enc.reg(this ); // Also src1
short src = enc.reg(in(1));
- enc.add1(x86_64_v2.rex(src, dst, 0));
+ enc.add1(x86_64_v2.rex(dst, src, 0));
// opcode; 0x69 or 0x6B
enc.add1( 0x69 + (x86_64_v2.imm8(_imm) ? 2 : 0) );
enc.add1( x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src) );
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
index fc896dce..40a38809 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -18,7 +18,8 @@ public class NewX86 extends NewNode implements MachNode {
}
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("ldi rcx = #1\n");
+ sb.p("\n"); // Multi-op trigger
+ sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
sb.p("call #calloc");
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
index 4a14240a..bc3d421d 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -11,8 +11,7 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitX86 extends SplitNode {
- SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); }
- @Override public String op() { return "mov"; }
+ SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); }
@Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
@Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
@@ -49,10 +48,10 @@ public class SplitX86 extends SplitNode {
// Stack spills
if( dst >= x86_64_v2.MAX_REG ) {
- int off = enc._fun.computeStackOffset(enc._code,dst);
+ int off = fun(enc).computeStackOffset(enc._code,dst);
if( src >= x86_64_v2.MAX_REG ) {
// Rare stack-stack move. push [RSP+soff]; pop [RSP+doff]
- int soff = enc._fun.computeStackOffset(enc._code,src);
+ int soff = fun(enc).computeStackOffset(enc._code,src);
enc.add1(0xFF);
x86_64_v2.indirectAdr(0, (short)-1/*index*/, (short)x86_64_v2.RSP, soff, 6, enc);
enc.add1(0x8F);
@@ -63,7 +62,7 @@ public class SplitX86 extends SplitNode {
return;
}
if( src >= x86_64_v2.MAX_REG ) {
- int off = enc._fun.computeStackOffset(enc._code,src);
+ int off = fun(enc).computeStackOffset(enc._code,src);
LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0);
return;
}
@@ -98,10 +97,4 @@ public class SplitX86 extends SplitNode {
enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, src));
}
-
- // General form: "mov dst = src"
- @Override public void asm(CodeGen code, SB sb) {
- FunNode fun = code._encoding==null ? null : code._encoding._fun;
- sb.p(code.reg(this,fun)).p(" = ").p(code.reg(in(1),fun));
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
index 4403b02b..faedeb8b 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
@@ -31,7 +31,7 @@ public class StoreX86 extends MemOpX86 {
short src = enc.reg(val());
if( src == -1 ) {
- int log = _declaredType.log_size();
+ int log = Integer.numberOfTrailingZeros(_sz-'0');
x86_64_v2.rexF(-1, ptr, idx, log==3, enc);
switch( log ) {
case 0: enc .add1(0xC6); break;
@@ -47,7 +47,7 @@ public class StoreX86 extends MemOpX86 {
case 3: enc.add4(_imm); break; // Limit of a 4 byte immediate
}
} else {
- encVal(enc,_declaredType,ptr,idx,src,_off,_scale);
+ encVal(enc,_con,ptr,idx,src,_off,_scale);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
index 359b6d83..8d8d3950 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
@@ -2,54 +2,51 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2;
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.cpus.riscv.riscv;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
// Function constants
-public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
- TFPX86( ConstantNode con ) { super(con); }
- @Override public String op() {
- return _con == Type.NIL ? "xor" : "ldx";
- }
+public class TFPX86 extends FunPtrNode implements MachNode, RIPRelSize {
+ private byte _opLen;
+ final String _ext; // External name
+ // Pointer to a Simple function
+ TFPX86( FunPtrNode fptr ) { this((TypeFunPtr)fptr._type,null); }
+ // Spill clone: copy inputs without registering their reverse edges;
+ // insertBefore will place and register the clone in its use block.
+ private TFPX86( TFPX86 fptr ) { super(fptr); _ext = fptr._ext; }
+ // Pointer to an Extern "C" function
+ TFPX86( TypeFunPtr fptr, String ext ) { super(fptr,CodeGen.CODE._start,null); _type = fptr; _ext = ext; }
+ @Override public String op() { return "ldx"; }
+ @Override public String label() { return op(); }
+ // The ideal FunPtrNode is pinned because its Return input is a lifetime
+ // hook. After selection this is an ordinary cloneable load-address
+ // instruction and must be scheduled down near its actual use.
+ @Override public boolean isPinned() { return false; }
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new TFPX86(this); }
+ @Override public TFPX86 copy() { return new TFPX86(this); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
- // Zero-set uses XOR kills flags
- @Override public RegMask killmap() {
- return _con == Type.NIL ? x86_64_v2.FLAGS_MASK : null;
- }
-
@Override public void encoding( Encoding enc ) {
- short dst = enc.reg(this);
- // Short form for zero
- if( _con==Type.NIL ) {
- // XOR dst,dst. Can skip REX is dst is low 8, makes this a 32b
- // xor, which will also zero the high bits.
- if( dst >= 8 ) enc.add1(x86_64_v2.rex(dst, dst, 0));
- enc.add1(0x33); // opcode
- enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.DIRECT, dst, dst));
- return;
- }
-
+ if( _ext==null ) enc.relo(this); // Internal patch
+ else enc.external(this,_ext); // ELF-file external patch
// lea to load function pointer address
// lea rax, [rip+disp32]
- enc.relo(this);
- enc.add1(x86_64_v2.rex(dst, 0, 0));
+ short dst = enc.reg(this);
+ // 0 or 1 for REX depending on the dst.
+ // Zero/sign extend should be fine, so not wide.
+ _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc));
enc.add1(0x8D); // opcode
enc.add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101));
enc.add4(0);
}
- @Override public byte encSize(int delta) {
- return 7;
- }
+ @Override public byte encSize(int delta) { return _opLen; }
- // Delta is from opcode start
+ // Delta is from opcode start, X86 expects it from opcode end
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart + 3, delta - 7);
+ enc.patch4(opStart + opLen - 4, delta - opLen);
}
// Human-readable form appended to the SB. Things like the encoding,
@@ -57,11 +54,9 @@ public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
// Just something like "ld4\tR17=[R18+12] // Load array base".
// General form: "op\tdst=src+src"
@Override public void asm(CodeGen code, SB sb) {
- String reg = code.reg(this);
- if( _con == Type.NIL )
- sb.p(reg).p(",").p(reg);
- else
- _con.print(sb.p(reg).p(" = #"));
+ sb.p(code.reg(this));
+ if( _ext == null ) _type.print(sb.p(" #"));
+ else sb.p(_ext);
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
index c23f6c5f..cc8741b3 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
@@ -4,32 +4,33 @@ import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{
- TMPX86(ConstantNode con ) { super(con); }
+ private byte _opLen;
+ final String _ext;
+ TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
@Override public String op() { return "ldp"; }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
@Override public boolean isClone() { return true; }
- @Override public Node copy() { return new TMPX86(this); }
+ @Override public Node copy() { return new TMPX86(this,_ext); }
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
short dst = enc.reg(this);
- x86_64_v2.rexF(dst, 0, 0, true, enc);
+ // 0 or 1 for REX depending on the dst.
+ _opLen = (byte)(6+ x86_64_v2.rexF(dst, 0, 0, true, enc));
enc.add1(0x8D).add1(x86_64_v2.modrm(x86_64_v2.MOD.INDIRECT, dst, 0b101)).add4(0);
- int opLen = 1/*rexF*/+1/*opcode 0x8D*/+1/*modrm*/+4/*pc rel offset*/;
- // Base of constant array
- TypeMemPtr tmp = (TypeMemPtr)_con;
- int baseOff = tmp._obj.aryBase();
- enc.largeConstant(this,tmp._obj,opLen-baseOff,2/*ELF encoding PC32*/);
+ enc.largeConstant(this,((TypeMemPtr)_con)._obj, _opLen-4, 2/*ELF encoding PC32*/);
}
// Delta is from opcode start
- @Override public byte encSize(int delta) { return (byte)7; }
+ @Override public byte encSize(int delta) { return _opLen; }
// Delta is from opcode start
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
- enc.patch4(opStart+opLen-4,delta-opLen);
+ enc.patch4(opStart + opLen - 4, delta - opLen);
}
@Override public void asm(CodeGen code, SB sb) {
diff --git a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 026e12aa..a1ad17dc 100644
--- a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -70,7 +70,7 @@ public class x86_64_v2 extends Machine {
public static int REX_WB = 0x49;
public enum MOD {
- INDIRECT, // [mem]
+ INDIRECT, // [mem]
INDIRECT_disp8, // [mem + 0x12]
INDIRECT_disp32, // [mem + 0x12345678]
DIRECT, // mem
@@ -188,22 +188,29 @@ public class x86_64_v2 extends Machine {
// Map from function signature and argument index to register.
// Used to set input registers to CallNodes, and ParmNode outputs.
@Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot ) { return callInMask(tfp,idx,maxArgSlot); }
+ @Override public RegMask callArgMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) { return callInMask(tfp,idx,maxArgSlot,conv); }
static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot ) {
+ return callInMask(tfp,idx,maxArgSlot,CodeGen.CODE._callingConv);
+ }
+ static RegMask callInMask( TypeFunPtr tfp, int idx, int maxArgSlot, String conv ) {
if( idx==0 ) return RPC_MASK;
if( idx==1 ) return null;
if( idx-2 >= tfp.nargs() ) return null; // Anti-dependence
- return switch( CodeGen.CODE._callingConv ) {
+ return switch( conv ) {
case "SystemV" -> callSys5 (tfp,idx,maxArgSlot);
- case "Win64" -> callWin64(tfp,idx,maxArgSlot);
+ case "win64" -> callWin64(tfp,idx,maxArgSlot);
default -> throw Utils.TODO();
};
}
// Maximum stack args used by this signature
@Override public short maxArgSlot( TypeFunPtr tfp ) {
- return switch( CodeGen.CODE._callingConv ) {
+ return maxArgSlot(tfp,CodeGen.CODE._callingConv);
+ }
+ @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) {
+ return switch( conv ) {
case "SystemV" -> maxArgSlotSys5 (tfp);
- case "Win64" -> maxArgSlotWin64(tfp);
+ case "win64" -> maxArgSlotWin64(tfp);
default -> throw Utils.TODO();
};
}
@@ -236,14 +243,22 @@ public class x86_64_v2 extends Machine {
static RegMask callWin64(TypeFunPtr tfp, int idx, int maxArgSlot ) {
// idx 2,3,4,5 passed in registers, with stack slot mirrors.
// idx >= 6 passed on stack, starting at slot#1 (#0 reserved for RPC).
- if( idx >= 6 )
- return new RegMask(MAX_REG+maxArgSlot+(idx-2));
+ int sigidx = idx-2;
+ if( hiddenSelf(tfp) ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
+ if( sigidx >= 4 )
+ return new RegMask(MAX_REG+maxArgSlot+sigidx);
return tfp.arg(idx-2) instanceof TypeFloat
- ? XMMS8 [idx-2]
- : WIN64_CALL[idx-2];
+ ? XMMS8 [sigidx]
+ : WIN64_CALL[sigidx];
}
static short maxArgSlotWin64(TypeFunPtr tfp) {
- return (short)tfp.nargs();
+ return (short)Math.max(4,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0));
+ }
+ static short maxIncomingArgSlotWin64(TypeFunPtr tfp) {
+ return (short)Math.max(0,tfp.nargs() - (hiddenSelf(tfp) ? 1 : 0) - 4);
}
// Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing
@@ -277,9 +292,14 @@ public class x86_64_v2 extends Machine {
static RegMask callSys5(TypeFunPtr tfp, int idx, int maxArgSlot ) {
// First 6 integers passed in registers: rdi,rsi,rdx,rcx,r08,r09
// First 8 floats passed in registers: xmm0-xmm7
+ int sigidx = idx-2;
+ if( hiddenSelf(tfp) ) {
+ if( sigidx==0 ) return null;
+ sigidx--;
+ }
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=2; i<idx; i++ ) {
- if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++;
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) {
+ if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
@@ -289,13 +309,16 @@ public class x86_64_v2 extends Machine {
}
static short maxArgSlotSys5(TypeFunPtr tfp) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=0; i<tfp.nargs(); i++ ) {
+ for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
return (short)nstk;
}
+ private static boolean hiddenSelf(TypeFunPtr tfp) {
+ return tfp.nargs() > 0 && tfp.arg(0) == TypePtr.PTR;
+ }
static final long SYSTEM5_CALLER_SAVE =
(1L << RAX) | (1L << RCX) | (1L << RDX) |
@@ -314,12 +337,18 @@ public class x86_64_v2 extends Machine {
(1L<<XMM4) | (1L<<XMM5) ;
@Override public long callerSave() {
- return switch (CodeGen.CODE._callingConv) {
+ return callerSave(CodeGen.CODE._callingConv);
+ }
+ @Override public long callerSave(String conv) {
+ return switch (conv) {
case "SystemV" -> SYSTEM5_CALLER_SAVE;
- case "Win64" -> WIN64_CALLER_SAVE;
- default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv);
+ case "win64" -> WIN64_CALLER_SAVE;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + conv);
};
}
+ @Override public String cCallingConv(String callingConv) {
+ return callingConv;
+ }
@Override public long neverSave() { return 1L<<RSP; }
@Override public RegMask retMask( TypeFunPtr tfp ) {
return tfp.ret() instanceof TypeFloat ? XMM0_MASK : RAX_MASK;
@@ -328,8 +357,8 @@ public class x86_64_v2 extends Machine {
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(String kind, byte round, LRG lrg) {
- return new SplitX86(kind, round);
+ @Override public SplitNode split(LRG lrg, String kind, byte round ) {
+ return new SplitX86(lrg,kind, round);
}
// Return a MachNode unconditional branch
@@ -341,28 +370,34 @@ public class x86_64_v2 extends Machine {
@Override
public Node instSelect(Node n) {
return switch (n) {
- case AddFNode addf -> addf(addf);
case AddNode add -> add(add);
case AndNode and -> and(and);
case BoolNode bool -> cmp(bool);
case CProjNode c -> new CProjNode(c);
case CallEndNode cend -> new CallEndMach(cend);
case CallNode call -> call(call);
- case CastNode cast -> new CastMach(cast);
+ case CheckCastNode cast -> new CheckCastMach(cast);
+ case PtrToIntNode ptr -> new PtrToIntMach(ptr);
+ case GuardNode guard -> new GuardMach(guard);
case ConstantNode con -> con(con);
- case DivFNode divf -> new DivFX86(divf);
- case DivNode div -> new DivX86(div);
+ case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div);
+ case EscapeNode esc -> new EscapeNode(esc);
case FunNode fun -> new FunX86(fun);
+ case FunPtrNode fptr -> fptr(fptr);
case IfNode iff -> jmp(iff);
case LoadNode ld -> ld(ld);
case MemMergeNode mem -> new MemMergeNode(mem);
- case MinusNode neg -> new NegX86(neg);
- case MulFNode mulf -> new MulFX86(mulf);
- case MulNode mul -> mul(mul);
+ case MinusNode neg -> {
+ if( neg.mode()==2 ) throw Utils.TODO();
+ yield new NegX86(neg);
+ }
+ case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul);
case NewNode nnn -> new NewX86(nnn);
case NotNode not -> new NotX86(not);
case OrNode or -> or(or);
case ParmNode parm -> new ParmX86(parm);
+ case BulkMemPhiNode phi-> new BulkMemPhiNode(phi);
+ case MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read-> new ReadOnlyMach(read);
@@ -370,13 +405,14 @@ public class x86_64_v2 extends Machine {
case SarNode sar -> sar(sar);
case ShlNode shl -> shl(shl);
case ShrNode shr -> shr(shr);
+ case StartCUNode start -> new StartCUNode(start);
+ case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubFNode subf -> new SubFX86(subf);
- case SubNode sub -> sub(sub);
+ case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
- case XCtrlNode x -> new ConstantNode(Type.XCONTROL);
+ case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL);
case XorNode xor -> xor(xor);
case LoopNode loop -> new LoopNode(loop);
@@ -391,15 +427,12 @@ public class x86_64_v2 extends Machine {
// Attempt a full LEA-style break down.
private Node add(AddNode add) {
+ if( add.mode()==2 )
+ return new AddFX86(add);
Node lhs = add.in(1);
Node rhs = add.in(2);
- if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3)
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && ld.declaredType().log_size() >= 3)
return new AddMemX86(add, address(ld), ld.ptr(), idx, off, scale, 0, rhs);
-
-// if(rhs instanceof LoadNode ld && ld.nOuts() == 1 && ld._declaredType.log_size() >= 3) {
-// throw Utils.TODO(); // Swap load sides
-// }
-
// Attempt a full LEA-style break down.
// Returns one of AddX86, AddIX86, LeaX86, or LHS
if( rhs instanceof ConstantNode off2 && off2._con instanceof TypeInteger toff ) {
@@ -422,17 +455,6 @@ public class x86_64_v2 extends Machine {
}
- private Node addf(AddFNode addf) {
- if(addf.in(1) instanceof LoadNode ld && ld.nOuts() == 1)
- return new AddFMemX86(addf, address(ld), ld.ptr(), idx, off, scale, addf.in(2));
-
-// if(addf.in(2) instanceof LoadNode ld && ld.nOuts() == 1)
-// throw Utils.TODO(); // Swap load sides
-
- return new AddFX86(addf);
- }
-
-
private Node _lea(Node add, Node base, Node idx, int off) {
int scale = 0;
if( base instanceof ShlNode && !(idx instanceof ShlNode) )
@@ -456,8 +478,10 @@ public class x86_64_v2 extends Machine {
}
private Node call(CallNode call) {
- return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
- ? new CallX86(call, tfp)
+ return call.fptr() instanceof FunPtrNode con
+ ? new CallX86(call, (TypeFunPtr)con._type)
+ : call.fptr() instanceof ConstantNode con && con._type instanceof TypeFunPtr
+ ? new CallX86(call, (TypeFunPtr)con._type)
: new CallRX86(call);
}
@@ -482,11 +506,11 @@ public class x86_64_v2 extends Machine {
// Since cmp does not record a BOP, SetX/Jmp need to know if the CMP is swapped.
// Vs memory
- if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld._declaredType) )
+ if( lhs instanceof LoadNode ld && ld.nOuts() == 1 && rhs._type.isa(ld.declaredType()) )
return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(rhs), val, false);
// Operands swap in the encoding directly, no need for Set/Jmp to swap `bop`
- if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) &&
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) &&
(val!=null || bool.op()=="==" || bool.op()=="!=") )
return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
@@ -504,18 +528,23 @@ public class x86_64_v2 extends Machine {
private Node con( ConstantNode con ) {
if( !con._con.isConstant() )
- return new ConstantNode(con); // Default unknown caller inputs
+ return ConstantNode.raw(con); // Default unknown caller inputs
+ String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
return switch (con._con) {
- case TypeInteger ti -> new IntX86(con);
- case TypeFloat tf -> new FltX86(con);
- case TypeFunPtr tfp -> new TFPX86(con);
- case TypeMemPtr tmp -> new TMPX86(con);
+ case TypeInteger ti -> new IntX86(con,ext);
+ case TypeFloat tf -> new FltX86(con,ext);
+ case TypeMemPtr tmp -> new TMPX86(con,ext);
+ case TypeFunPtr tfp -> new TFPX86(tfp,ext);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(con);
+ case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con);
};
}
+ private Node fptr( FunPtrNode con ) {
+ return new TFPX86(con);
+ }
+
private Node i2f8(ToFloatNode tfn) {
assert tfn.in(1)._type instanceof TypeInteger ti;
return new I2f8X86(tfn);
@@ -528,7 +557,9 @@ public class x86_64_v2 extends Machine {
String op = "!=";
if( iff.in(1) instanceof BoolNode bool ) op = swap ? IfNode.swap(bool.op()) : bool.op();
else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO)));
+ else iff.setDef(1, new BoolNode.NE(iff.in(1),
+ ConstantNode.raw(TypeInteger.ZERO),
+ (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
return new JmpX86(iff, op);
}
@@ -549,7 +580,7 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
- return new ProjX86(prj);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
}
private Node sar(SarNode sar) {
diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
index a9f86ce8..206983a0 100644
--- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -1,130 +1,126 @@
package com.seaofnodes.simple.print;
+import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.codegen.Encoding.Relo;
import com.seaofnodes.simple.codegen.Encoding;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.*;
+import java.util.HashMap;
import java.util.HashSet;
public abstract class ASMPrinter {
- public static SB print(SB sb, CodeGen code) {
- if( code._cfg==null )
- return sb.p("Need _cfg set, run after GCM");
+ public static SB print(SB sb, CodeGen code ) {
+ Encoding enc = code._encoding;
+ if( enc == null ) return sb.p("No encoding for ").p(code._srcName);
// instruction address
int iadr = 0;
- for( int i=0; i<code._cfg._len; i++ )
- if( code._cfg.at(i) instanceof FunNode fun )
- iadr = print(iadr,sb,code,fun,i);
+ // Print all functions in order
+ for( int i=0; i<enc._cfg._len; i++ )
+ if( enc._cfg.at(i) instanceof FunNode fun )
+ iadr = print(iadr,sb,code,enc,fun,i);
- // constant pool
+ // constant pools
iadr = (iadr+15)&-16; // pad to 16
- Encoding enc = code._encoding;
- if( enc!=null && !enc._bigCons.isEmpty() && iadr < enc._bits.size() ) {
- // radix sort the big constants by alignment
- Ary<Relo>[] raligns = new Ary[5];
- for( Node op : enc._bigCons.keySet() ) {
- Relo relo = enc._bigCons.get(op);
- int align = relo._t.alignment();
- Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>( Relo.class)) : raligns[align];
- relos.add(relo);
- }
+ if( enc._cpool.size()>0 )
+ iadr = printConstantPool(iadr, sb, enc._cpool,enc._bigCons,true ,"Constant Pool");
- HashSet<Type> targets = new HashSet<>();
- sb.p("--- Constant Pool ------").nl();
-
- // By alignment
- for( int align = 4; align >= 0; align-- ) {
- Ary<Relo> relos = raligns[align];
- if( relos == null ) continue;
- for( Relo relo : relos ) {
- if( targets.contains(relo._t) ) continue;
- targets.add(relo._t);
- sb.hex2(iadr).p(" ");
- switch( relo._t ) {
- case TypeTuple tt -> {
- for( Type tx : tt._types ) {
- pN(enc,sb,iadr,align).p(" ");
- iadr += (1<<align);
- }
- }
-
- case TypeStruct ts -> {
- int sz = ts.size();
- int log = 1<<align;
- sz = (sz + (log -1)) & -log; // Round up final padding
- for( int i=0; i<sz; i++ )
- sb.hex1(enc.read1(iadr++));
- }
+ iadr = (iadr+15)&-16; // pad to 16
+ if( enc._sdata.size()>0 )
+ iadr = printConstantPool(iadr, sb,enc._sdata,enc._bigCons,false, "Static Data" );
+ return sb;
+ }
+
+
+ static int printConstantPool( int iadr, SB sb, BAOS bits, HashMap<Node,Relo> bigCons, boolean ro, String msg ) {
+ sb.p("--- ").p(msg).p(" ------").nl();
+ // radix sort the big constants by alignment
+ Ary<Relo>[] raligns = new Ary[5];
+ for( Node op : bigCons.keySet() ) {
+ Relo relo = bigCons.get(op);
+ // non-constant structs in the r/w data, everything else in r/o data
+ if( (relo._t instanceof TypeStruct ts && !ts.isConstant()) == ro )
+ continue;
+ int align = relo._t.alignment();
+ Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>( Relo.class)) : raligns[align];
+ relos.add(relo);
+ }
+
+ HashSet<Type> targets = new HashSet<>();
+
+ // By alignment
+ int dadr=0;
+ for( int align = 4; align >= 0; align-- ) {
+ Ary<Relo> relos = raligns[align];
+ if( relos == null ) continue;
+ for( Relo relo : relos ) {
+ if( targets.contains(relo._t) ) continue;
+ targets.add(relo._t);
+ sb.hex2(iadr+dadr).p(" ");
+ if( relo._t instanceof TypeStruct ts ) {
+ int sz = ts.size();
+ int log = 1<<align;
+ sz = (sz + (log -1)) & -log; // Round up final padding
+ for( int i=0; i<sz; i++ )
+ sb.hex1(bits.read1(dadr++));
+ } else {
// Simple primitive (e.g. larger int, float)
- default -> {
- pN(enc,sb,iadr,align).fix(9-(1<<align),"");
- iadr += (1<<align);
- }
- }
- relo._t.print(sb).nl();
+ pN(bits,sb,dadr,align).fix(9-(1<<align),"");
+ dadr += (1<<align);
}
+ relo._t.print(sb.p('\t')).nl();
}
}
-
- return sb;
+ return iadr+dadr;
}
- private static SB pN( Encoding enc, SB sb, int iadr, int log ) {
+
+ private static SB pN( BAOS bits, SB sb, int dadr, int log ) {
switch( log ) {
- case 0: sb.hex1(enc.read1(iadr)); break;
- case 1: sb.hex2(enc.read2(iadr)); break;
- case 2: sb.hex4(enc.read4(iadr)); break;
- case 3: sb.hex8(enc.read8(iadr)); break;
+ case 0: sb.hex1(bits.read1(dadr)); break;
+ case 1: sb.hex2(bits.read2(dadr)); break;
+ case 2: sb.hex4(bits.read4(dadr)); break;
+ case 3: sb.hex8(bits.read8(dadr)); break;
}
return sb;
}
- private static int print(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) {
- FunNode old=null;
- if( code._encoding!=null ) {
- old = code._encoding._fun;
- code._encoding._fun = fun; // Useful printing after RA
- }
+ private static int print(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) {
// Function header
sb.nl().p("---");
if( fun._name != null ) sb.p(fun._name).p(" ");
fun.sig().print(sb);
sb.p("---------------------------").nl();
- if( code._encoding!=null && code._encoding._padFunHeads )
- iadr = (iadr + 15)&-16; // All function entries padded to 16 align
+ iadr = (iadr + 15)&-16; // All function entries padded to 16 align
if( fun._frameAdjust != 0 )
- iadr = doInst(iadr,sb,code,fun,cfgidx,fun,true,true);
- while( !(code._cfg.at(cfgidx) instanceof ReturnNode) )
- iadr = doBlock(iadr,sb,code,fun,cfgidx++);
+ iadr = doInst(iadr,sb,code,enc, cfgidx,fun,true,enc!=null);
+ while( !(enc._cfg.at(cfgidx) instanceof ReturnNode) )
+ iadr = doBlock(iadr,sb,code,enc,fun,cfgidx++);
// Function separator
sb.p("---");
fun.sig().print(sb);
sb.p("---------------------------").nl();
- if( code._encoding != null )
- code._encoding._fun = old;
return iadr;
}
static private final int opWidth = 5;
static private final int argWidth = 30;
- static int doBlock(int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx) {
+ static int doBlock(int iadr, SB sb, CodeGen code, Encoding enc, FunNode fun, int cfgidx) {
final int encWidth = code._mach==null ? 2 : code._mach.defaultOpSize()*2;
- CFGNode bb = code._cfg.at(cfgidx);
+ CFGNode bb = enc._cfg.at(cfgidx);
if( bb != fun && !(bb instanceof IfNode) && !(bb instanceof CallEndNode) && !(bb instanceof CallNode) && !(bb instanceof CProjNode && bb.in(0) instanceof CallEndNode ))
sb.p(label(bb)).p(":").nl();
if( bb instanceof CallNode ) return iadr;
- final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal();
final boolean postEncode= code._phase.ordinal() >=CodeGen.Phase.Encoding.ordinal();
+ final boolean postAlloc = code._phase.ordinal() > CodeGen.Phase.RegAlloc.ordinal() ||
+ (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && code._regAlloc.done());
boolean once=false;
for( Node n : bb.outs() ) {
@@ -133,10 +129,10 @@ public abstract class ASMPrinter {
// Post-RegAlloc phi prints all on one line
if( postAlloc ) {
if( !once ) { once=true; sb.fix(4," ").p(" ").fix(encWidth,"").p(" "); }
- sb.p(phi._label).p(':').p(code.reg(phi,fun)).p(',');
+ sb.p(phi._label).p(':').p(code.reg(phi)).p(',');
} else {
// Pre-RegAlloc phi prints one line per
- sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi,fun));
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,phi._label).p(" ").p(code.reg(phi));
if( phi.getClass() == PhiNode.class ) {
sb.p(" = phi( ");
for( int i=1; i<phi.nIns(); i++ )
@@ -151,16 +147,17 @@ public abstract class ASMPrinter {
// All the non-phis
for( int i=0; i<bb.nOuts(); i++ )
if( !(bb.out(i) instanceof PhiNode) )
- iadr = doInst(iadr, sb,code, fun, cfgidx, bb.out(i),postAlloc, postEncode );
+ iadr = doInst(iadr, sb,code, enc, cfgidx, bb.out(i),postAlloc, postEncode );
return iadr;
}
- static int doInst( int iadr, SB sb, CodeGen code, FunNode fun, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) {
+ static int doInst( int iadr, SB sb, CodeGen code, Encoding enc, int cfgidx, Node n, boolean postAlloc, boolean postEncode ) {
if( n==null || n instanceof CProjNode ) return iadr;
if( postAlloc && n instanceof CalleeSaveNode ) return iadr;
if( postEncode && n instanceof ProjNode ) return iadr;
if( n instanceof MemMergeNode ) return iadr;
+ if( n instanceof EscapeNode ) return iadr;
if( n.getClass() == ConstantNode.class ) return iadr; // Default placeholders
final int dopz = code._mach==null ? 2 : code._mach.defaultOpSize();
final int encWidth = dopz*2;
@@ -170,8 +167,8 @@ public abstract class ASMPrinter {
// need to assume a jump. There's no real hardware op here, yet.
if( n instanceof RegionNode cfg && !(n instanceof FunNode) ) {
if( postEncode ) return iadr; // All jumps inserted already
- while( cfgidx < code._cfg._len-1 ) {
- CFGNode next = code._cfg.at(++cfgidx);
+ while( cfgidx < enc._cfg._len-1 ) {
+ CFGNode next = enc._cfg.at(++cfgidx);
if( next == n ) return iadr; // Fall-through, no branch
if( next.nOuts()>1 )
break; // Has code in the block, need to jump around
@@ -185,7 +182,9 @@ public abstract class ASMPrinter {
// get indent slightly and just print their index & node#
if( n instanceof ProjNode proj ) {
if( proj._type instanceof TypeMem ) return iadr; // Nothing for the hidden ones
- sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,proj._label==null ? "---" : proj._label).p(" ").p(code.reg(n,fun)).nl();
+ sb.fix(4," ").p(" ").fix(encWidth,"").p(" ").fix(opWidth,code.reg(n)).p(" // ");
+ if( proj._label != null ) sb.p(proj._label);
+ sb.nl();
return iadr;
}
@@ -195,15 +194,15 @@ public abstract class ASMPrinter {
// Encoding
int fatEncoding = 0;
- if( code._encoding != null && code._encoding._opLen!=null ) {
- int size = code._encoding._opLen[n._nid];
+ if( enc != null ) {
+ int size = enc.opLen(n);
if( code._asmLittle )
for( int i=0; i<Math.min(size,dopz); i++ )
- sb.hex1(code._encoding._bits.buf()[iadr++]);
+ sb.hex1(enc._bits.buf()[iadr++]);
else {
iadr += Math.min(size,dopz);
for( int i=0; i<Math.min(size,dopz); i++ )
- sb.hex1(code._encoding._bits.buf()[iadr-i-1]);
+ sb.hex1(enc._bits.buf()[iadr-i-1]);
}
for( int i=size*2; i<encWidth; i++ )
sb.p(" ");
@@ -244,16 +243,16 @@ public abstract class ASMPrinter {
sb.nl();
// Printing more op bits than fit
- if( isMultiOp != null && code._encoding != null && code._encoding._opLen!=null ) {
+ if( isMultiOp != null ) {
// Multiple ops, template style, no RA, no scheduling. Print out
// one-line-per-newline, with encoding bits up front.
- int size = code._encoding._opLen[n._nid];
+ int size = enc != null ? enc.opLen(n) : 0;
int off = Math.min(size,dopz);
while( isMultiOp!=null ) {
sb.hex2(iadr).p(" ");
int len = Math.min(size-off,dopz);
for( int i=0; i<len; i++ )
- sb.hex1(code._encoding._bits.buf()[iadr++]);
+ sb.hex1(enc._bits.buf()[iadr++]);
off += len;
for( int i=len; i<dopz; i++ ) sb.p(" ");
sb.p(" ");
@@ -273,7 +272,7 @@ public abstract class ASMPrinter {
// themselves. X86 special for super long encodings
sb.hex2(iadr).p(" ");
for( int i=0; i<fatEncoding; i++ )
- sb.hex1(code._encoding._bits.buf()[iadr++]);
+ sb.hex1(enc._bits.buf()[iadr++]);
sb.nl();
}
@@ -282,7 +281,7 @@ public abstract class ASMPrinter {
if( !(n instanceof CFGNode) && n instanceof MultiNode ) {
for( Node proj : n._outputs ) {
if( proj instanceof ProjNode ) // it could also be an ante-dep
- doInst(iadr,sb,code,fun, cfgidx, proj,postAlloc,postEncode);
+ doInst(iadr,sb,code,enc, cfgidx, proj,postAlloc,postEncode);
}
}
@@ -294,13 +293,10 @@ public abstract class ASMPrinter {
}
private static String isMultiOp(SB sb, int old, int len) {
- for( int i=old; i<len; i++ )
- if( sb.at(i)=='\n' ) {
- String s = sb.subString(i+1,len);
- sb.setLen(i);
- return s;
- }
- return null;
+ if( old==len || sb.at(old)!='\n' ) return null;
+ String rest = sb.subString(old+1,len);
+ sb.setLen(old);
+ return rest;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java b/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
index 20c8439a..ad2df8b6 100644
--- a/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
+++ b/src/main/java/com/seaofnodes/simple/print/GraphVisualizer.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.print;
import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CompUnit;
import com.seaofnodes.simple.node.*;
import java.util.*;
import static com.seaofnodes.simple.util.Utils.TODO;
@@ -21,16 +22,16 @@ public class GraphVisualizer {
public GraphVisualizer(boolean separateControlCluster) { this._separateControlCluster = separateControlCluster; }
public GraphVisualizer() { this(false); }
- public String generateDotOutput(Parser parse) { return generateDotOutput(parse._code._stop,parse._scope,parse._xScopes); }
- public String generateDotOutput(StopNode stop, Node scope, Stack<ScopeNode> xScopes) {
+ public String generateDotOutput(Parser parse) { return generateDotOutput(parse._ref,parse._scope,parse._xScopes); }
+ public String generateDotOutput(CompUnit cu, Node scope, Stack<ScopeNode> xScopes) {
// Since the graph has cycles, we need to create a flat list of all the
// nodes in the graph.
- Collection<Node> all = findAll(xScopes, stop, scope);
+ Collection<Node> all = findAll(xScopes, cu._stop, scope);
StringBuilder sb = new StringBuilder();
sb.append("digraph chapter18 {\n");
sb.append("/*\n");
- sb.append(stop._src);
+ sb.append(cu._src);
sb.append("\n*/\n");
// To keep the Scopes below the graph and pointing up into the graph we
@@ -80,7 +81,7 @@ public class GraphVisualizer {
// Just the Nodes first, in a cluster no edges
sb.append(doCtrl ? "\tsubgraph cluster_Controls {\n" : "\tsubgraph cluster_Nodes {\n"); // Magic "cluster_" in the subgraph name
for (Node n : all) {
- if (n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n==Parser.XCTRL)
+ if( n instanceof ProjNode || n instanceof CProjNode || n instanceof MemMergeNode || n instanceof XCtrlNode )
continue; // Do not emit, rolled into MultiNode or Scope cluster already
if( _separateControlCluster && doCtrl && !(n instanceof CFGNode) ) continue;
if( _separateControlCluster && !doCtrl && (n instanceof CFGNode) ) continue;
@@ -162,7 +163,7 @@ public class GraphVisualizer {
sb.append("\tsubgraph cluster_").append(scopeName).append(" {\n"); // Magic "cluster_" in the subgraph name
// Special for memory ScopeMinNode
if( level==0 ) {
- MemMergeNode n = scope.mem();
+ Node n = scope.mem();
sb.append("\t\t").append(n.uniqueName()).append(" [label=\"").append(n.glabel()).append("\"];\n");
}
sb.append("\t\t").append(scopeName).append(" [label=<\n");
@@ -195,7 +196,7 @@ public class GraphVisualizer {
// Do not display the Constant->Start edge;
// ProjNodes handled by Multi;
// ScopeNodes are done separately
- if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n==Parser.XCTRL )
+ if( n instanceof ConstantNode || n instanceof ProjNode || n instanceof CProjNode || n instanceof ScopeNode || n instanceof XCtrlNode )
continue;
for( int i=0; i<n.nIns(); i++ ) {
Node def = n.in(i);
diff --git a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
index d1265bef..7c0f84f3 100644
--- a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -1,14 +1,12 @@
package com.seaofnodes.simple.print;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Serialize;
import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashMap;
+
+import java.util.*;
public abstract class IRPrinter {
@@ -37,295 +35,138 @@ public abstract class IRPrinter {
return sb.p("\n");
}
-
- public static String prettyPrint(Node node, int depth) {
- return CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Schedule.ordinal()
- ? _prettyPrintScheduled( node, depth )
- : _prettyPrint( node, depth );
- }
-
- // Another bulk pretty-printer. Makes more effort at basic-block grouping.
- private static String _prettyPrint( Node node, int depth ) {
- // First, a Breadth First Search at a fixed depth.
- BFS bfs = new BFS(node,depth);
- // Convert just that set to a post-order
- ArrayList<Node> rpos = new ArrayList<>();
- BitSet visit = new BitSet();
- for( int i=bfs._lim; i< bfs._bfs.size(); i++ )
- postOrd( bfs._bfs.get(i), null, rpos, visit, bfs._bs);
- // Reverse the post-order walk
+ // Bulk whole program pretty print
+ public static String prettyPrint( CodeGen code ) {
+ // Loop-tree for nodeOrder
+ CFGNode.LoopTree old = code._start._ltree;
+ if( old==null )
+ code._start.buildLoopTree( code._linker, code._stop);
+ Ary<Node> nodes = Serialize.nodeOrder(code);
+ if( old == null )
+ code._start._ltree = null;
SB sb = new SB();
- boolean gap=false;
- for( int i=rpos.size()-1; i>=0; i-- ) {
- Node n = rpos.get(i);
- if( n instanceof CFGNode || n instanceof MultiNode ) {
- if( !gap ) sb.p("\n"); // Blank before multihead
- if( n instanceof FunNode fun )
- fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ")).p("----------------------\n");
- printLine( n, sb ); // Print head
- while( --i >= 0 ) {
- Node t = rpos.get(i);
- if( !(t.in(0) instanceof MultiNode) ) { i++; break; }
- printLine( t, sb );
- }
- if( n instanceof ReturnNode ret ) {
- FunNode fun = ret.fun();
- sb.p("--- ");
- if( fun != null )
- fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "));
- sb.p("----------------------\n");
- }
- if( !(n instanceof CallNode) ) {
- sb.p("\n"); // Blank after multitail
- gap = true;
- }
- } else {
- printLine( n, sb );
- gap = false;
+ Node prior=null;
+ for( Node n : nodes ) {
+ if( n instanceof FunNode fun ) {
+ sb.nl().p("--- ");
+ fun.sig().print(sb.p(fun._name==null ? "" : fun._name).p(" "));
+ sb.p("----------------------\n");
}
+ if( n instanceof MultiNode || n instanceof RegionNode || n instanceof CallNode ||
+ (isMultiChild(prior) && !isMultiChild(n)) )
+ sb.nl();
+ printLine(n,sb);
+ if( n instanceof ReturnNode ret )
+ sb.p("--- ").p(ret._fun._name==null ? "" : ret._fun._name).p(" ----------------------\n");
+ prior = n;
}
return sb.toString();
}
- private static void postOrd(Node n, Node prior, ArrayList<Node> rpos, BitSet visit, BitSet bfs) {
- if( !bfs.get(n._nid) )
- return; // Not in the BFS visit
- if( n instanceof FunNode && !(prior instanceof StartNode) )
- return; // Only visit Fun from Start
- if( visit.get(n._nid) ) return; // Already post-order walked
- visit.set(n._nid);
- // First walk the CFG, then everything
- if( n instanceof CFGNode ) {
- for( Node use : n._outputs )
- // Follow CFG, not across call/function borders, and not around backedges
- if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) &&
- use.nOuts() >= 1 && !(use._outputs.get(0) instanceof LoopNode) )
- postOrd(use, n, rpos,visit,bfs);
- for( Node use : n._outputs )
- // Follow CFG, not across call/function borders
- if( use instanceof CFGNode && !(n instanceof CallNode && use instanceof FunNode) )
- postOrd(use,n,rpos,visit,bfs);
- }
- // Follow all outputs
- for( Node use : n._outputs )
- if( use != null &&
- !(n instanceof CallNode && use instanceof FunNode) &&
- (n instanceof FunNode || !(use instanceof ParmNode)) )
- postOrd(use, n, rpos,visit,bfs);
- // Post-order
- rpos.add(n);
+ private static boolean isMultiHead(Node n) {
+ return n instanceof RegionNode || n instanceof MultiNode;
}
- // Breadth-first search, broken out in a class to keep in more independent.
- // Maintains a root-set of Nodes at the limit (or past by 1 if MultiHead).
- public static class BFS {
- // A breadth first search, plus MultiHeads for any MultiTails
- public final ArrayList<Node> _bfs;
- public final BitSet _bs; // Visited members by node id
- public final int _depth; // Depth limit
- public final int _lim; // From here to _bfs._len can be roots for a reverse search
- public BFS( Node base, int d ) {
- _depth = d;
- _bfs = new ArrayList<>();
- _bs = new BitSet();
-
- add(base); // Prime the pump
- int idx=0, lim=1; // Limit is where depth counter changes
- while( idx < _bfs.size() ) { // Ran out of nodes below depth
- Node n = _bfs.get(idx++);
- for( Node def : n._inputs )
- if( def!=null && !_bs.get(def._nid) )
- add(def);
- if( idx==lim ) { // Depth counter changes at limit
- if( --d < 0 )
- break; // Ran out of depth
- lim = _bfs.size(); // New depth limit
- }
- }
- // Toss things past the limit except multi-heads
- while( idx < _bfs.size() ) {
- Node n = _bfs.get(idx);
- if( n instanceof MultiNode ) idx++;
- else del(idx);
- }
- // Root set is any node with no inputs in the visited set
- lim = _bfs.size();
- for( int i=_bfs.size()-1; i>=0; i-- )
- if( !any_visited(_bfs.get(i)) )
- swap( i,--lim);
- _lim = lim;
- }
- void swap( int x, int y ) {
- if( x==y ) return;
- Node tx = _bfs.get(x);
- Node ty = _bfs.get(y);
- _bfs.set(x,ty);
- _bfs.set(y,tx);
- }
- void add(Node n) {
- _bfs.add(n);
- _bs.set(n._nid);
- }
- void del(int idx) {
- _bs.clear(_bfs.get(idx)._nid);
- Utils.del(_bfs, idx);
- }
- boolean any_visited( Node n ) {
- for( Node def : n._inputs )
- if( def!=null && _bs.get(def._nid) )
- return true;
- return false;
- }
+ private static boolean isMultiChild(Node n) {
+ return n!=null && n.nIns() > 0 && n.in(0) != null && isMultiHead(n.in(0));
}
- public static String _prettyPrint( CodeGen code ) {
- SB sb = new SB();
- // Print the Start "block"
- printLine(code._start,sb);
- for( Node n : code._start._outputs )
- printLine(n,sb);
- sb.nl();
- // Skip start, stop
- for( int i=1; i<code._cfg._len-1; i++ ) {
- CFGNode blk = code._cfg.at(i);
- if( blk.blockHead() && (!(blk instanceof CProjNode) || (blk.cfg0() instanceof IfNode )) ) {
- if( blk instanceof FunNode fun )
- sb.p("--- ").p(fun.label()).p(" ---------------------------").nl();
- // Print block header
- sb.p("%-13.13s".formatted(label(blk)+":"));
- sb.p( " ".repeat(4) ).p(" [[ ");
- if( blk instanceof RegionNode )
- for( int j=1; j<blk.nIns(); j++ )
- label(sb,blk.cfg(j));
- else
- label(sb,blk.cfg(0));
- sb.p(" ]] \n");
- }
- printLine(blk,sb);
- if( blk instanceof ReturnNode ret )
- sb.p("--- ").p(ret.fun().label()).p(" ---------------------------").nl();
+ // ----------------------------------------
+ // Another bulk pretty-printer. Makes more effort at basic-block grouping.
+ public static String prettyPrint(Node node, int depth) {
+ // Convert just that set to a post-order
+ Ary<Node> post = new Ary<>(Node.class);
+ Ary<Node> rets = new Ary<>(Node.class);
+ var visit = new IdentityHashMap<Node,Integer>();
+ assert depth < 5000;
+ postOrd( node, 0, depth, visit, post, rets);
+ // Function calls all print independent
+ while( rets.size()>0 )
+ postOrd( rets.pop(), 0, depth, visit, post, rets );
- // Block contents
- for( Node n : blk._outputs ) {
- if( n instanceof CFGNode cfg ) continue;
- printLine(n,sb);
- if( !(n instanceof CFGNode) && n instanceof MultiNode )
- for( Node use : n._outputs )
- if( use instanceof ProjNode )
- printLine(use,sb);
+
+ // Reverse the post-order walk
+ SB sb = new SB();
+ for( int i=0; i<post._len; i++ ) {
+ Node n = post.at(i);
+ // Split ahead of a Region/MultiNode
+ if( n instanceof RegionNode || n instanceof MultiNode ||
+ (i>0 && isMultiChild(post.at(i-1)) && !isMultiChild(n)) )
+ sb.nl();
+ if( n instanceof FunNode fun )
+ fun.sig().print(sb.p("--- ").p(fun._name==null ? "" : fun._name).p(" ")).p("----------------------\n");
+ printLine( n, sb ); // Print head
+ if( n instanceof ReturnNode ret ) {
+ FunNode fun = ret.fun();
+ sb.p("--- ").p(fun==null ? "" : fun._name).p("----------------------\n");
}
}
-
- printLine(code._stop,sb);
return sb.toString();
}
- // Bulk pretty printer, knowing scheduling information is available
- private static String _prettyPrintScheduled( Node node, int depth ) {
- // Backwards DFS walk to depth.
- HashMap<Integer,Integer> ds = new HashMap<>();
- Ary<Node> ns = new Ary<>(Node.class);
- _walk(ds,ns,node,depth);
- // Remove data projections, these are force-printed behind their multinode head
- for( int i=0; i<ns.size(); i++ ) {
- if( ns.get(i) instanceof ProjNode proj && !(proj.in(0) instanceof CFGNode) ) {
- ns.del(i--);
- ds.remove(proj._nid);
- }
- }
- // Print by block with least idepth
- SB sb = new SB();
- Ary<Node> bns = new Ary<>(Node.class);
- while( !ds.isEmpty() ) {
- CFGNode blk = null;
- for( Node n : ns ) {
- CFGNode cfg = n instanceof CFGNode cfg0 && cfg0.blockHead() ? cfg0 : n.cfg0();
- if( blk==null || cfg.idepth() < blk.idepth() )
- blk = cfg;
- }
- Integer d = ds.remove(blk._nid);
- ns.del(ns.find(blk));
- // Print block header
- sb.p("%-13.13s".formatted(label(blk)+":"));
- sb.p( " ".repeat(4) ).p(" [[ ");
- if( blk instanceof StartNode ) ;
- else if( blk instanceof RegionNode || blk instanceof StopNode )
- for( int i=(blk instanceof StopNode ? 3 : 1); i<blk.nIns(); i++ )
- label(sb,blk.cfg(i));
- else
- label(sb,blk.cfg(0));
- sb.p(" ]] \n");
- printLine(blk,sb);
+ // keep the largest PO depth?; probably want *shortest*
+ private static void postOrd( Node n, int d, int cutoff, IdentityHashMap<Node,Integer> visit, Ary<Node> post, Ary<Node> rets ) {
+ if( n==null ) return;
+ if( d >= cutoff ) return; // Too deep
+ Integer depth = visit.get(n);
+ if( depth!=null ) return; // Been there, done that
+
+ // Not a multi-child (Phi of a Region or Proj of a MultiNode)
+ if( !isMultiHead(n) && !isMultiChild(n) ) {
+ visit.put(n,d); // Visit node N and depth D
+ for( Node def : n._inputs ) // Children before visit
+ postOrd(def, d+1, cutoff, visit, post, rets);
+ post.add(n); // Post-order add
+ return;
+ }
- // Collect block contents that are in the depth limit
- bns.clear();
- int xd = Integer.MAX_VALUE;
- for( Node use : blk._outputs ) {
- Integer i = ds.get(use._nid);
- if( i!=null && !(use instanceof CFGNode cfg && cfg.blockHead()) ) {
- if( bns.find(use)==-1 )
- bns.add(use);
- xd = Math.min(xd,i);
+ // Multi-child; print the parent and all children together
+ Node multi = isMultiHead(n) ? n : n.in(0);
+ // Multi + children are visited all at once
+ visit.put(multi,d+1);
+ for( Node out : multi.outs() )
+ visit.put(out,d);
+
+ // Order to visit all the children
+ Node[] outs = multi.outs().asAry();
+ Arrays.sort(outs, Comparator.comparingInt( IRPrinter::sortOrder ) );
+
+ // Do not walk out of Functions, this makes functions stand-alone
+ // but otherwise unordered
+ if( !(multi instanceof FunNode fun && fun._folding) ) {
+ // Visit all children inputs all at once
+ for( Node out : outs ) {
+ // A FunNode is both a multi-head AND a multi-child from Start
+ if( !isMultiHead(out) && out != null ) {
+ for( Node def : out._inputs ) {
+ postOrd(def, d+1, cutoff, visit, post, rets);
+ }
}
}
- // Print Phis up front, if any
- for( int i=0; i<bns.size(); i++ )
- if( bns.get(i) instanceof PhiNode phi )
- printLine( phi, sb,bns,i--,ds,ns);
-
- // Print block contents in depth order, bumping depth until whole block printed
- for( ; !bns.isEmpty(); xd++ )
- for( int i=0; i<bns.size(); i++ ) {
- Node n = bns.get(i);
- if( ds.get(n._nid)==xd ) {
- printLine( n, sb, bns, i--, ds,ns );
- if( n instanceof MultiNode && !(n instanceof CFGNode) ) {
- for( Node use : n._outputs ) {
- if( use instanceof ProjNode )
- printLine(use,sb,bns,bns.indexOf(use),ds,ns);
- }
- }
- }
+ // Visit multi inputs
+ for( Node def : multi._inputs ) {
+ if( multi instanceof CallEndNode && def instanceof ReturnNode ret && !ret._fun._folding ) {
+ rets.add(ret); // But save the Return for a separate function print
+ } else {
+ postOrd(def, d+2, cutoff, visit, post, rets);
}
- sb.p("\n");
+ }
}
- return sb.toString();
- }
- private static void _walk( HashMap<Integer,Integer> ds, Ary<Node> ns, Node node, int d ) {
- Integer nd = ds.get(node._nid);
- if( nd!=null && d <= nd ) return; // Been there, done that
- Integer old = ds.put(node._nid,d) ;
- if( old == null )
- ns.add(node);
- if( d == 0 ) return; // Depth cutoff
- for( Node def : node._inputs )
- if( def != null &&
- !(node instanceof LoopNode loop && loop.back()==def) &&
- // Don't walk into or out of functions
- !(node instanceof CallEndNode && def instanceof ReturnNode) &&
- !(node instanceof FunNode && def instanceof CallNode) &&
- !(node instanceof ParmNode && !(def instanceof FunNode))
- )
- _walk(ds,ns,def,d-1);
+ // Post visit the multi and all children
+ post.add(multi);
+ for( Node out : outs )
+ if( !isMultiHead(out) )
+ post.add(out);
}
- static String label( CFGNode blk ) {
- if( blk instanceof StartNode ) return "START";
- return (blk instanceof LoopNode ? "LOOP" : "L")+blk._nid;
- }
- static void label( SB sb, CFGNode blk ) {
- if( !blk.blockHead() ) blk = blk.cfg(0);
- sb.p( "%-9.9s ".formatted( label( blk ) ) );
- }
- static void printLine( Node n, SB sb, Ary<Node> bns, int i, HashMap<Integer,Integer> ds, Ary<Node> ns ) {
- printLine( n, sb );
- if( i != -1 ) bns.del(i);
- ds.remove(n._nid);
- int idx = ns.find(n);
- if( idx!=-1 ) ns.del(idx);
+ static int sortOrder( Node n ) {
+ if( n instanceof ProjNode proj ) return proj._idx;
+ if( n instanceof PhiNode phi ) return phi._nid;
+ if( n == null ) return 0;
+ return n._nid+1000000;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/print/JSViewer.java b/src/main/java/com/seaofnodes/simple/print/JSViewer.java
index 62ff0001..0f34af2e 100644
--- a/src/main/java/com/seaofnodes/simple/print/JSViewer.java
+++ b/src/main/java/com/seaofnodes/simple/print/JSViewer.java
@@ -202,11 +202,11 @@ public class JSViewer implements AutoCloseable {
int lexStart = level==0 ? 0 : scope._kinds.at(level-1)._lexSize;
// Special for memory ScopeMinNode
- MemMergeNode n = scope.nIns()>1 ? scope.mem() : null;
- if( level==0 && n!=null && n.nIns()>2 ) {
+ Node n = scope.nIns()>1 ? scope.mem() : null;
+ if( level==0 && n instanceof MemMergeNode merge && n.nIns()>2 ) {
sb.i().p("<TR>");
for( int m=2; m<n.nIns(); m++ )
- cell(sb,"#"+m,n.alias(m),"m"+m);
+ cell(sb,"#"+m,merge.alias(m),"m"+m);
sb.p("</TR>\n"); // End scope level
}
@@ -346,7 +346,7 @@ public class JSViewer implements AutoCloseable {
sb.i().p(scopeName).p(" -> ").p(n.uniqueName()).p(" [ style=dashed color=grey];\n");
}
// Force functions above return
- if( n instanceof ReturnNode ret && ret.inProgress() && !ret.fun().isDead() )
+ if( n instanceof ReturnNode ret && !ret.fun().isDead() )
sb.i().p(ret.ctrl().uniqueName()).p(" -> ").p(ret.fun().uniqueName()).p("[style=invis]\n");
}
}
@@ -376,8 +376,7 @@ public class JSViewer implements AutoCloseable {
}
// Memory
- if( scope.nIns()>1 ) {
- MemMergeNode n = scope.mem();
+ if( scope.nIns()>1 && scope.mem() instanceof MemMergeNode n ) {
for( int i=2; i<n.nIns(); i++ ) {
Node def = n.in(i);
while( def instanceof ScopeNode lazy )
diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java
index b2b2deca..b6ea5843 100644
--- a/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -1,6 +1,8 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
import java.util.*;
@@ -8,6 +10,7 @@ import java.util.*;
* Represents a field in a struct. This is not a Type in the type system.
*/
public class Field extends Type {
+ static final Field[] EMPTY = new Field[0];
// The pair {fieldName,type} uniquely identifies a field.
@@ -17,29 +20,25 @@ public class Field extends Type {
public Type _t;
// Unique memory alias, not sensibly part of a "type" but very convenient here.
public int _alias;
- // Field must be written to exactly once, no more, no less
+ // Field is only written in the constructor
public boolean _final;
- // Field is final in the declaration; value is the same for all instances
- // and will be moved to the class object
- public boolean _one;
private static final Ary<Field> FREE = new Ary<>(Field.class);
- private Field(String fname, Type type, int alias, boolean xfinal, boolean one ) { super(TFLD); init(fname,type,alias,xfinal,one); }
- private Field init(String fname, Type type, int alias, boolean xfinal, boolean one ) {
+ private Field(String fname, Type type, int alias, boolean xfinal ) { super(TFLD); init(fname,type,alias,xfinal); }
+ private Field init(String fname, Type type, int alias, boolean xfinal ) {
_fname = fname;
_t = type;
_alias = alias;
_final = xfinal;
- _one = one;
return this;
}
// Return a filled-in Field; either from free list or alloc new.
- private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean one ) {
- return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,one) : FREE.pop().init(fname,type,alias,xfinal,one);
+ private static Field malloc(String fname, Type type, int alias, boolean xfinal ) {
+ return FREE.isEmpty() ? new Field(fname,type,alias,xfinal) : FREE.pop().init(fname,type,alias,xfinal);
}
// Malloc-from
- public Field malloc( ) { return malloc(_fname,null,_alias,_final,_one); }
+ public Field malloc( ) { return malloc(_fname,null,_alias,_final); }
@Override Field free(Type t) {
Field f = (Field)t;
assert !f.isFree() && !f._terned;
@@ -52,26 +51,26 @@ public class Field extends Type {
@Override boolean isFree() { return _t ==null; }
// Make and intern with listed fields
- public static Field make( String fname, Type type, int alias, boolean xfinal, boolean one ) {
- Field f = malloc(fname,type,alias,xfinal,one);
+ public static Field make( String fname, Type type, int alias, boolean xfinal ) {
+ Field f = malloc(fname,type,alias,xfinal);
Field f2 = f.intern();
if( f2==f ) return f;
return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
}
public Field makeFrom( Type type ) {
- return type == _t ? this : make(_fname,type,_alias,_final,_one);
+ return type == _t ? this : make(_fname,type,_alias,_final);
}
public Field makeFrom( boolean xfinal ) {
- return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_one);
+ return xfinal == _final ? this : make(_fname,_t,_alias,xfinal);
}
// Cycle-making-breaking
void setType(Type t) { assert _t ==null; _t = t; }
- public static final Field BOT = make(" ",Type.BOTTOM,-999,true,false);
- public static final Field TEST = make("test",Type.NIL,-2,false,false);
- public static final Field TEST2 = make("test",Type.NIL,-2,true, false);
- public static final Field FLT = make("flt ",TypeFloat.F32,-2,false,false);
+ public static final Field BOT = make(" ",Type.BOTTOM,-999,false);
+ public static final Field TEST = make("test",Type.NIL,-2,false);
+ public static final Field TEST2 = make("test",Type.NIL,-2,true);
+ public static final Field FLT = make("flt ",TypeFloat.F32,-2,false);
public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); }
@Override Field xmeet( Type that ) {
@@ -81,17 +80,16 @@ public class Field extends Type {
if( fld ==BOT.dual() ) return this;
if( _fname!=fld._fname ) { assert !_fname.equals(fld._fname); return BOT; }
assert _alias==fld._alias;
- assert _one ==fld._one ;
- return make(_fname, _t.meet(fld._t ),_alias,_final | fld._final, _one);
+ return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final);
}
@Override
- Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_one); }
+ Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final); }
@Override Field rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- Field d = malloc(_fname,null,_alias,!_final,_one);
+ Field d = malloc(_fname,null,_alias,!_final);
(_dual = d)._dual = this; // Cross link duals
d._t = _t._terned ? _t.dual() : _t.rdual();
return d;
@@ -99,38 +97,52 @@ public class Field extends Type {
@Override boolean _isConstant() { return _t._isConstant(); }
@Override boolean _isFinal() { return _final && _t._isFinal(); }
- @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_one); }
- boolean isGLB2() { return _final && _t._isGLB(true); }
- Field glb2() {
- Type glb = _t._glb(true);
- return (glb== _t && _final ) ? this : make(_fname,glb,_alias,true,_one);
+ @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true); }
+ @Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
+ @Override public Type _glb(boolean mem) { return make(_fname,_t._glb(true),_alias,true); }
+ @Override Field _close( String name, HashMap<String, Type> TYPES ) { return makeFrom( _t._close(name, TYPES )); }
+
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ return makeFrom(_t._upgradeType(TYPES));
}
- @Override Field _close() { return makeFrom( _t._close()); }
// Override in subclasses
- int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_one ? 2048 : 0); }
+ int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0); }
private boolean static_eq( Field f ) {
- return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _one==f._one && _t !=null && f._t !=null;
+ return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _t !=null && f._t !=null;
}
@Override boolean eq( Type t ) {
Field f = (Field)t;
return static_eq(f) && _t ==f._t;
}
@Override boolean cycle_eq(Type t) {
+ if( this==t ) return true;
Field f = (Field)t;
return static_eq(f) && _t.cycle_eq(f._t );
}
- @Override int nkids() { return 1; }
- @Override Type at( int idx ) { return _t; }
- @Override void set( int idx, Type t ) { _t = t; }
+ @Override public int nkids() { return 1; }
+ @Override public Type at( int idx ) { return _t; }
+ @Override public void set( int idx, Type t ) { _t = t; }
+ // Tags: final/!final; +alias+name
+ @Override int TAGOFF() { return 2; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ baos.write(TAGOFFS[_type] + (_final ? 1 : 0));
+ baos.packed2(_alias);
+ baos.packed2(strs.get(_fname));
+ }
+ static Field packed( int tag, BAOS bais, String[] strs ) {
+ int alias = bais.packed2();
+ String fname = strs[bais.packed2()];
+ return malloc(fname,null,alias,(tag&1)==1);
+ }
@Override
public SB _print( SB sb, BitSet visit, boolean html ) {
- sb.p(_final?"":"!").p(_one?"$":"").p(_fname).p(":").p(_alias).p(" : ");
+ sb.p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : ");
return _t ==null ? sb.p("---") : _t.print(sb,visit,html);
}
- @Override public String str() { return (_final?"":"!")+(_one?"$":"")+_fname; }
+ @Override public String str() { return (_final?"":"!")+_fname; }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/Type.java b/src/main/java/com/seaofnodes/simple/type/Type.java
index 2d5a6d04..fd3a7c59 100644
--- a/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -1,5 +1,6 @@
package com.seaofnodes.simple.type;
+import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.util.*;
import java.util.*;
@@ -56,34 +57,38 @@ public class Type /*implements Cloneable*/ {
static final byte TXCTRL = 3; // Ctrl flow top (mini-lattice: any-xctrl-ctrl-all)
static final byte TNIL = 4; // low null of all flavors
static final byte TXNIL = 5; // high or choice null
- static final byte TSIMPLE = 6; // End of the Simple Types
+ static final byte TSIMPLE = 5; // End of the Simple Types
- static final byte TPTR = 7; // All nil-able scalar values
+ static final byte TSCALAR = 6; // Integer, float, memory pointer or function pointer
+ static final byte TPTR = 7; // All nil-able pointer values
static final byte TINT = 8; // All Integers; see TypeInteger
static final byte TFLT = 9; // All Floats ; see TypeFloat
static final byte TCONARY =10; // Constant array
static final byte TRPC =11; // Return Program Control (Return PC or RPC)
- static final byte TTUPLE =12; // Tuples; finite collections of unrelated Types, kept in parallel
- static final byte TCYCLIC =13; // Has internal pointers, needs recursive treatment
- static final byte TMEMPTR =13; // Memory pointer to a struct type
- static final byte TFUNPTR =14; // Function pointer; unique signature and code address (just a bit)
- static final byte TMEM =15; // All memory (alias 0) or A slice of memory - with specific alias
+ static final byte TCYCLIC =12; // Has internal pointers, needs recursive treatment
+ static final byte TMEMPTR =12; // Memory pointer to a struct type
+ static final byte TFUNPTR =13; // Function pointer; unique signature and code address (just a bit)
+ static final byte TMEM =14; // All memory (alias 0) or A slice of memory - with specific alias
+ static final byte TFLD =15; // Named fields in structs
static final byte TSTRUCT =16; // Structs; tuples with named fields
- static final byte TFLD =17; // Named fields in structs
+ static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel
+
+ static final byte TMAX =18;
// Basic RTTI, useful for a lot of fast tests.
public final byte _type;
public boolean _terned;
- public boolean is_simple() { return _type < TSIMPLE; }
+ public boolean is_simple() { return _type <= TSIMPLE; }
+ public boolean is_nokids() { return _type < TCYCLIC; }
private static final String[] STRS = new String[]{"Bot","Top","Ctrl","~Ctrl","null","~nil"};
- static final int[] CNTS = new int[TFLD+1];
protected Type(byte type) {
_type = type; // RTTI
_uid = (char)UID++; // A unique ID for every type
+ if( UID > 32000 )
+ type = type;
assert _uid!=0; // Overflow
- CNTS[type]++;
}
public static final Type BOTTOM = new Type( TBOT ).intern(); // ALL
@@ -97,6 +102,7 @@ public class Type /*implements Cloneable*/ {
ts.add(BOTTOM);
ts.add(CONTROL);
ts.add(NIL);
+ TypeScalar.gather(ts);
TypeNil.gather(ts);
TypePtr.gather(ts);
TypeInteger.gather(ts);
@@ -135,7 +141,7 @@ public class Type /*implements Cloneable*/ {
// Factory method which interns "this"
@SuppressWarnings("unchecked")
- <T extends Type> T intern() {
+ public <T extends Type> T intern() {
//assert check();
assert !_terned; // Do not ask for already-interned
T t2 = (T)INTERN.get(this);
@@ -216,16 +222,16 @@ public class Type /*implements Cloneable*/ {
}
// Overridden in subclasses; subclass can assume "this!=t" and java classes are same
boolean eq(Type t) { return this==t; }
- boolean cycle_eq(Type t) { assert _type < TCYCLIC; return eq(t); }
+ boolean cycle_eq(Type t) { assert is_nokids(); return _type==t._type && eq(t); }
// A pair of uids
int pid( Type that ) {
return _uid < that._uid ? (_uid<<16 | that._uid) : (that._uid<<16 | _uid);
}
// At/Set child at 'idx' to t
- Type at ( int idx ) { throw Utils.TODO(); }
- void set( int idx, Type t ) { throw Utils.TODO(); }
- int nkids() { assert _type < TTUPLE; return 0; } // Number of kids
+ public Type at( int idx ) { throw Utils.TODO(); }
+ public void set( int idx, Type t ) { throw Utils.TODO(); }
+ public int nkids() { return 0; } // Number of kids
// Clear and re-insert the basic Type INTERN table
@@ -254,6 +260,7 @@ public class Type /*implements Cloneable*/ {
// ----------------------------------------------------------
public final Type meet(Type t) {
+ assert _terned && t._terned;
// Shortcut for the self case
if( t == this ) return this;
// Same-type is always safe in the subclasses
@@ -264,7 +271,14 @@ public class Type /*implements Cloneable*/ {
// Reverse; xmeet 2nd arg is never "is_simple" and never equal to "this".
if( is_simple() ) return this.xmeet(t );
if( t.is_simple() ) return t .xmeet(this);
- return Type.BOTTOM; // Mixing 2 unrelated types
+ // TypeConAry meet its element type is OK
+ if( this instanceof TypeConAry tcon && tcon.elemT() == t._type ) return tcon.ymeet(t);
+ if( t instanceof TypeConAry tcon && tcon.elemT() == _type ) return tcon.ymeet(this);
+ // Two distinct scalar families meet at the scalar envelope.
+ if( this instanceof TypeScalar scalar0 && t instanceof TypeScalar scalar1 )
+ return scalar0.smeet(scalar1);
+ // Mixing 2 unrelated types
+ return Type.BOTTOM;
}
// Compute meet right now. Overridden in subclasses.
@@ -277,8 +291,14 @@ public class Type /*implements Cloneable*/ {
if( _type==TTOP || t._type==TBOT ) return t;
// RHS TypeNil vs NIL/XNIL
- if( _type== TNIL ) return t instanceof TypeNil ptr ? ptr.meet0() : (t._type==TXNIL ? TypePtr.PTR : BOTTOM);
- if( _type== TXNIL ) return t instanceof TypeNil ptr ? ptr.meetX() : (t._type== TNIL ? TypePtr.PTR : BOTTOM);
+ if( _type==TNIL || _type==TXNIL ) {
+ if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX();
+ if( t instanceof TypeScalar scalar ) {
+ if( t._type==TSCALAR ) return t.isHigh() ? this : t;
+ return TypeScalar.BOT;
+ }
+ return t._type==(_type==TNIL ? TXNIL : TNIL) ? TypePtr.PTR : BOTTOM;
+ }
// 'this' is only {TCTRL,TXCTRL}
// Other non-simple RHS things bottom out
@@ -316,19 +336,15 @@ public class Type /*implements Cloneable*/ {
}
// True if this "isa" t; e.g. 17 isa TypeInteger.BOT
- // Applies for pessimistic case
- public boolean isa( Type t ) { return meet(t)==t; }
+ public boolean isa( Type t ) { return meet(t)==t; }
- // True if this "isa" t up to named structures
- public boolean shallowISA( Type t ) { return isa(t); }
-
- public Type nonZero() { return TypePtr.NPTR; }
+ public Type nonZero() { return this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; }
// Make a zero version of this type, 0 for integers and null for pointers.
public Type makeZero() { return Type.NIL; }
- // Is forward-reference
- public boolean isFRef() { return false; }
+ //// Is forward-reference
+ //public boolean isFRef() { return false; }
// Cap at limits
public Type oob() { return isHigh() ? TOP : BOTTOM; }
@@ -337,7 +353,7 @@ public class Type /*implements Cloneable*/ {
// Cyclic types! Flag the start of a cyclic type by putting a sentinel in
// VISIT (and eventually clearing VISIT when done). Then do a normal
- // recursive descent visit of all types; this will accumilate types without
+ // recursive descent visit of all types; this will accumulate types without
// interning them. When done we have to visit the possibly cyclic type and
// intern the whole cycle, possibly hitting the entire cycle on a prior
// interned cycle. Any sub-part might also be interned, including whole
@@ -350,7 +366,8 @@ public class Type /*implements Cloneable*/ {
// "C0<->D0" cycle.
Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; }
- Type recurClose() {
+ Type recurClose() { return recurClose(null); }
+ Type recurClose(Type[] types) {
VISIT.remove(0L); // Just ignore the sentinel
Ary<Type> ts = new Ary<>(Type.class);
ts.addAll(VISIT.values());
@@ -374,6 +391,9 @@ public class Type /*implements Cloneable*/ {
// Upgrade the result
Type rez = INTERN.get(this);
assert rez!=null;
+ if( types!=null )
+ for( int i=0; i<types.length; i++ )
+ types[i] = INTERN.get(types[i]);
// Pass#4?: Free up any created-but-already interned
for( Type t : FREES )
@@ -415,10 +435,12 @@ public class Type /*implements Cloneable*/ {
free(this);
}
- Type free(Type free) { return this; }
+ Type free(Type free) {
+ return this;
+ }
boolean isFree() { return false; }
- <T extends Type> T delayFree(Type free) {
+ final <T extends Type> T delayFree(Type free) {
assert !free._terned;
FREES.push(free);
return (T)this;
@@ -427,7 +449,7 @@ public class Type /*implements Cloneable*/ {
final Type install() {
if( this instanceof TypeStruct ) {
Type x = _intern(); // Stop the recursion
- assert x==this;
+ if( x!=this ) return x.delayFree(this);
}
int nkids = nkids();
for( int i=0; i<nkids; i++ ) {
@@ -440,7 +462,7 @@ public class Type /*implements Cloneable*/ {
}
}
}
- return this instanceof TypeStruct ? this : _intern();
+ return this instanceof TypeStruct || _terned ? this : _intern();
}
// Strict constant values, things on the lattice centerline.
@@ -450,7 +472,7 @@ public class Type /*implements Cloneable*/ {
// Are all reachable struct Fields are final?
public final boolean isFinal() { return recurClose(recurOpen()._isFinal()); }
- boolean _isFinal() { assert _type < TCYCLIC; return true; }
+ boolean _isFinal() { assert is_nokids(); return true; }
public final Type makeRO() {
if( isFinal() ) return this;
@@ -464,6 +486,7 @@ public class Type /*implements Cloneable*/ {
boolean _isGLB(boolean mem) {
return switch(_type) {
case TBOT -> false;
+ case TSCALAR -> this==TypeScalar.BOT;
case TNIL -> false;
case TCTRL -> true;
case TXCTRL -> false;
@@ -480,9 +503,149 @@ public class Type /*implements Cloneable*/ {
}
Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; }
- Type _close() { return this; }
+ // Bulk close-over all recursive types
+ public static TypeStruct[] closeOver(TypeStruct[] ts, HashMap<String,Type> TYPES) {
+ BOTTOM.recurOpen();
+ for( TypeStruct t : ts )
+ t._close(null, TYPES);
+ for( int i=0; i<ts.length; i++ ) {
+ // Parser only tracks the mutable name of arrays, so the closeOver
+ // returns that one. However, internal types might have immutable
+ // versions.
+ String name = ts[i].isAry() ? (ts[i]._name+"!").intern() : ts[i]._name;
+ ts[i] = (TypeStruct)VISIT.get(name);
+ }
+ BOTTOM.recurClose(ts);
+ return ts;
+ }
+
+ Type _close( String name, HashMap<String, Type> TYPES ) { return this; }
public Type widen() { return this; }
+ // Replace recursively all TypeBuilders with cyclic TypeStructs
+ public Type upgradeType(HashMap<String,Type> TYPES) {
+ return recurOpen()._upgradeType(TYPES).recurClose();
+ }
+ Type _upgradeType(HashMap<String,Type> TYPES) { assert is_nokids(); return this; }
+
+
+ // Recursively gather all types
+ public void gather(HashMap<Type,Integer> types ) {
+ if( types.containsKey(this) ) return; // TEST
+ types.put(this,types.size()); // SET
+ int nkids = nkids();
+ for( int i=0; i<nkids; i++ )
+ at(i).gather(types);
+ }
+
+ // Compute serialization byte tag compression space
+ final static int[] TAGOFFS = new int[TMAX+1];
+ public static int[] TAGOFFS() {
+ if( TAGOFFS[TMAX-1]==0 )
+ // One entry per unique tag-space, using the BOT value from that
+ // tag-space except for the first tag-space which uses 1 tag for
+ // multiple Types instead of 1 Type having multiple tags. Has to
+ // be ordered to match _type enum.
+ for( Type t : new Type[]{Type.XNIL,TypeScalar.BOT,TypePtr.PTR,TypeInteger.BOT,TypeFloat.F64,TypeConAryB.ABC,TypeRPC.BOT,TypeMemPtr.BOT,TypeFunPtr.BOT,TypeMem.BOT,Field.BOT,TypeStruct.BOT,TypeTuple.BOT} )
+ TAGOFFS[t._type+1] = TAGOFFS[t._type] + t.TAGOFF();
+ assert TAGOFFS[TMAX] <= 255;
+ return TAGOFFS;
+ }
+ // Reserve 6 tags, 0-5, for plain Types
+ int TAGOFF() { assert is_simple(); return 6; }
+
+ public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert is_simple();
+ baos.write(_type);
+ }
+ static Type packed( int tag ) {
+ return switch( tag ) {
+ case TBOT -> BOTTOM;
+ case TTOP -> TOP;
+ case TCTRL -> CONTROL;
+ case TXCTRL->XCONTROL;
+ case TNIL -> NIL;
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Produce a type with null child types
+ static Type packed( BAOS bais, String[] strs ) {
+ int x = bais.read();
+ int type = Arrays.binarySearch( TAGOFFS, x );
+ if( type < 0 ) type = -(type+1)-1;
+ int off = x - TAGOFFS[type];
+ return switch( type ) {
+ case 0, 1, 2, 3, 4, 5 -> Type.packed( off );
+ case TSCALAR -> TypeScalar .packed( off );
+ case TPTR -> TypePtr .packed( off );
+ case TINT -> TypeInteger.packed( off, bais );
+ case TFLT -> TypeFloat .packed( off, bais );
+ case TCONARY -> TypeConAry .packed( off, bais );
+ case TRPC -> TypeRPC .packed( off, bais );
+ case TMEMPTR -> TypeMemPtr .packed( off, bais );
+ case TFUNPTR -> TypeFunPtr .packed( off, bais );
+ case TMEM -> TypeMem .packed( off, bais );
+ case TFLD -> Field .packed( off, bais, strs );
+ case TSTRUCT -> TypeStruct .packed( off, bais, strs );
+ case TTUPLE -> TypeTuple .packed( off, bais );
+ default -> throw Utils.TODO();
+ };
+ }
+
+ // Read a packed Type array
+ public static Type[] packed( BAOS bais, String[] strs, int ntypes, HashMap<String,Type> existingTypes,
+ GlobalBits fileAliases, GlobalBits aliases,
+ GlobalBits fileFidxs , GlobalBits fidxs ,
+ GlobalBits fileRpcs , GlobalBits rpcs ) {
+ Type[] types = new Type[ntypes];
+ // Read Types in ID# order, no children
+ for( int i=0; i<ntypes; i++ )
+ types[i] = Type.packed(bais,strs);
+ // Start possibly recursive type collection
+ BOTTOM.recurOpen();
+ // Read Types in ID# order, only children
+ for( int i=0; i<ntypes; i++ ) {
+ VISIT.put(types[i]._uid,types[i]);
+ int nkids = types[i].nkids();
+ for( int j=0; j<nkids; j++ )
+ types[i].set(j,types[bais.packed4()]);
+ }
+ // Remap file-local aliases/fidxs/rpcs into this compilation unit's
+ // local dense indexes via their global identities.
+ for( int i=0; i<ntypes; i++ ) {
+ if( types[i] instanceof Field fld ) {
+ if( fld._alias >= GlobalBits.RESERVED )
+ fld._alias = aliases.map(fileAliases,fld._alias);
+ }
+ if( types[i] instanceof TypeFunPtr tfp )
+ tfp._fidxs = remapBits(fileFidxs,fidxs,tfp._fidxs,GlobalBits.RESERVED);
+ if( types[i] instanceof TypeRPC rpc )
+ rpc._rpcs = remapBits(fileRpcs,rpcs,rpc._rpcs,GlobalBits.RESERVED);
+ if( types[i] instanceof TypeMem mem ) {
+ if( mem._alias >= GlobalBits.RESERVED )
+ mem._alias = aliases.map(fileAliases,mem._alias);
+ mem._escFs = remapBits(fileFidxs ,fidxs ,mem._escFs,GlobalBits.RESERVED);
+ mem._escAs = remapBits(fileAliases,aliases,mem._escAs,GlobalBits.RESERVED);
+ }
+ }
+
+ // Intern them all at once
+ BOTTOM.recurClose(types);
+ return types;
+ }
+
+ private static int[] remapBits(GlobalBits fileBits, GlobalBits bits, int[] fileLocals, int firstMapped ) {
+ if( fileLocals == XInt.EMPTY || fileLocals == XInt.FULL )
+ return fileLocals;
+ assert !XInt.isHigh(fileLocals);
+ int[] locals = XInt.EMPTY;
+ for( int fileLocal = XInt.next(fileLocals,-1); fileLocal >=0; fileLocal = XInt.next(fileLocals,fileLocal) )
+ locals = XInt.make(locals,fileLocal < firstMapped ? fileLocal : bits.map(fileBits,fileLocal));
+ return locals;
+ }
+
+
// ----------------------------------------------------------
// Size in bits to hold an instance of this type.
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
index c5543030..26fa4a67 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -1,54 +1,51 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
-import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Represents a constant array of primitives
*/
-public class TypeConAry<A> extends Type {
+public abstract class TypeConAry<A> extends TypeScalar {
boolean _any;
// One of byte,short,int,long,float,double array
public final A _ary;
- TypeConAry( boolean any, byte type, A ary ) { super(type); _any = any; _ary = ary; }
- public static final TypeConAry BOT = new TypeConAry(false, TINT, null).intern();
+ TypeConAry( boolean any, A ary ) { super(TCONARY); _any = any; _ary = ary; }
public static void gather(ArrayList<Type> ts) {
- ts.add(BOT);
TypeConAryB.gather(ts);
TypeConAryI.gather(ts);
}
@Override public String str() { return (_any?"~":"") + "[]"; }
- @Override TypeConAry xdual() {
- if( _ary==null )
- return new TypeConAry(!_any,_type,null);
- return this;
- }
+ @Override TypeConAry xdual() { return this; }
@Override Type xmeet(Type t) {
- if( t instanceof TypeInteger ti ) return imeet(ti);
TypeConAry ary = (TypeConAry)t; // Invariant
- if( this==BOT ) return BOT;
- if( ary ==BOT ) return BOT;
- if( this==BOT.dual() ) return ary ;
- if( ary ==BOT.dual() ) return this;
assert _ary!=ary._ary; // Already interned and this!=t
+ // Unrelated constant arrays
return elem().meet(ary.elem());
}
- Type imeet( TypeInteger ti ) {
- if( this==BOT.dual() ) return ti;
- if( this==BOT ) return BOT;
- Type elem = elem();
- if( !(elem instanceof TypeInteger) ) return BOTTOM;
- if( ti.isHigh() && elem.isa(ti.dual()) )
+
+ Type ymeet( Type t ) {
+ if( t instanceof TypeInteger ti ) {
+ // if i can isa *each* element, then maybe can keep.
+ // no good to i.isa(elem()) because fails the dual
+ boolean fail = false;
+ for( int j=0; j<len(); j++ )
+ if( !ti.isa(TypeInteger.constant(at8(j))) )
+ { fail=true; break; }
+ Type e = elem();
+ if( fail )
+ return ti.meet(e);
return this;
- return elem.meet(ti);
+ }
+ throw Utils.TODO();
}
-
@Override public boolean isHigh() { return this==TOP; }
@Override boolean _isConstant() { return true; }
@Override Type _glb(boolean mem) { return this; }
@@ -67,10 +64,24 @@ public class TypeConAry<A> extends Type {
}
return TypeInteger.make(min,max);
}
+ // Generic element _type
+ public int elemT() { return TBOT; }
public long at8(int idx) { throw Utils.TODO(); }
public int len() { throw Utils.TODO(); }
@Override public int log_size() { throw Utils.TODO(); }
- public void write( ByteArrayOutputStream baos ) { throw Utils.TODO(); }
+ public void write( BAOS baos ) { throw Utils.TODO(); }
+
+ // Reserve tags for u8 array
+ @Override int TAGOFF() { return 1; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert log_size()==0 && !_any;
+ baos.write(TAGOFFS[TCONARY]+0);
+ baos.packed4(len());
+ baos.write((byte[])_ary);
+ }
+ static TypeConAry packed( int tag, BAOS bais ) {
+ return TypeConAryB.make(bais.read(new byte[bais.packed4()]));
+ }
@Override boolean eq(Type t) {
return t instanceof TypeConAry ary && _any==ary._any && ary._ary==null;
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
index 9e5f0a2d..bd2827ed 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
@@ -1,21 +1,22 @@
package com.seaofnodes.simple.type;
+import com.seaofnodes.simple.util.BAOS;
import java.util.ArrayList;
import java.util.Arrays;
-import java.io.ByteArrayOutputStream;
public class TypeConAryB extends TypeConAry<byte[]> {
- private TypeConAryB(byte[] bs) { super(false,TINT,bs); }
+ private TypeConAryB(byte[] bs) { super(false,bs); }
public static TypeConAryB make( byte[] bs ) { return new TypeConAryB(bs).intern(); }
public static TypeConAryB make( String s ) { return make(s.getBytes()); }
static final TypeConAryB ABC = make("abc");
static final TypeConAryB ABCD = make("abcd");
public static void gather(ArrayList<Type> ts) { ts.add(ABC); ts.add(ABCD); }
+ @Override public int elemT() { return TINT; }
@Override public long at8(int idx) { return _ary[idx]; }
@Override public int len() { return _ary.length; }
@Override public int log_size() { return 0; }
@Override public String str() { return "[\""+new String(_ary)+"\"]"; }
@Override boolean eq(Type t) { return t instanceof TypeConAryB ary && Arrays.equals(_ary,ary._ary); }
@Override int hash() { return Arrays.hashCode(_ary); }
- @Override public void write( ByteArrayOutputStream baos ) { baos.write(_ary,0,_ary.length); }
+ @Override public void write( BAOS baos ) { baos.write(_ary); }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
index 8228887e..bb2ab83c 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAryI.java
@@ -4,10 +4,11 @@ import java.util.ArrayList;
import java.util.Arrays;
public class TypeConAryI extends TypeConAry<int[]> {
- private TypeConAryI(int[] bs) { super(false,TINT,bs); }
+ private TypeConAryI(int[] bs) { super(false,bs); }
public static TypeConAryI make( int[] bs ) { return new TypeConAryI(bs).intern(); }
private static final TypeConAryI I123 = make(new int[]{1,2,3});
public static void gather(ArrayList<Type> ts) { ts.add(I123); }
+ @Override public int elemT() { return TINT; }
@Override public long at8(int idx) { return _ary[idx]; }
@Override public int len() { return _ary.length; }
@Override public int log_size() { return 2; }
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
index f3de581c..f4968f28 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -1,14 +1,16 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Float Type
*/
-public class TypeFloat extends Type {
+public class TypeFloat extends TypeScalar {
// - high -64, high -32, con 0, low +32, low +64
public byte _sz;
@@ -98,6 +100,28 @@ public class TypeFloat extends Type {
@Override boolean _isConstant() { return _sz==0; }
@Override public Type makeZero() { return FZERO; }
+
+ // Reserve tags for F64,F32,constant
+ @Override int TAGOFF() { return 3; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( this==F64 ) baos.write(TAGOFFS[_type] + 0);
+ else if( this==F32 ) baos.write(TAGOFFS[_type] + 1);
+ else {
+ assert isConstant();
+ baos.write(TAGOFFS[_type] + 2);
+ baos.packed8(Double.doubleToLongBits(_con));
+ }
+ }
+
+ static Type packed( int tag, BAOS bais ) {
+ return switch( tag ) {
+ case 0 -> F64;
+ case 1 -> F32;
+ case 2 -> constant(Double.longBitsToDouble(bais.packed8()));
+ default -> throw Utils.TODO();
+ };
+ }
+
@Override
int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); }
@Override
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
index ddef9651..e2ec6263 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -1,8 +1,6 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.*;
import java.util.*;
/**
@@ -21,27 +19,25 @@ public class TypeFunPtr extends TypeNil {
// A TypeFunPtr is Signature and a set of functions.
public Type[] _sig;
public Type _ret;
- // TODO:
- boolean _open; // Extra args are true:BOTTOM, false:TOP
- // Cheesy easy implementation for a small set; 1 bit per unique function
- // within the same Type[]. Can be upgraded to a BitSet for larger classes
- // of functions. Negative means "these 63 concrete bits plus infinite
- // unknown more"
- public long _fidxs; // 63 unique functions per signature
+ boolean _open; // Extra args are true:scalar BOT, false:scalar TOP
+ // Set of functions. Bit zero is used for mutable/immutable safety. Sign
+ // bit repeats infinitely.
+ public int[] _fidxs;
private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class);
- private TypeFunPtr(byte nil, boolean open, Type[] sig, Type ret, long fidxs) { super(TFUNPTR,nil); init(nil,open,sig,ret,fidxs); }
- private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, long fidxs ) {
+ private TypeFunPtr(byte nil) { super(TFUNPTR,nil); }
+ private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
assert sig != null;
_nil = nil;
_open = open;
_sig = sig;
_ret = ret;
- _fidxs= fidxs;
+ assert _fidxs == null;
+ _fidxs= XInt.intern(fidxs);
return this;
}
- static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) {
- return FREE.isEmpty() ? new TypeFunPtr(nil,open,sig,ret,fidxs) : FREE.pop().init(nil,open,sig,ret,fidxs);
+ static TypeFunPtr malloc( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ return (FREE.isEmpty() ? new TypeFunPtr(nil) : FREE.pop()).init(nil,open,sig,ret,fidxs);
}
@Override TypeFunPtr free(Type t) {
TypeFunPtr fun = (TypeFunPtr)t;
@@ -50,55 +46,75 @@ public class TypeFunPtr extends TypeNil {
fun._ret = null;
fun._dual = null;
fun._hash = 0;
+ fun._fidxs = null;
FREE.push(fun);
return this;
}
@Override boolean isFree() { return _sig==null; }
// All fields directly listed
- public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, long fidxs ) {
+ public static TypeFunPtr make( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
TypeFunPtr fun = malloc(nil,open,sig,ret,fidxs);
TypeFunPtr f2 = fun.intern();
if( f2==fun ) return fun;
return VISIT.isEmpty() ? f2.free(fun) : f2.delayFree(fun);
}
- public static TypeFunPtr make( boolean nil, boolean open, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),open,sig,ret,-1); }
+ public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return make((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); }
+ public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) {
+ return make(nil,open,sig,ret,XInt.make(fidx));
+ }
+
@Override TypeFunPtr makeFrom( byte nil ) { return nil ==_nil ? this : make( nil,_open,_sig,_ret, _fidxs); }
public TypeFunPtr makeFrom( Type ret ) { return ret ==_ret ? this : make( _nil,_open,_sig, ret, _fidxs); }
- public TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _open, _sig,_ret,1L<<fidx ); }
-
+ public TypeFunPtr makeFrom( int fidx ) { return make1((byte)2,_open,_sig,_ret,fidx); }
+ public TypeFunPtr makeFrom( Type arg, int idx ) {
+ // Alter named arg
+ Type[] sig = _sig.clone();
+ sig[idx] = arg;
+ return make(_nil,_open,sig,_ret,_fidxs);
+ }
+ @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
public static final Type[] TEMPTY = new Type[0];
static final Type[] TINT = new Type[]{TypeInteger.BOT};
static final Type[] TINTMEM = new Type[]{TypeInteger.BOT,TypeFloat.F32};
static final Type[] TINTINT = new Type[]{TypeInteger.BOT,TypeInteger.BOT};
- public static TypeFunPtr BOT = make((byte)3,true ,TEMPTY,Type.BOTTOM,-1);
- public static TypeFunPtr TEST = make((byte)2,false,TINTMEM,TypeInteger.BOT, 1);
- public static TypeFunPtr TEST0 = make((byte)3,false,TINTMEM,TypeInteger.BOT, 3);
- public static TypeFunPtr MAIN = make((byte)3,false,TINT ,Type.BOTTOM,-1); // Main can return anything
- public static TypeFunPtr CALLOC= make((byte)3,false,TINTINT,TypeMemPtr .BOT,-1);
+ public static TypeFunPtr BOT = make((byte)3,true,TEMPTY,Type.BOTTOM, XInt.FULL);
+ public static TypeFunPtr TEST = make((byte)2,true,TINTMEM,TypeInteger.BOT, XInt.SET1);
+ public static TypeFunPtr TEST0 = make((byte)3,true,TINTMEM,TypeInteger.BOT, XInt.SET3);
+ public static TypeFunPtr MAIN = make((byte)3,true,TINT ,Type.BOTTOM, XInt.FULL); // Main can return anything
+ public static TypeFunPtr CALLOC= make((byte)3,true,TINTINT,TypeMemPtr .BOT, XInt.FULL);
public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN);ts.add(CALLOC); }
+ private static final Type[] ARG_EMPTY = new Type[0];
@Override
Type xmeet(Type t) {
TypeFunPtr that = (TypeFunPtr) t;
- // if equal, no matters.
- // if short is BOT, chop ; recurPre on short.
- // if short is TOP, copy long; recurPre on long.
- TypeFunPtr min = _sig.length < that._sig.length ? this : that;
- if( _sig.length != that._sig.length && (min._open ^ min==this) )
- return that.xmeet(this);
- // Recurse all common fields
- Type[] args = new Type[_sig.length];
- for( int i=0; i<min._sig.length; i++ )
- args[i] = _sig[i].meet(that._sig[i]);
- // Handle extra args
- for( int i=min._sig.length; i<_sig.length; i++ )
- args[i] = _sig[i];
- return make(xmeet0(that),_open | that._open, args, _ret.meet(that._ret), _fidxs | that._fidxs);
+ boolean open = _open | that._open;
+ Type def0 = this._open ? TypeScalar.BOT : TypeScalar.TOP;
+ Type def1 = that._open ? TypeScalar.BOT : TypeScalar.TOP;
+ Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
+
+ int nargs = Math.max(_sig.length,that._sig.length);
+ Type[] args = new Type[nargs];
+
+ for( int i=0; i<nargs; i++ ) {
+ Type t0 = i < this._sig.length ? this._sig[i] : def0;
+ Type t1 = i < that._sig.length ? that._sig[i] : def1;
+ args[i] = t0.meet(t1);
+ }
+
+ while( nargs>0 && args[nargs-1] == def )
+ nargs--;
+
+ if( nargs != args.length )
+ args = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(args,nargs);
+
+ return make(xmeet0(that), open, args, _ret.meet(that._ret), XInt.meet(_fidxs,that._fidxs));
}
+
static Type[] xmeet( Type[] ts0, Type[] ts1) {
if( ts0==ts1 ) return ts0;
assert ts0.length==ts1.length;
@@ -108,7 +124,7 @@ public class TypeFunPtr extends TypeNil {
return ts;
}
- @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), ~_fidxs); }
+ @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); }
static Type[] xdual(Type[] ts) {
Type[] dual = new Type[ts.length];
for( int i=0; i<ts.length; i++ )
@@ -120,7 +136,7 @@ public class TypeFunPtr extends TypeNil {
if( _dual!=null ) return dual();
assert !_terned;
Type[] sigs = new Type[_sig.length];
- TypeFunPtr d = malloc(dual0(),!_open,sigs,null,~_fidxs);
+ TypeFunPtr d = malloc(dual0(),!_open,sigs,null, XInt.dual(_fidxs));
(_dual = d)._dual = this; // Cross link duals
// Return rdual
d._ret = _ret._terned ? _ret.dual() : _ret.rdual();
@@ -130,53 +146,97 @@ public class TypeFunPtr extends TypeNil {
return d;
}
- // RHS is NIL; do not deep-dual when crossing the centerline
- @Override public Type meet0() { return _nil==3 ? this : make((byte)3,_open,_sig,_ret,_fidxs); }
+ // RHS is NIL; deep-dual when crossing the centerline
+ @Override public Type meet0() {
+ if( _nil==3 ) return this;
+ if( _nil==2 ) return make((byte)3,_open,_sig,_ret,_fidxs);
+
+ return make((byte)3,_open,_sig,_ret,_fidxs);
+ }
- @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==0); }
+ @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); }
- @Override boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); }
+ @Override boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); }
@Override boolean _isFinal() { return true; }
@Override boolean _isGLB(boolean mem) { return true; }
@Override TypeFunPtr _glb(boolean mem) { return this; }
- @Override TypeFunPtr _close() {
+
+ @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) {
Type[] sig = new Type[_sig.length];
- TypeFunPtr fun = malloc(_nil,false,sig,null,_fidxs);
+ TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs);
// Now start the recursion
- fun._ret = _ret._close();
+ fun._ret = _ret._close(name, TYPES );
for( int i=0; i<sig.length; i++ )
- sig[i] = _sig[i]._close();
+ sig[i] = _sig[i]._close(name, TYPES );
+ return fun;
+ }
+ // Replace recursively all TypeBuilders with cyclic TypeStructs
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ Type[] sig = new Type[_sig.length];
+ TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs);
+ // Now start the recursion
+ fun._ret = _ret._upgradeType(TYPES);
+ for( int i=0; i<sig.length; i++ )
+ sig[i] = _sig[i]._upgradeType(TYPES);
return fun;
}
@Override public int log_size() { return 2; } // (1<<2)==4-byte pointers
public Type arg(int i) { return _sig[i]; }
- public long fidxs() { return _fidxs; }
+ public int[] fidxs() { return _fidxs; }
public Type ret() { return _ret; }
public int nargs() { return _sig.length; }
- public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); }
+ public int fidx() { return XInt.onlyBit(_fidxs); }
public int nfcns() {
- if( _fidxs < 0 ) return Integer.MAX_VALUE; // Infinite choices
- return Long.bitCount(_fidxs);
+ if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices
+ return XInt.bitCount(_fidxs);
}
// Used to walk over type structures
- @Override int nkids() { return _sig.length+1; }
- @Override Type at( int idx ) {
+ @Override public int nkids() { return _sig.length+1; }
+ @Override public Type at( int idx ) {
return idx == _sig.length ? _ret : _sig[idx];
}
- @Override void set( int idx, Type t ) {
+ @Override public void set( int idx, Type t ) {
if( idx == _sig.length ) _ret = t;
else _sig[idx] = t;
}
+ // Reserve tags for null/not and open/close, one fidx/bitset, 0-6 args
+ // Tags 0-5 - not,close,1 fidx, 0-5 args (+fidx)
+ // Tag 6 - not ,close+fidxs+nargs
+ // Tag 7 - null,close+fidxs+nargs
+ @Override int TAGOFF() { return 8; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert _open; // Open: Passing extra args is fine, and extras ignored.
+ if( _nil==2 && nargs()<6 && nfcns() == 1 ) {
+ baos.write(TAGOFFS[_type] + nargs());
+ baos.packed2(fidx());
+ } else {
+ baos.write(TAGOFFS[_type] + 6 + (_nil==2 ? 0 : 1));
+ baos.packed1(nargs());
+ XInt.packed(baos,_fidxs);
+ }
+ }
+ static TypeFunPtr packed( int tag, BAOS bais ) {
+ if( tag < 6 ) {
+ int fidx = bais.read();
+ assert fidx < 64; // Need a larger fidx space
+ return malloc((byte)2,true,new Type[tag],null, XInt.make(fidx));
+ }
+ byte nil = (byte)(tag==6 ? 2 : 3);
+ int nargs = bais.packed1();
+ int[] fidxs = XInt.packed(bais);
+ return malloc(nil,true,new Type[nargs],null,fidxs);
+ }
+
@Override
int hash() {
- long hash = _ret.hashCode() ^ _fidxs ^ super.hash();
+ long hash = _ret.hashCode() ^ XInt.hash(_fidxs) ^ super.hash();
for( Type arg : _sig )
hash = hash*19 ^ arg.hashCode();
return Utils.fold(hash);
@@ -221,26 +281,24 @@ public class TypeFunPtr extends TypeNil {
@Override public String str() { return "{"+printFIDX()+"}"; }
+ @Override
SB _print(SB sb, BitSet visit, boolean html ) {
sb.p(x()).p("{ ");
if( _sig!=null )
for( Type t : _sig )
- sb.p(t.str()).p(" "); // Short form in signature
+ sb.p(t==null ? "---" : t.str()).p(" "); // Short form in signature
+ if( !_open ) sb.p("^ ");
_ret.print(sb.p(html ? "&rarr; " : "-> "),visit,html).p(" #");
// Print fidxs
return sb.p(printFIDX()).p("}").p(q());
}
String printFIDX() {
String tilde = isHigh() ? "~" : "";
- long fidxs = isHigh() ? ~_fidxs : _fidxs;
- String fidx = fidxs==0 ? ""
- : Long.bitCount(fidxs) == 1 ? ""+Long.numberOfTrailingZeros(fidxs)
- : fidxs == -1 ? "ALL"
- : "b"+Long.toBinaryString(fidxs); // Just some function bits
+ String fidx = XInt.str(_fidxs);
return tilde+fidx;
}
// Usage: for( long fidxs=fidxs(); fidxs!=0; fidxs=nextFIDX(fidxs) ) { int fidx = Long.numberOfTrailingZeros(fidxs); ... }
- public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
+ //public static long nextFIDX(long fidxs) { return fidxs & (fidxs-1); }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
index 97c17a34..25c3fa6f 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -1,14 +1,16 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.AryInt;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Integer Type
*/
-public class TypeInteger extends Type {
+public class TypeInteger extends TypeScalar {
/**
* Describes an integer *range* - everything from min to max; both min and
* max are inclusive. If min==max, this is a constant.
@@ -116,14 +118,17 @@ public class TypeInteger extends Type {
if( this==I16 || this==U16 ) return 1; // 1<<1 == 2 bytes
if( this==I32 || this==U32 ) return 2; // 1<<2 == 4 bytes
if( this==BOT ) return 3; // 1<<3 == 8 bytes
- if( isConstant() ) { // just const here
- if (-0xFF <= _min && _min <= 0xFF) return 0;
- else if (-0xFFFF <= _min && _min <= 0xFFFF) return 1;
- else if (-0xFFFFFFFFL <= _min && _min <= 0xFFFFFFFFL) return 2;
- else return 3;
- }
- if( -128 <= _min && _max < 128 ) return 0; // 1 byte
- throw Utils.TODO();
+ if( isConstant() ) return log(_min); // just const here
+ int lo = log(_min);
+ int hi = log(_max);
+ return Math.max(lo,hi);
+ }
+
+ private static int log(long x) {
+ if( -0xFFL <= x && x <= 0xFFL) return 0;
+ if( -0xFFFFL <= x && x <= 0xFFFFL) return 1;
+ if(-0xFFFFFFFFL <= x && x <= 0xFFFFFFFFL) return 2;
+ return 3;
}
public long value() { assert isConstant(); return _min; }
@@ -147,7 +152,6 @@ public class TypeInteger extends Type {
@Override
public Type xmeet(Type other) {
- if( other instanceof TypeConAry ary ) return ary.imeet(this);
// Invariant from caller: 'this' != 'other' and same class (TypeInteger)
TypeInteger i = (TypeInteger)other; // Contract
return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen));
@@ -160,8 +164,8 @@ public class TypeInteger extends Type {
@Override public TypeInteger nonZero() {
if( isHigh() ) return this;
if( this==ZERO ) return null; // No sane answer
- if( _min==0 ) return make(1,Math.max(_max,1)); // specifically good on BOOL
- if( _max==0 ) return make(_min,-1);
+ if( _min==0 ) return make(1,Math.max(_max,1),_widen); // specifically good on BOOL
+ if( _max==0 ) return make(_min,-1,_widen);
return this;
}
@Override public Type makeZero() { return ZERO; }
@@ -174,6 +178,39 @@ public class TypeInteger extends Type {
return isHigh() ? dual() : this;
}
+ // Reserve tags for I64,I32,U32,0,constant,generic
+ @Override int TAGOFF() { return 7; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( this==BOT ) baos.write(TAGOFFS[_type] + 0);
+ else if( this==I32 ) baos.write(TAGOFFS[_type] + 1);
+ else if( this==U32 ) baos.write(TAGOFFS[_type] + 2);
+ else if( this==BOOL) baos.write(TAGOFFS[_type] + 3);
+ else if( this==ZERO) baos.write(TAGOFFS[_type] + 4);
+ else if( isConstant() ) {
+ baos.write(TAGOFFS[_type] + 5);
+ baos.packed8(_min);
+ } else {
+ baos.write(TAGOFFS[_type] + 6);
+ baos.packed8(_min);
+ baos.packed8(_max);
+ assert -3 <= _widen && _widen <= 3;
+ baos.packed1(_widen + 3);
+ }
+ }
+
+ static Type packed( int tag, BAOS bais ) {
+ return switch( tag ) {
+ case 0 -> BOT;
+ case 1 -> I32;
+ case 2 -> U32;
+ case 3 -> BOOL;
+ case 4 -> ZERO;
+ case 5 -> constant(bais.packed8());
+ case 6 -> make(bais.packed8(),bais.packed8(),(byte)(bais.packed1()-3));
+ default -> throw Utils.TODO();
+ };
+ }
+
@Override int hash() { return Utils.fold(_min) * Utils.fold(_max) + _widen; }
@Override public boolean eq( Type t ) {
return t instanceof TypeInteger i && _min==i._min && _max==i._max && _widen==i._widen;
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 99b19c75..3bbdf1df 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -1,10 +1,9 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.*;
import java.util.ArrayList;
import java.util.BitSet;
+import java.util.HashMap;
/**
@@ -18,13 +17,35 @@ public class TypeMem extends Type {
// N means alias slice#N.
public int _alias;
public Type _t; // Memory contents, some scalar type
+ public boolean _one; // Singleton memory, corresponding to TypeMemPtr._one.
+ public boolean _clz; // Class-object singleton memory; true singletons stay constant after construction.
+ public boolean _final;// Memory is only set in the constructor
+
+ // Escaped aliases and functions - escaped into this memory. These are
+ // reachable from some chain of indirections from `_t`. Bit zero is
+ // used for mutable/immutable safety. Sign bit repeats infinitely.
+ public int[] _escFs;
+ public int[] _escAs;
+
+ static public int CNT;
private static final Ary<TypeMem> FREE = new Ary<>(TypeMem.class);
- private TypeMem(int alias, Type t) { super(TMEM); init(alias,t); }
- private TypeMem init(int alias, Type t) {
- assert alias>1 || (t==Type.TOP || t==Type.BOTTOM);
+
+ private TypeMem() { super(TMEM); CNT++; }
+ private TypeMem init(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
_alias = alias;
_t = t;
+ _one = one;
+ _clz = clz;
+ // Public bulk memory is an aggregate of otherwise-unsplit aliases;
+ // a single final bit cannot describe it. Keep the lattice-dual high
+ // form distinct, and retain finality for private singleton memory and
+ // precise alias slices.
+ _final = t!=null && alias==1 && !one && !t.isHigh() ? false : xfinal;
+
+ assert _escFs == null && _escAs == null;
+ _escFs = XInt.intern(fidxs);
+ _escAs = XInt.intern(aliases);
return this;
}
@Override TypeMem free(Type t) {
@@ -32,25 +53,59 @@ public class TypeMem extends Type {
mem._alias= -99;
mem._dual = null;
mem._hash = 0;
+ mem._t = null;
+ mem._one = false;
+ mem._clz = false;
+ mem._final = false;
+ mem._escFs = null;
+ mem._escAs = null;
FREE.push(mem);
return this;
}
@Override boolean isFree() { return _alias == -99; }
- public static TypeMem malloc(int alias, Type t) {
- return FREE.isEmpty() ? new TypeMem(alias,t) : FREE.pop().init(alias,t);
+ public static TypeMem malloc(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
+ return (FREE.isEmpty() ? new TypeMem() : FREE.pop()).init(alias,t,one,clz,xfinal,fidxs,aliases);
}
- public static TypeMem make(int alias, Type t) {
- TypeMem f = malloc(alias,t);
+ public static TypeMem make(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
+ if( fidxs ==null ) fidxs = XInt.EMPTY;
+ if( aliases==null ) aliases= XInt.EMPTY;
+ TypeMem f = malloc(alias,t,one,clz,xfinal, fidxs, aliases);
TypeMem f2 = f.intern();
if( f2==f ) return f;
return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
}
+ public static TypeMem make(int alias, Type t) { return make(alias,t,false,false,false,null,null); }
+ // Make a private memory
+ public static TypeMem makePrivate(Type t) { return make(1,t,true,false,false,escapeFIDX(XInt.EMPTY,t), escapeAlias(XInt.EMPTY,t)); }
+
+ public TypeMem makeFrom(Type t) { return make(_alias,t,_one,_clz,_final,_escFs,_escAs); }
+ public TypeMem makeFrom(int alias) { return make(alias,_t,_one,_clz,_final,_escFs,_escAs); }
+ public TypeMem makeFrom(int[] fidxes, int[] aliases) {
+ return make(_alias,_t,_one,_clz,_final,fidxes,aliases);
+ }
+ // Existing escapes plus `t` escapes
+ public TypeMem escapes(Type t) {
+ return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
+ }
+ // Existing escapes plus `t` escapes
+ public TypeMem escapesFrom(Type t) {
+ return make(_alias,_t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
+ }
+ // Existing escapes plus `t` escapes
+ public TypeMem escapesFrom(int alias, Type t) {
+ return make(alias,t,_one,_clz,_final, escapeFIDX(_escFs,t), escapeAlias(_escAs,t));
+ }
+ public TypeMem escapesAliases(Type t) {
+ return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t));
+ }
- public static final TypeMem TOP = make(1, Type.TOP );
- public static final TypeMem BOT = make(1, Type.BOTTOM);
+ public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY);
+ public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL );
+ public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY);
+ public static final TypeMem SELF_MEM = make(1, TypeStruct.BOT);
- public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); }
+ public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); ts.add(SELF_MEM); ts.add(START); }
@Override
TypeMem xmeet(Type t) {
@@ -59,50 +114,139 @@ public class TypeMem extends Type {
if( that==TOP ) return this;
if( this==BOT ) return BOT;
if( that==BOT ) return BOT;
- int alias = _alias==that._alias ? _alias : 1;
+ // Treat a alias-1 has either very-high or very-low, according to "_t"
+ int alias = _alias==that._alias ? _alias // Same-same
+ : this._alias==1 && this._t.isHigh() ? that._alias // this is high-1 alias, so take that
+ : that._alias==1 && that._t.isHigh() ? this._alias // that is high-1 alias, so take this
+ : 1;
Type mt = _t.meet(that._t);
- return make(alias,mt);
+ int[] fidxs = XInt.meet( _escFs, that._escFs );
+ int[] aliases= XInt.meet( _escAs, that._escAs );
+ // Singleton & class facts are kept only when both sides agree.
+
+ // Final is kept if either side is final, merging a final & non-final
+ // memory can be argued either way here:
+
+ // - Keep it: merging a final & non-final memory means a later Store
+ // update will be flagged as an error.
+ // - Lose it: merging a final & non-final memory means a later Load
+ // cannot be promised the memory is unchanging.
+ return make(alias, mt, _one & that._one, _clz & that._clz, _final | that._final, fidxs, aliases);
}
@Override
- Type xdual() { return _t._dual==_t ? this : malloc(_alias,_t.dual()); }
+ Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); }
@Override TypeMem rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMem d = malloc(_alias,null);
+ TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs));
(_dual = d)._dual = this; // Cross link duals
d._t = _t._terned ? _t.dual() : _t.rdual();
+ if( d._alias==1 && !d._one && !d._t.isHigh() ) d._final = false;
return d;
}
+ @Override public TypeMem makeZero() { return TOP; }
@Override public boolean isHigh() { return _t.isHigh(); }
+ @Override boolean _isConstant() { return _one && _clz && _alias!= 1 && _t._isConstant(); }
@Override public int log_size() { throw Utils.TODO(); }
@Override boolean _isFinal() { return _t._isFinal(); }
@Override boolean _isGLB(boolean mem) { return _t._isGLB(true); }
- @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true)); }
+ @Override public Type _glb(boolean mem) { return make(_alias,_t._glb(true),_one,_clz,_final,_escFs,_escAs); }
+
+ @Override TypeMem _close( String name, HashMap<String, Type> TYPES ) { return malloc(_alias,_t._close(name, TYPES ),_one,_clz,_final,_escFs,_escAs); }
+
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ return make(_alias,_t._upgradeType(TYPES),_one,_clz,_final,_escFs,_escAs);
+ }
- @Override int hash() { return 9876543 + _alias + _t.hashCode(); }
+ @Override int hash() { return 9876543 + _alias + _t.hashCode() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); }
@Override boolean eq(Type t) {
TypeMem that = (TypeMem) t; // Invariant
- return _alias == that._alias && _t == that._t;
+ return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs;
}
@Override boolean cycle_eq(Type t) {
TypeMem that = (TypeMem) t; // Invariant
- return _alias == that._alias && _t.cycle_eq(that._t);
+ return _alias == that._alias && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs && _t.cycle_eq(that._t);
}
- @Override int nkids() { return 1; }
- @Override Type at( int idx ) { return _t; }
- @Override void set( int idx, Type t ) { _t = t; }
+ @Override public int nkids() { return 1; }
+ @Override public Type at( int idx ) { return _t; }
+ @Override public void set( int idx, Type t ) { _t = t; }
+ // one tag for alias#1, one tag for generic alias.
+ // tag bit for _one, another bit for _final.
+ @Override int TAGOFF() { return 12; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ baos.write(TAGOFFS[_type]
+ + (_alias==1 ? 0 : 1)
+ + (!_final ? 0 : 2)
+ + (!_one ? 0 : (_clz ? 4 : 8)));
+ if( _alias!=1 ) {
+ // generic alias
+ assert _alias <= 255;
+ baos.packed1( _alias );
+ }
+ XInt.packed(baos,_escFs);
+ XInt.packed(baos,_escAs);
+ }
+ static TypeMem packed( int tag, BAOS bais ) {
+ int alias = (tag&1)==0 ? 1 : bais.packed1();
+ boolean xfinal= (tag&2)==2;
+ boolean one = (tag&12)!=0;
+ boolean clz = (tag&4)==4;
+ int[] escFs = XInt.packed(bais);
+ int[] escAs = XInt.packed(bais);
+ return malloc(alias,null,one,clz,xfinal,escFs,escAs);
+ }
@Override public SB _print(SB sb, BitSet visit, boolean html) {
- sb.p("#");
+ sb.p('#');
+ if( !_final ) sb.p('!'); // Mutable memory
+ if( _one ) sb.p(_clz ? 'z' : '-');
if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
- return _t.print(sb.p(_alias).p(":"),visit,html);
+ sb.p(_alias).p(":[");
+ _t.print(sb,visit,html).p(",");
+ XInt.str(sb,_escFs).p(",");
+ return XInt.str(sb,_escAs).p("]");
+ }
+
+ @Override public String str() {
+ SB sb = new SB().p('#');
+ if( !_final ) sb.p('!'); // Mutable memory
+ if( _one ) sb.p(_clz ? 'z' : '-');
+ if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT").toString();
+ sb.p(_alias).p(":[").p(_t.str()).p(",");
+ XInt.str(sb,_escFs).p(",");
+ return XInt.str(sb,_escAs).p("]").toString();
}
- @Override public String str() { return toString(); }
+
+ // Tracking bulk escaped aliases and fidxs
+
+ // Escape all FIDXs from t
+ private static int[] escapeFIDX( int[] xs, Type t ) {
+ return switch(t) {
+ case TypeMemPtr tmp -> escapeFIDX(xs,tmp._obj);
+ case TypeStruct ts -> ts._open
+ ? XInt.FULL // Open structs can have any fields
+ : XInt.meet(xs,ts.fidxs());
+ case TypeFunPtr tfp -> XInt.meet(xs,tfp.fidxs());
+ case TypeMem mem -> XInt.meet(xs,mem._escFs);
+ case Type tt -> tt == BOTTOM ? XInt.FULL : xs;
+ };
+ }
+
+ private static int[] escapeAlias( int[] xs, Type t ) {
+ return switch(t) {
+ case TypeMemPtr tmp -> escapeAlias(xs,tmp._obj);
+ case TypeStruct ts -> ts._open
+ ? XInt.FULL // Open structs can have any fields
+ : XInt.meet(xs,ts.aliases());
+ case TypeMem mem -> XInt.meet(xs,mem._escAs);
+ case Type tt -> tt == BOTTOM ? XInt.FULL : xs;
+ };
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index 689b73ce..67d80591 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -1,8 +1,9 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
import java.util.*;
/**
@@ -26,19 +27,23 @@ public class TypeMemPtr extends TypeNil {
// (TOP ,true ) - a nil
public TypeStruct _obj;
- public boolean _one; // Singleton instance
+ public boolean _one; // Singleton pointer value; the pointer itself is constant.
+ public boolean _pub; // Escaped/public pointer. False means constructor-private.
private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
- private TypeMemPtr(byte nil, TypeStruct obj, boolean one) { super(TMEMPTR,nil); init(nil,obj,one); }
- private TypeMemPtr init(byte nil, TypeStruct obj, boolean one) { _nil=nil; _obj=obj; _one=one; return this; }
+ private TypeMemPtr(byte nil, TypeStruct obj, boolean one, boolean pub) { super(TMEMPTR,nil); init(nil,obj,one,pub); }
+ private TypeMemPtr init(byte nil, TypeStruct obj, boolean one, boolean pub) { _nil=nil; _obj=obj; _one=one; _pub=pub; return this; }
// Return a filled-in TypeMemPtr; either from free list or alloc new.
- private static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one) {
- return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one) : FREE.pop().init(nil,obj,one);
+ static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one, boolean pub) {
+ return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one,pub) : FREE.pop().init(nil,obj,one,pub);
}
// All fields directly listed
public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) {
- TypeMemPtr tmp = malloc(nil, obj, one);
+ return make(nil,obj,one,true);
+ }
+ public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) {
+ TypeMemPtr tmp = malloc(nil, obj, one, pub);
TypeMemPtr t2 = tmp.intern();
if( t2==tmp ) return tmp;
return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
@@ -47,6 +52,8 @@ public class TypeMemPtr extends TypeNil {
TypeMemPtr tmp = (TypeMemPtr)t;
tmp._nil = -99;
tmp._obj = null;
+ tmp._one = false;
+ tmp._pub = true;
tmp._dual = null;
tmp._hash = 0;
FREE.push(tmp);
@@ -57,11 +64,13 @@ public class TypeMemPtr extends TypeNil {
static TypeMemPtr make(byte nil, TypeStruct obj) { return make(nil, obj, false); }
public static TypeMemPtr make (TypeStruct obj) { return make((byte)2, obj); }
public static TypeMemPtr makeNullable(TypeStruct obj) { return make((byte)3, obj); }
+ public static TypeMemPtr makePrivate (TypeStruct obj) { return make((byte)2, obj, false, false); }
- public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
- @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one); }
- public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub); }
+ public TypeMemPtr makeHigh(byte nil) { return make(nil,_obj.makeHigh(),false); }
+ @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
// An abstract pointer, pointing to either a Struct or an Array.
// Can also be null or not, so 4 choices {TOP,BOT} x {nil,not}
@@ -76,16 +85,19 @@ public class TypeMemPtr extends TypeNil {
public TypeNil xmeet(Type t) {
TypeMemPtr that = (TypeMemPtr) t;
assert !isFree() && !that.isFree();
- return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one);
+ // Can't keep _one if mixing two unequal singletons; the result is not
+ // either singleton. Once either side is public, the meet is public.
+ boolean one = _one && that._one && (_obj==that._obj || !(_obj.isConAry() || that._obj.isConAry()));
+ return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), one, _pub | that._pub);
}
@Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one); }
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub); }
@Override TypeMemPtr rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, _one);
+ TypeMemPtr d = malloc(dual0(), null, !_one, !_pub);
(_dual = d)._dual = this; // Cross link duals
d._obj = _obj._terned ? _obj.dual() : _obj.rdual();
return d;
@@ -94,50 +106,52 @@ public class TypeMemPtr extends TypeNil {
// RHS is NIL; do not deep-dual when crossing the center line
@Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj); }
- @Override boolean _isConstant() { return _one && _obj._isConstant(); }
+ @Override boolean _isConstant() { return _one; }
@Override boolean _isFinal() { return _obj._isFinal(); }
@Override TypeMemPtr _makeRO() { return makeFrom(_obj._makeRO()); }
- @Override boolean _isGLB(boolean mem) { return _obj.isGLB2(); }
+ @Override boolean _isGLB(boolean mem) { return _obj._isGLB(true); }
@Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2()); }
- @Override TypeMemPtr _close() { return makeFrom(_obj._close()); }
-
-
- // True if this "isa" t up to named structures
- @Override public boolean shallowISA( Type t ) {
- if( !(t instanceof TypeMemPtr that) ) return false;
- if( this==that ) return true;
- if( xmeet0(that)!=that._nil ) return false;
- if( _obj==that._obj ) return true;
- if( _obj._name.equals(that._obj._name) )
- return true; // Shallow, do not follow matching names, just assume ok
- throw Utils.TODO(); // return _obj.shallowISA(that._obj);
- }
+ @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub); }
- // Is forward-reference
- @Override public boolean isFRef() { return _obj.isFRef(); }
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
+ }
@Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
- @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); }
+ @Override int hash() { return _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
@Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return super.eq(ptr) && _one == ptr._one && _obj == ptr._obj;
+ return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
}
@Override boolean cycle_eq(Type t) {
if( t._type != TMEMPTR ) return false;
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj);
+ return super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
}
- @Override int nkids() { return 1; }
- @Override Type at( int idx ) { return _obj; }
- @Override void set( int idx, Type t ) { _obj = (TypeStruct)t; }
+ @Override public int nkids() { return 1; }
+ @Override public Type at( int idx ) { return _obj; }
+ @Override public void set( int idx, Type t ) { _obj = (TypeStruct)t; }
+
+ // Reserve tags for null/not, one/general and public/private
+ @Override int TAGOFF() { return 8; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert _nil>=2;
+ baos.write(TAGOFFS[_type]
+ + (_nil==2 ? 0 : 1)
+ + (!_one ? 0 : 2)
+ + (!_pub ? 0 : 4) );
+ }
+ static TypeMemPtr packed( int tag, BAOS bais ) {
+ return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4);
+ }
@Override public String str() {
if( this== NOTBOT) return "*void";
if( this== BOT) return "*void?";
- return x()+"*"+_obj.str()+q();
+ return x()+"*"+(_obj==null?"---":_obj.str())+q();
}
@Override SB _print(SB sb, BitSet visit, boolean html ) {
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/src/main/java/com/seaofnodes/simple/type/TypeNil.java
index 56d73b00..32505aa2 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeNil.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeNil.java
@@ -7,7 +7,7 @@ import java.util.ArrayList;
/**
* Nil-able Scalar types
*/
-public abstract class TypeNil extends Type {
+public abstract class TypeNil extends TypeScalar {
// 0 = high-subclass choice nil
// 1 = high-subclass no nil
// 2 = low -subclass no nil
diff --git a/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/src/main/java/com/seaofnodes/simple/type/TypePtr.java
index 75478119..3ff9765b 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypePtr.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypePtr.java
@@ -1,9 +1,12 @@
package com.seaofnodes.simple.type;
+import com.seaofnodes.simple.util.AryInt;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.BAOS;
import java.util.ArrayList;
import java.util.BitSet;
+import java.util.HashMap;
/**
* Represents a Scalar; a single register-sized value.
@@ -50,6 +53,14 @@ public class TypePtr extends TypeNil {
boolean _isGLB(boolean mem) { return this==PTR; }
@Override TypePtr _glb(boolean mem) { return PTR; }
+ // Reserve 2 tags, PTR and NPTR
+ @Override int TAGOFF() { return 2; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ assert _nil>=2;
+ baos.write(TAGOFFS[_type] + (_nil==2 ? 0 : 1));
+ }
+ static TypePtr packed( int tag ) { return tag==0 ? NPTR : PTR; }
+
@Override public String str() { return STRS[_nil]; }
@Override public SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
index a1389540..a39ebc04 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
@@ -1,32 +1,26 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
-import java.util.ArrayList;
-import java.util.BitSet;
-import java.util.HashSet;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.*;
/**
* Return Program Control or Return PC or RPC
*/
public class TypeRPC extends Type {
- // A set of CallEndNode IDs (or StopNode); commonly just one.
- // Basically a sparse bit set
- HashSet<Integer> _rpcs;
-
- // If true, invert the meaning of the bits
- boolean _any;
+ // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit
+ // XInt sets represent complements/infinite sets.
+ public int[] _rpcs;
private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class);
- private TypeRPC init(boolean any, HashSet<Integer> rpcs) { _any=any; _rpcs=rpcs; return this; }
+ private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; }
private TypeRPC() { super(TRPC); }
- private static TypeRPC malloc(boolean any, HashSet<Integer> rpcs) {
- return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs);
+ private static TypeRPC malloc(int[] rpcs) {
+ return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs);
}
- private static TypeRPC make(boolean any, HashSet<Integer> rpcs) {
- TypeRPC rpc = (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs);
+ private static TypeRPC make(int[] rpcs) {
+ TypeRPC rpc = malloc(rpcs);
TypeRPC t2 = rpc.intern();
return t2==rpc ? rpc : t2.free(rpc);
}
@@ -41,71 +35,72 @@ public class TypeRPC extends Type {
@Override boolean isFree() { return _rpcs==null; }
public static TypeRPC constant(int cend) {
- HashSet<Integer> rpcs = new HashSet<>();
- rpcs.add(cend);
- return make(false,rpcs);
+ return make(XInt.make(cend));
+ }
+
+ public int rpc() {
+ return XInt.onlyBit(_rpcs);
}
- public static final TypeRPC BOT = make(true,new HashSet<>());
+
+ public static final TypeRPC BOT = make(XInt.FULL);
private static final TypeRPC TEST2 = constant(2);
private static final TypeRPC TEST3 = constant(2);
+ public static final TypeRPC EMPTY = make(XInt.EMPTY);
public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST2); ts.add(TEST3); }
+ @Override public TypeRPC makeZero() { return EMPTY; }
+
+
@Override public String str() {
- if( _rpcs.isEmpty() )
- return _any ? "$[ALL]" : "$[]";
- if( _rpcs.size()==1 )
- for( Integer rpc : _rpcs )
- return _any ? "$[-"+rpc+"]" : "$["+rpc+"]";
- return "$["+(_any ? "-" : "")+_rpcs+"]";
+ if( _rpcs == XInt.FULL ) return "$[ALL]";
+ if( _rpcs == XInt.EMPTY ) return "$[]";
+ return "$"+XInt.str(_rpcs);
}
@Override
public TypeRPC xmeet(Type other) {
TypeRPC rpc = (TypeRPC)other;
- // If the two sets are equal, the _any must be unequal (invariant),
- // so they cancel and all bits are set.
- if( _rpcs.equals(rpc._rpcs) )
- return BOT;
- // Classic union of bit sets (which might be infinite).
- HashSet<Integer> lhs = _rpcs, rhs = rpc._rpcs;
- // Smaller on left
- if( lhs.size() > rhs.size() ) { lhs = rpc._rpcs; rhs = _rpcs; }
-
- HashSet<Integer> rpcs = new HashSet<>();
- boolean any = true;
- // If both sets are infinite, intersect.
- if( _any && rpc._any ) {
- for( Integer i : lhs ) if( rhs.contains(i) ) rpcs.add(i);
-
- } else if( !_any && !rpc._any ) {
- // if neither set is infinite, union.
- rpcs.addAll( lhs );
- rpcs.addAll( rhs );
- any = false;
- } else {
- // If one is infinite, subtract the other from it.
- HashSet<Integer> sub = _any ? rpc._rpcs : _rpcs;
- HashSet<Integer> inf = _any ? _rpcs : rpc._rpcs;
- for( Integer i : inf )
- if( inf.contains(i) && !sub.contains(i) )
- rpcs.add(i);
- }
- return make(any,rpcs);
+ return make(XInt.meet(_rpcs,rpc._rpcs));
}
- @Override Type xdual() { return malloc(!_any,_rpcs); }
+ @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); }
+ @Override Type rdual() {
+ TypeRPC d = xdual();
+ (_dual = d)._dual = this;
+ return d;
+ }
- @Override boolean _isConstant() { return !_any && _rpcs.size()==1; }
+ @Override boolean _isConstant() { return XInt.isConstant(_rpcs); }
@Override boolean _isGLB(boolean mem) { return true; }
+ // Reserve tags for ALL, singleton, generic
+ @Override int TAGOFF() { return 3; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( this==BOT ) {
+ baos.write( TAGOFFS[_type] + 0 );
+ } else if( XInt.isConstant(_rpcs) ) { // Singleton
+ baos.write(TAGOFFS[_type] + 1);
+ baos.packed2(rpc());
+ } else { // Generic
+ assert !XInt.isHigh(_rpcs); // Not serializing above-center RPC
+ baos.write(TAGOFFS[_type] + 2);
+ XInt.packed(baos,_rpcs);
+ }
+ }
+ static TypeRPC packed( int tag, BAOS bais ) {
+ if( tag==0 ) return BOT;
+ if( tag==1 ) return malloc(XInt.make(bais.packed2()));
+ return malloc(XInt.packed(bais));
+ }
+
@Override
- int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; }
+ int hash() { return XInt.hash(_rpcs); }
@Override
public boolean eq( Type t ) {
TypeRPC rpc = (TypeRPC)t; // Contract
- return _any==rpc._any && _rpcs.equals(rpc._rpcs);
+ return _rpcs==rpc._rpcs;
}
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
new file mode 100644
index 00000000..7b5d6317
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
@@ -0,0 +1,58 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashMap;
+
+/**
+ * The envelope for values which can live in an ordinary register or memory
+ * field: integers, floats, memory pointers, and function pointers.
+ *
+ * `BOT` means any scalar value and `TOP` means no scalar value. They sit
+ * strictly inside the global {@link Type#BOTTOM}/{@link Type#TOP} pair, which
+ * also includes non-values such as control and memory.
+ */
+public class TypeScalar extends Type {
+ protected TypeScalar(byte type) { super(type); }
+ private TypeScalar() { this(TSCALAR); }
+
+ public static final TypeScalar BOT = new TypeScalar().intern();
+ public static final TypeScalar TOP = BOT.dual();
+
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); }
+
+ /** Meet two different scalar families. */
+ final Type smeet(TypeScalar scalar) {
+ if( _type == TSCALAR ) return isHigh() ? scalar : this;
+ if( scalar._type == TSCALAR ) return scalar.isHigh() ? this : scalar;
+ return BOT;
+ }
+
+ @Override Type xmeet(Type t) {
+ TypeScalar scalar = (TypeScalar)t;
+ return isHigh() ? scalar : this;
+ }
+
+ @Override TypeScalar xdual() { return new TypeScalar(); }
+ @Override public boolean isHigh() { return this == TOP; }
+ @Override boolean _isGLB(boolean mem) { return this == BOT; }
+ @Override Type _glb(boolean mem) { return BOT; }
+
+ // Reserve tags for both lattice endpoints. Unlike most concrete scalar
+ // families, generic scalar TOP can survive into a serialized graph.
+ @Override int TAGOFF() { return 2; }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs) {
+ assert this == BOT || this == TOP;
+ baos.write(TAGOFFS[_type] + (this == BOT ? 0 : 1));
+ }
+ static TypeScalar packed(int tag) {
+ assert tag == 0 || tag == 1;
+ return tag == 0 ? BOT : TOP;
+ }
+
+ @Override public String str() { return isHigh() ? "~scalar" : "scalar"; }
+ @Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
+}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 1cabbe32..f51ab859 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -10,42 +10,65 @@ import java.util.*;
public class TypeStruct extends Type {
// A Struct has a name and a set of fields; the fields themselves have
- // names, types and aliases. The name has no semantic meaning, but is
- // useful for debugging.
-
- // During parsing a mid-declaration struct is flagged as a "open". It is
- // treated as having infinite fields with correct name and type BOTTOM.
+ // names, types and aliases. The name is essentially another field,
+ // immutable and type ignored (BOT).
+
+ /**
+ * `_open` and `_fref` are independent lattice coordinates.
+ *
+ * `_open` says the structural tail is not known yet. On the low side an
+ * open struct has infinitely many implicit BOT fields; its dual has an
+ * implicit TOP tail. A real declaration is therefore open while its body
+ * is being parsed:
+ *
+ * ```text
+ * struct S { int x; /* more declared fields may follow *\/ }
+ * ```
+ *
+ * `_fref` says no actual declaration has contributed to the type. The
+ * parser may still invent fields demanded by uses, leaving a useful open
+ * structural constraint:
+ *
+ * ```text
+ * S? p; p.x; // fref S { BOT x; ... }
+ * ```
+ *
+ * Meeting that constraint with a real definition ANDs `_fref` off while
+ * retaining and checking the invented field. Closing recursive types must
+ * not by itself clear `_fref`; closure discovers shape, not a declaration.
+ */
public String _name; // Struct name
- public boolean _open; // infinite fields are all true:BOTTOM, false:TOP
+ public boolean _open;
+ public boolean _fref;
public Field[] _fields;
- private TypeStruct(String name, boolean open, Field[] fields) {
- super(TSTRUCT);
+ private TypeStruct() { super(TSTRUCT); }
+ private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class);
+ private TypeStruct init(String name, boolean open, boolean fref, Field[] fields) {
+ // Check for const array element name with mutable field
+ //assert !name.startsWith("[~]") || fields.length!=2 || fields[1]._t==null || (fields[1]._final != fields[1]._t.isHigh());
_name = name;
_open = open;
+ _fref = fref;
_fields = fields;
+ return this;
}
-
- private static final Ary<TypeStruct> FREE = new Ary<>(TypeStruct.class);
// Return a filled-in TypeStruct; either from free list or alloc new.
- private static TypeStruct malloc(String name, boolean open, Field[] fields) {
- if( FREE.isEmpty() ) return new TypeStruct(name,open,fields);
- TypeStruct ts = FREE.pop();
- assert ts.isFree();
- ts._name = name;
- ts._open = open;
- ts._fields = fields;
- return ts;
+ private static TypeStruct malloc(String name, boolean open, boolean fref, Field[] fields) {
+ return (FREE.isEmpty() ? new TypeStruct() : FREE.pop()).init(name,open,fref,fields);
}
// Free ts; return this.
@Override TypeStruct free(Type t) {
TypeStruct ts = (TypeStruct)t;
assert !ts.isFree() && !ts._terned;
+ ts._name = null;
ts._fields = null;
ts._offs = null;
ts._dual = null;
ts._hash = 0;
+ ts._aliases = null;
+ ts._fidxs = null;
FREE.push(ts);
return this;
}
@@ -54,73 +77,202 @@ public class TypeStruct extends Type {
// All fields directly listed
public static TypeStruct make(String name, boolean open, Field... fields) {
- TypeStruct ts = malloc(name, open, fields);
+ return make(name,open,false,fields);
+ }
+ public static TypeStruct make(String name, boolean open, boolean fref, Field... fields) {
+ TypeStruct ts = malloc(name, open, fref, fields);
TypeStruct t2 = ts.intern();
if( t2==ts ) return ts;
return VISIT.isEmpty() ? t2.free(ts) : ts.delayFree(ts);
}
+
+ // Summary aliases, after interning. Skips private
+ // fields which need to be explicitly loaded.
+ private int[] _aliases;
+ public int[] aliases() {
+ if( _aliases != null ) return _aliases;
+ if( _fields.length==0 ) return (_aliases=XInt.EMPTY);
+ assert _terned;
+ int maxAlias = 0;
+ for( Field f : _fields )
+ maxAlias = Math.max(maxAlias,f._alias);
+ int len = XInt.idx(maxAlias)+1;
+ if( (maxAlias&31)==31 ) len++;
+ int[] xs = XInt.free(len);
+ for( Field f : _fields )
+ if( f._fname.charAt(0) != '_' && // Skip private fields.
+ !hiddenInit(f) && // Skip private allocation helper.
+ f._t != Type.TOP ) // Skip uninit fields
+ xs[XInt.idx(f._alias)] |= XInt.mask(f._alias);
+ return (_aliases=XInt.intern(xs));
+ }
+
+ // Summary fidxs, after interning. Skips private
+ // fields which need to be explicitly loaded.
+ private int[] _fidxs;
+ public int[] fidxs() {
+ if( _fidxs != null ) return _fidxs;
+ assert _terned;
+ int[] xs = XInt.EMPTY;
+ for( Field f : _fields )
+ if( f._fname.charAt(0) != '_' && // Skip private fields.
+ !hiddenInit(f) && // Skip private allocation helper.
+ f._t instanceof TypeFunPtr tfp ) // Looking for TFP
+ xs = XInt.meet(xs,tfp._fidxs);
+ return (_fidxs = xs);
+ }
+
+ // With a user constructor, the same-named function field is the hidden
+ // default <init> allocation helper, not part of the public class surface.
+ public boolean hiddenInit(Field fld) {
+ if( field("<ctor>") == null || !_name.startsWith("class:") ) return false;
+ int idx = _name.lastIndexOf('.');
+ String name = _name.substring(idx<0 ? "class:".length() : idx+1);
+ return fld._fname.equals(name);
+ }
+
// New open struct with no fields
public static TypeStruct open( String name ) { return make(name,true); }
+ public static TypeStruct forward( String name ) { return make(name,true,true); }
+ public TypeStruct defined() { return _fref ? make(_name,_open,false,_fields) : this; }
- // Array
+ // Array, variant body
public static TypeStruct makeAry(String name, TypeInteger len, int lenAlias, Type body, int bodyAlias, boolean efinal) {
return make(name,false,
- Field.make("#" ,len , lenAlias,true ,false),
- Field.make("[]",body,bodyAlias,efinal,false));
+ Field.make("#" , len, lenAlias, true ),
+ Field.make("[]",body,bodyAlias,efinal));
+ }
+ // Array, known constant body
+ public static TypeStruct makeAry(String name, int lenAlias, int bodyAlias, TypeConAry con) {
+ Type len = TypeInteger.constant(con.len());
+ return make(name,false,
+ Field.make("#" ,len , lenAlias,true),
+ Field.make("[]" ,con.elem(),bodyAlias,true),
+ Field.make("[con]",con ,bodyAlias,true));
}
+
public TypeStruct makeHigh() {
Field[] fs = new Field[_fields.length];
for( int i=0; i<_fields.length; i++ )
fs[i] = _fields[i].makeFrom(Type.TOP);
- return make(_name,false,fs);
+ return make(_name,_open,_fref,fs);
+ }
+
+ public TypeStruct makeInit() {
+ Field[] fs = new Field[_fields.length];
+ for( int i=0; i<_fields.length; i++ ) {
+ Type zero = _fields[i]._t.makeZero();
+ fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && _fields[i]._t != Type.TOP && zero.isa(_fields[i]._t) ? zero : Type.TOP);
+ }
+ return make(_name,_open,_fref,fs);
}
public TypeStruct add( Field f ) {
- assert _open && find(f._fname)==-1; // No double field names
+ assert find(f._fname)==-1; // No double field names
Field[] flds = Arrays.copyOf(_fields,_fields.length+1);
flds[_fields.length] = f;
- return make(_name,true,flds);
+ return make(_name,_open,_fref,flds);
}
public TypeStruct replace( Field f ) {
- assert !_open;
Field[] flds = Arrays.copyOf(_fields,_fields.length);
flds[find(f._fname)] = f;
- return make(_name,false,flds);
+ return make(_name,_open,_fref,flds);
+ }
+ public TypeStruct remove( int i ) {
+ Field[] flds = new Field[_fields.length-1];
+ System.arraycopy(_fields, 0,flds,0,i);
+ System.arraycopy(_fields,i+1,flds,i,flds.length-i);
+ return make(_name,_open,_fref,flds);
}
+ public TypeStruct addOrUpdate(Field fld) {
+ int idx = find(fld._fname);
+ int len = idx == -1 ? (idx=_fields.length)+1 : _fields.length;
+ Field[] flds = Arrays.copyOf(_fields,len);
+ flds[idx] = fld;
+ return make(_name,_open,_fref,flds);
+ }
public final TypeStruct close() {
- return (TypeStruct)recurOpen()._close().recurClose();
+ return (TypeStruct)recurOpen()._close(_name, null).recurClose();
}
- @Override TypeStruct _close() {
- TypeStruct ts = (TypeStruct)VISIT.get(_name);
+ @Override TypeStruct _close( String name, HashMap<String, Type> TYPES ) {
+ assert (name==null) != (TYPES==null); // One or the other
+ String namePlusFinal = isAry() ? (_name+(_fields[1]._final ? "~" : "!")).intern() : _name;
+ TypeStruct ts = (TypeStruct)VISIT.get(namePlusFinal);
if( ts!=null ) return ts;
- ts = recurPre(_name,false);
+ TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this;
+ if( base == null ) base = this;
+ boolean open = base._fref ? base._open : name != _name && name != null && _open;
+ ts = base.recurPre(namePlusFinal, _name, open, base._fref );
Field[] flds = ts._fields;
-
// Now start the recursion
for( int i=0; i<flds.length; i++ )
- flds[i].setType(_fields[i]._t._close());
+ flds[i].setType(base._fields[i]._t._close(name, TYPES));
+ // Note: this whole complex shenanigans here is because there are
+ // actually two flavors of any array (non-/final contents) but I
+ // allow the parser to represent both with the same name string.
+ if( isAry() && _fields[1]._final && !flds[1]._final ) // This is a final array variant
+ flds[1] = flds[1].makeFrom(true); // Add final to the base TYPES non-final array
+
+ return ts;
+ }
+
+ // Needs full recursive treatment; e.g.
+ // struct LL { LL? next; int !q; } // Q is mutable field.
+ // Stored into a not-mutable, so need that version also:
+ // struct LL { LL? next; int q; } // Q is NOT mutable field.
+ // Now upgrade:
+ // it is not the default version from TYPES.
+ // it is fully cyclic
+ //
+ // Evil question: has no *open* internal fields, so can I assume it is fully correct and never needs upgrade?
+ //
+ // upgrades are for well-formed types with internal ptrs to early open
+ // versions of other types, as happens in all the CoRecur tests.
+ // During the co-recur tests, can I make non-default cycles with
+ // weaker versions of other early types?
+ // Seems plausible...
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ TypeStruct base = (TypeStruct)TYPES.get(_name);
+ if( this == base ) return this; // Already the good form
+ TypeStruct ts = (TypeStruct)VISIT.get(_uid);
+ if( ts!=null ) return ts;
+ // Main work: replace open with closed
+ if( _open )
+ return base;
+ // Build recursive version
+ ts = recurPre(_uid,_name,false,_fref);
+ Field[] flds = ts._fields;
+ // Now start the recursion
+ for( int i=0; i<flds.length; i++ )
+ flds[i].setType(_fields[i]._t._upgradeType(TYPES));
return ts;
}
static final AryInt CEQUALS = new AryInt();
- public static final TypeStruct BOT = open("$STRUCT");
- public static final TypeStruct TOP = BOT.dual();
+ public static final String TOPNAME = "$TOP", BOTNAME = "$BOT";
+ public static final TypeStruct TOP = make("$TOP",false);
+ public static final TypeStruct BOT = TOP.dual();
public static final TypeStruct TEST= make("test",false,Field.TEST);
private static final TypeStruct ARY = makeAry("[]i64",TypeInteger.U32,-1,TypeInteger.BOT,-2,false);
private static final TypeStruct STR = makeAry("[]u8" ,TypeInteger.U32,-1,TypeInteger.U8 ,-4,false);
- private static final TypeStruct ABC = makeAry("[]u8",TypeInteger.constant(3),-1,TypeConAryB.ABC,-4,true);
+ private static final TypeStruct ABC = makeAry("[]u8",-1,-4,TypeConAryB.ABC);
+ // Representative unresolved forms for the exhaustive lattice tests:
+ // bare forward ref, forward ref constrained by a field use, and definition.
+ private static final TypeStruct FREF0 = forward("%FREF");
+ private static final TypeStruct FREF1 = FREF0.add(Field.make("x",Type.BOTTOM,-20,false));
+ private static final TypeStruct FDEF1 = make("%FREF",false,Field.make("x",TypeInteger.BOT,-20,false));
// A pair of self-cyclic types
- private static final TypeStruct SINT0 = open("%SINT");
- private static final TypeStruct SFLT0 = open("%SFLT");
- private static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false, false));
- private static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false, false));
- public static final TypeStruct SFLT2 = SFLT1.close();
+ private static final TypeStruct SINT0 = make("%SINT",false);
+ private static final TypeStruct SFLT0 = make("%SFLT",false);
+ public static final TypeStruct SINT1 = SINT0.add(Field.make("a", TypeInteger.U32, -1, false)).add(Field.make("s2",TypeMemPtr.make((byte)2,SFLT0),-2, false));
+ public static final TypeStruct SFLT1 = SFLT0.add(Field.make("b", TypeFloat .F32, -3, false)).add(Field.make("s1",TypeMemPtr.make((byte)2,SINT1),-4, false));
+ @Override public boolean isHigh() { return _name==TOPNAME; }
public static void gather(ArrayList<Type> ts) {
ts.add(BOT);
@@ -128,14 +280,15 @@ public class TypeStruct extends Type {
ts.add(ARY);
ts.add(STR);
ts.add(ABC);
+ ts.add(FREF0);
+ ts.add(FREF1);
+ ts.add(FDEF1);
ts.add(SINT0);
ts.add(SFLT0);
ts.add(SINT1);
ts.add(SFLT1);
- ts.add(SFLT2);
- ts.add(((TypeMemPtr)(SFLT2.field("s1")._t))._obj);
// Break cyclic init: built a struct
- Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true,true);
+ Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true);
TypeStruct scalloc = make("calloc",false,fcalloc);
ts.add(scalloc);
@@ -144,7 +297,7 @@ public class TypeStruct extends Type {
// Find field index by name
public int find(String fname) {
for( int i=0; i<_fields.length; i++ )
- if( _fields[i]._fname.equals(fname) )
+ if( _fields[i]._fname==fname )
return i;
return -1;
}
@@ -170,28 +323,27 @@ public class TypeStruct extends Type {
if( this==BOT ) return BOT;
if( that==BOT ) return BOT;
- // Within the same compilation unit, struct names are unique. If the
- // names differ, its different structs. Across many compilation units,
- // structs with the same name but different field layouts can be
- // interned... which begs the question:
- // "What is the meet of structs from two different compilation units?"
- // And the answer is: "don't ask".
- if( !_name.equals(that._name) )
- return BOT; // It's a struct; that's about all we know
-
// if equal, no matters.
// if short is BOT, chop ; recurPre on short.
// if short is TOP, copy long; recurPre on long.
TypeStruct min = _fields.length < that._fields.length ? this : that;
- if( _fields.length != that._fields.length && (min._open ^ min==this) )
+ if( _fields.length != that._fields.length && (!min._open ^ min!=this) )
return that.xmeet(this);
+ assert (_name==that._name) == (_name.equals(that._name)); // Strings are interned
+ String name =
+ that._name==TOPNAME ? _name :
+ _name==TOPNAME ? that._name :
+ that._name==_name ? _name :
+ BOTNAME;
+ if( name==BOTNAME ) return BOT;
+
// Check all other fields are sanely similar; same struct type but
// different field contents (e.g. field "age" is either 'int' or '18').
int len = Math.min(_fields.length,that._fields.length);
for( int i=0; i<len; i++ ) {
Field f0 = _fields[i], f1 = that._fields[i];
- if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._one != f1._one )
+ if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias )
return BOT;
}
@@ -205,7 +357,7 @@ public class TypeStruct extends Type {
// Setup and install the type, prior to recursing, so we can find our
// recursive self again.
- ts = recurPre(pid,_open | that._open);
+ ts = recurPre(pid,name,_open | that._open,_fref & that._fref);
// Recurse all common fields
Field[] flds = ts._fields;
@@ -226,12 +378,10 @@ public class TypeStruct extends Type {
@Override
TypeStruct xdual() {
- if( _name=="$STRUCT" )
- return malloc("$STRUCT",false,new Field[0]);
Field[] flds = new Field[_fields.length];
for( int i=0; i<_fields.length; i++ )
flds[i] = _fields[i].dual();
- return malloc(_name,!_open,flds);
+ return malloc(sdual(),!_open,!_fref,flds);
}
// Recursive dual
@@ -239,19 +389,26 @@ public class TypeStruct extends Type {
if( _dual!=null ) return dual();
assert !_terned;
Field[] flds = new Field[_fields.length];
- TypeStruct d = malloc(_name,!_open,flds);
+ TypeStruct d = malloc(sdual(),!_open,!_fref,flds);
(_dual = d)._dual = this; // Cross link duals
for( int i=0; i<_fields.length; i++ )
flds[i] = _fields[i]._terned ? _fields[i].dual() : _fields[i].rdual();
return d;
}
- // Is forward-reference
- @Override public boolean isFRef() { return _open; }
+ private String sdual() {
+ return
+ _name==TOPNAME ? BOTNAME :
+ _name==BOTNAME ? TOPNAME :
+ _name;
+ }
@Override boolean _isConstant() {
if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant
VISIT.put(_uid,this);
+ if( _open ) return false; // Infinite BOT fields
+ // Special case for constant arrays
+
// Check all fields for being constant
for( Field field : _fields )
if( !field._isConstant() )
@@ -261,69 +418,63 @@ public class TypeStruct extends Type {
// All fields are final
@Override boolean _isFinal() {
- if( _open ) return false; // May have more more non-final fields
+ if( _open ) return false; // May have more non-final fields
if( VISIT.containsKey(_uid) ) // Test: been here before?
return true; // Cycles assume final
- VISIT.put(_uid,this); // Set: dont do this again
+ VISIT.put(_uid,this); // Set: don't do this again
for( Field fld : _fields )
if( !fld._isFinal() )
return false;
return true;
}
- private TypeStruct recurPre(Object key, boolean open) {
+ private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) {
// Make a clone of original; suitable for hashing so can build
// e.g. TMPs to clone as part of cycles - but will fail the 'eq' check
// until the entire cycle is built.
Field[] flds = new Field[_fields.length];
for( int i=0; i<flds.length; i++ )
flds[i] = _fields[i].malloc(); // Blank copy, but can be hashed
- TypeStruct ts = malloc(_name, open, flds );
+ TypeStruct ts = malloc(name, open, fref, flds );
+ ts._aliases = aliases();
VISIT.put(key,ts);
return ts;
}
- // Make a read-only version
- @Override TypeStruct _makeRO() {
- // Check for already visited
- TypeStruct ts = (TypeStruct)VISIT.get(_name);
+ private interface FieldTransform { Type apply(Type t); }
+
+ private TypeStruct recurTransform(Object key, String name, boolean open, boolean fref, boolean xfinal, FieldTransform transform) {
+ TypeStruct ts = (TypeStruct)VISIT.get(key);
if( ts!=null ) return ts; // Already visited
- ts = recurPre(_name,_open); // Make a new type with blank fields
+ ts = recurPre(key,name,open,fref);
Field[] flds = ts._fields;
- for( Field fld : flds ) fld._final = true;
+ for( Field fld : flds )
+ fld._final |= xfinal;
// Now start the recursion
for( int i=0; i<flds.length; i++ )
- flds[i].setType(_fields[i]._t._makeRO());
+ flds[i].setType(transform.apply(_fields[i]._t));
return ts;
}
-
- // All fields are at GLB already
- boolean isGLB2() {
- if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB
- VISIT.put(_uid,this);
- for( Field fld : _fields )
- if( !fld.isGLB2() )
- return false;
- return true;
+ // Make a read-only version
+ @Override TypeStruct _makeRO() {
+ return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO);
}
// Keeps the same struct, but lower-bounds all fields.
public TypeStruct glb2() {
- TypeStruct ts = (TypeStruct)VISIT.get(_name);
- if( ts!=null ) return ts;
- ts = recurPre(_name,_open);
- Field[] flds = ts._fields;
- for( int i=0; i<flds.length; i++ )
- flds[i]._final = true ;
-
- // Now start the recursion
- for( int i=0; i<flds.length; i++ )
- flds[i].setType(_fields[i]._t._glb(true));
+ return recurTransform(_uid,_name,_open,false,false,t -> t._glb(true));
+ }
- return ts;
+ @Override boolean _isGLB(boolean mem) {
+ if( VISIT.containsKey(_uid) ) return true; // Cycles assume GLB
+ VISIT.put(_uid,this);
+ for( Field f : _fields )
+ if( !f._t._isGLB(mem) )
+ return false;
+ return true;
}
// log_size for a struct is not defined, unless its exactly some power of
@@ -340,10 +491,13 @@ public class TypeStruct extends Type {
// If false, always false.
// If true , maybe true, need to check recursive fields.
private boolean static_eq( TypeStruct ts ) {
- return _name.equals(ts._name) && _open==ts._open && _fields.length==ts._fields.length;
+ assert (_name==ts._name) == (_name.equals(ts._name)); // Strings are interned
+ return _name==ts._name && _open==ts._open && _fref==ts._fref && _fields.length==ts._fields.length;
}
@Override boolean eq(Type t) {
+ TypeStruct ts = (TypeStruct)t; // Invariant
+
// Recursive; so use cyclic equals
if( !VISIT.isEmpty() ) {
assert CEQUALS.isEmpty();
@@ -352,7 +506,6 @@ public class TypeStruct extends Type {
return rez;
}
// Normal equals
- TypeStruct ts = (TypeStruct)t; // Invariant
if( !static_eq(ts) ) return false;
if( _fields==ts._fields ) return true;
for( int i = 0; i < _fields.length; i++ )
@@ -380,15 +533,35 @@ public class TypeStruct extends Type {
@Override
int hash() {
- long hash = _name.hashCode() ^ (_open ? 4 : 0);
+ long hash = _name.hashCode() ^ (_open ? 4 : 0) ^ (_fref ? 8 : 0);
for( Field f : _fields )
hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias);
return Utils.fold(hash);
}
- @Override int nkids() { return _fields.length; }
- @Override Type at( int idx ) { return _fields[idx]; }
- @Override void set( int idx, Type t ) { _fields[idx] = (Field)t; }
+ @Override public int nkids() { return _fields.length; }
+ @Override public Field at( int idx ) { return _fields[idx]; }
+ @Override public void set( int idx, Type t ) { _fields[idx] = (Field)t; }
+ // Tags 0-4 - closed +#nflds (+name)
+ // Tags 5 - closed(+ nflds +name)
+ // Tags 6-10- open +#nflds (+name)
+ // Tags 11 - open (+ nflds +name)
+ @Override int TAGOFF() { return 24; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ baos.write(TAGOFFS[_type]+ (_fref ? 12 : 0) + (_open ? 6 : 0) + Math.min(5,_fields.length));
+ if( _fields.length >= 5 )
+ baos.packed2(_fields.length);
+ baos.packed2(strs.get(_name));
+ // TODO: Write aliases summary
+ }
+ static TypeStruct packed( int tag, BAOS bais, String[] strs ) {
+ boolean fref = tag >= 12;
+ if( fref ) tag -= 12;
+ int ntag = tag >= 6 ? tag-6 : tag;
+ int nflds = ntag < 5 ? ntag : bais.packed2();
+ String name = strs[bais.packed2()];
+ return malloc(name,ntag!=tag,fref,new Field[nflds]);
+ }
@Override
SB _print(SB sb, BitSet visit, boolean html ) {
@@ -401,14 +574,17 @@ public class TypeStruct extends Type {
sb.p(" {");
for( Field f : _fields )
(f._t ==null ? sb.p("---") : f._t.print(sb,visit,html)).p(f._final ? " " : " !").p(f._fname).p("; ");
- if( _open ) sb.p("... ");
+ if( _open && !_fref ) sb.p("... ");
return sb.p("}");
}
@Override public String str() { return (isFree() ? "FREE:":"")+_name; }
- public boolean isAry() { return _fields.length>=2 && _fields[_fields.length-1]._fname=="[]"; }
+ public boolean isAry() { return find("[]")!=-1; }
+ public boolean isConAry() {
+ return isAry() && _fields[1]._t instanceof TypeConAry;
+ }
public int aryBase() {
assert isAry();
@@ -446,8 +622,7 @@ public class TypeStruct extends Type {
int[] cnts = new int[5]; // Count of fields at log field size
int flen = _fields.length;
for( int i=0; i<flen; i++ )
- if( !_fields[i]._one )
- cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl)
+ cnts[_fields[i]._t.log_size()]++; // Log size is 0(byte), 1(i16/u16), 2(i32/f32), 3(i64/dbl)
int off = 0, idx = 0; // Base common struct fields go here, e.g. Mark/Klass
// Compute offsets to the start of each power-of-2 aligned fields.
int[] offs = new int[4];
@@ -458,7 +633,6 @@ public class TypeStruct extends Type {
// Assign offsets to all fields.
// Really a hidden radix sort.
for( int i=0; i<flen; i++ ) {
- if( _fields[i]._one ) continue;
int log = _fields[i]._t.log_size();
_offs[idx++] = offs[log]; // Field offset
offs[log] += 1<<log; // Next field offset at same alignment
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
index df9aba31..86f8aef1 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -1,8 +1,6 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.SB;
-import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.*;
import java.util.*;
public class TypeTuple extends Type {
@@ -31,20 +29,32 @@ public class TypeTuple extends Type {
}
@Override boolean isFree() { return _types==null; }
+ public TypeTuple makeFrom(int idx, Type tx ) {
+ if( _types[idx]==tx ) return this;
+ Type[] types = _types.clone();
+ types[idx] = tx;
+ return make(types);
+ }
+ public TypeTuple meetFrom(int idx, Type t ) { return makeFrom(idx,_types[idx].meet(t)); }
public static final TypeTuple BOT = malloc(new Type[0]).intern();
public static final TypeTuple TOP = BOT.dual();
+ // The default state, a triple of CONTROL, Memory, and Value, with bottoms
+ // for all. This is the default StopNode type, when new functions are
+ // being created and we cannot tell what the future holds. This is the
+ // default ReturnNode type, returning what the worst a function can do.
+ public static final TypeTuple STATE = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM);
+ public static final TypeTuple STOP_HIGH = make(Type.CONTROL,TypeMem.START,Type.TOP);
public static final TypeTuple TEST = make(TypeInteger.BOT,TypeMemPtr.TEST);
- public static final TypeTuple START= make(Type.CONTROL,TypeMem.TOP,TypeInteger.BOT);
- public static final TypeTuple RET = make(Type.CONTROL,TypeMem.BOT,Type.BOTTOM);
+ public static final TypeTuple DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP);
public static final TypeTuple IF_BOTH = make(Type. CONTROL,Type. CONTROL);
public static final TypeTuple IF_NEITHER = make(Type.XCONTROL,Type.XCONTROL);
public static final TypeTuple IF_TRUE = make(Type. CONTROL,Type.XCONTROL);
public static final TypeTuple IF_FALSE = make(Type.XCONTROL,Type. CONTROL);
- public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(IF_TRUE); }
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(STATE); ts.add(IF_TRUE); }
@Override
Type xmeet(Type other) {
@@ -58,7 +68,24 @@ public class TypeTuple extends Type {
@Override TypeTuple xdual() {
if( _types.length==0 ) return malloc(null);
- return malloc(TypeFunPtr.xdual(_types));
+ boolean isCon=true;
+ for( Type t : _types )
+ if( t._dual!=t )
+ // Not a constant
+ return malloc(TypeFunPtr.xdual(_types));
+ return this;
+ }
+ @Override TypeTuple rdual() {
+ if( _dual!=null ) return dual();
+ assert !_terned;
+ assert _types.length>0;
+ Type[] ts = new Type[_types.length];
+ TypeTuple d = malloc(ts);
+ (_dual = d)._dual = this; // Cross link duals
+ // Collection rdual
+ for( int i=0; i<_types.length; i++ )
+ ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual();
+ return d;
}
@Override boolean _isConstant() {
@@ -71,6 +98,13 @@ public class TypeTuple extends Type {
@Override TypeMemPtr _makeRO() { throw Utils.TODO(); }
+ @Override Type _upgradeType(HashMap<String,Type> TYPES) {
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<ts.length; i++ )
+ ts[i] = _types[i]._upgradeType(TYPES);
+ return make(ts);
+ }
+
@Override public int log_size() { throw Utils.TODO(); }
@Override public int alignment() {
assert isConstant();
@@ -80,7 +114,30 @@ public class TypeTuple extends Type {
return align;
}
- public Type ret() { assert _types.length==3; return _types[2]; }
+ public Type ctl() { assert _types.length>=3; return _types[0]; }
+ public Type ret() { assert _types.length>=3; return _types[2]; }
+ public TypeMem mem() { assert _types.length>=3;
+ return _types[1] instanceof TypeMem tmem ? tmem : (_types[1].isHigh() ? TypeMem.START : TypeMem.BOT);
+ }
+
+ @Override public int nkids() { return _types.length; }
+ @Override public Type at( int idx ) { return _types[idx]; }
+ @Override public void set( int idx, Type t ) { _types[idx] = t; }
+
+ // Reserve tags for tuples with 2,3,generic
+ @Override int TAGOFF() { return 4; }
+ @Override public void packed( BAOS baos, HashMap<String,Integer> strs ) {
+ if( _types.length==2 ) baos.write(TAGOFFS[_type] + 0);
+ else if( _types.length==3 ) baos.write(TAGOFFS[_type] + 1);
+ else if( _types.length==4 ) baos.write(TAGOFFS[_type] + 2);
+ else baos.write(TAGOFFS[_type] + 3).packed2(_types.length);
+ }
+ static TypeTuple packed( int tag, BAOS bais ) {
+ if( tag==0 ) return malloc(new Type[2]);
+ if( tag==1 ) return malloc(new Type[3]);
+ if( tag==2 ) return malloc(new Type[4]);
+ return malloc(new Type[bais.packed2()]);
+ }
@Override SB _print(SB sb, BitSet visit, boolean html ) {
if( this==TOP ) return sb.p("[TOP]");
@@ -112,8 +169,4 @@ public class TypeTuple extends Type {
return true;
}
- @Override int nkids() { return _types.length; }
- @Override Type at( int idx ) { return _types[idx]; }
- @Override void set( int idx, Type t ) { _types[idx] = t; }
-
}
diff --git a/src/main/java/com/seaofnodes/simple/type/XInt.java b/src/main/java/com/seaofnodes/simple/type/XInt.java
new file mode 100644
index 00000000..e3054a73
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/type/XInt.java
@@ -0,0 +1,266 @@
+package com.seaofnodes.simple.type;
+
+import com.seaofnodes.simple.util.*;
+import java.lang.Integer;
+import java.util.Arrays;
+
+// Stupidly overkill interned immutable int[]'s
+public abstract class XInt {
+ private static final Ary<Ary<int[]>> FREES = new Ary(Ary.class);
+ private static final Ary<Ary<int[]>> INTERNS = new Ary(Ary.class);
+ private static int PROBES, HITS, NEWS;
+
+ public static final int[] EMPTY = intern(new int[]{0, 0});
+ public static final int[] FULL = intern(new int[]{0,-1});
+ public static final int[] SET1 = make(1);
+ public static final int[] SET3 = make(SET1, 2);
+
+ static int idx(int x) { return (x>>5)+1; }
+ static int mask(int x) { return 1<<(x&31); }
+
+ // No hash set, still mutable
+ public static boolean isMutable(int[] xs) { return xs[0]==0; }
+ // Return hash code; must be set hence frozen
+ public static int hash(int[] xs) { return xs[0]; }
+
+ // High bit set (infinite 1's extension)
+ public static boolean isHigh(int[] xs) { return xs[xs.length-1] < 0; }
+
+ // Check for a bit
+ public static boolean bit(int[] xs, int x) {
+ int idx = idx(x);
+ if( idx >= xs.length )
+ return isHigh(xs);
+ return (xs[idx] & mask(x))!=0;
+ }
+
+ // Number of bits set
+ public static int bitCount(int[] xs) {
+ assert !isHigh(xs); // Dont ask if infinite
+ int cnt=0;
+ for( int i=1; i<xs.length; i++ )
+ cnt += Integer.bitCount(xs[i]);
+ return cnt;
+ }
+
+ // Largest bit
+ public static int max(int[] xs) {
+ assert !isHigh(xs);
+ return Integer.numberOfTrailingZeros(xs[xs.length-1])+((xs.length-1-1)<<5);
+ }
+
+ // Has 1 bit set
+ public static boolean isConstant(int[] xs) { return !isHigh(xs) && bitCount(xs)==1; }
+ // Return the 1 set bit or assert
+ public static int onlyBit(int[] xs) {
+ assert isConstant(xs);
+ for( int i=1; i<xs.length; i++ )
+ if( xs[i] != 0 )
+ return Integer.numberOfTrailingZeros(xs[i]) + ((i-1)<<5);
+ throw Utils.TODO("Should not reach here");
+ }
+
+ // Subset check: ys is a subset of xs
+ public static boolean subset(int[] xs, int[] ys) {
+ assert !isMutable(ys);
+ if( xs==ys ) return true;
+ if( xs==FULL ) return true;
+ if( ys==EMPTY ) return true;
+ int min = Math.min(xs.length,ys.length);
+ for( int i=1; i<min; i++ )
+ if( (xs[i] | ys[i])!=xs[i] )
+ return false;
+ if( xs.length == ys.length )
+ return true;
+ if( ys.length < xs.length )
+ return !isHigh(ys); // If ys is infinite, it'll hit one of the xs extra zeros
+ return isHigh(xs);
+ }
+
+
+ // Construct a set with 1 bit set
+ public static int[] make(int x ) { return make(EMPTY,x); }
+
+ // Add a bit
+ public static int[] make(int[] xs, int x ) {
+ assert !isMutable(xs);
+ int idx = idx(x);
+ if( idx >= xs.length && isHigh(xs) )
+ return xs; // Set in the default infinite
+ if( idx < xs.length && (mask(x) & xs[idx])!=0 )
+ return xs; // Already set
+ int len = Math.max(xs.length,idx+1);
+ if( (x&31)==31 && idx+1==len )
+ len++;
+ int[] ys = free(len);
+ System.arraycopy(xs,1,ys,1,xs.length-1);
+ ys[idx] |= mask(x);
+ return intern(ys);
+ }
+
+ // Interning, and life-cycle management
+ // Intern the array
+ public static int[] intern(int[] xs) {
+ if( !isMutable(xs) ) return xs; // Already interned
+ PROBES++;
+
+ // Trim trailing 0s or -1s, use the infinite extend bit
+ int len = xs.length;
+ while( len > 2 &&
+ ((xs[len-1]== 0 && xs[len-2]>=0) ||
+ (xs[len-1]== -1 && xs[len-2]< 0) ) )
+ len--;
+ // Replace with trimmed
+ if( len < xs.length ) {
+ var ys = free(len);
+ System.arraycopy(xs,1,ys,1,len-1);
+ xs = _free(xs,ys);
+ }
+
+ // Compute lame hash code
+ int hash = 0;
+ for( int i=1; i<len; i++ )
+ hash += xs[i];
+ if( hash == 0 ) hash = 0xCAFEBABE;
+ xs[0] = hash; // Set lame hashcode, and mark immutable
+
+ // Get length-specific INTERN table
+ var xss = INTERNS.atX(len);
+ if( xss==null )
+ INTERNS.setX( xs.length, xss = new Ary<>(int[].class));
+
+ // Probe for a hit
+ for( int[] ys : xss )
+ if( Arrays.equals(xs,ys) ) // Hit!
+ // Free new array and return old array
+ { HITS++; return _free(xs,ys); }
+
+ // Intern new array and return
+ return xss.push(xs);
+ }
+
+ // Return Ary of free int[]s of fixed size
+ private static Ary<int[]> _xss(int len) {
+ Ary<int[]> xss = FREES.atX(len);
+ if( xss==null )
+ FREES.setX(len, xss = new Ary<>(int[].class));
+ return xss;
+ }
+ // Pull free array from free list
+ static int[] free(int len) {
+ int[] xs = _xss(len).pop();
+ if( xs==null )
+ { NEWS++; xs = new int[len]; }
+ assert isMutable(xs);
+ return xs;
+ }
+ // Return ys. Put xs on free list
+ static int[] _free(int[] xs, int[] ys) {
+ Arrays.fill(xs,0); // Mutable again
+ _xss(xs.length).push(xs);
+ return ys;
+ }
+
+ // Meet: Set union
+ public static int[] meet(int[] xs, int[] ys) {
+ if( xs==ys ) return xs;
+ boolean isX=true, isY=true;
+ int min = Math.min(xs.length,ys.length);
+ for( int i=1; i<min; i++ ) {
+ if( (xs[i] | ys[i])!=xs[i] ) isX=false;
+ if( (xs[i] | ys[i])!=ys[i] ) isY=false;
+ }
+ if( !isHigh(xs) && min < ys.length )
+ isX = false; // YS has *some* more bits set, and XS does not
+ if( !isHigh(ys) && min < xs.length )
+ isY = false; // XS has *some* more bits set, and YS does not
+ if( isX ) return xs;
+ if( isY ) return ys;
+ // Get a free one
+ int[] ms = free(Math.max(xs.length,ys.length));
+ for( int i=1; i<min; i++ )
+ ms[i] = xs[i] | ys[i];
+ if( min < xs.length ) System.arraycopy(xs,min,ms,min,xs.length-min);
+ if( min < ys.length ) System.arraycopy(ys,min,ms,min,ys.length-min);
+ return intern(ms);
+ }
+
+ // Dual: flip all the bits
+ public static int[] dual(int[] xs) {
+ int[] ds = free(xs.length);
+ for( int i=1; i<xs.length; i++ )
+ ds[i] = ~xs[i];
+ return intern(ds);
+ }
+
+
+ public static void packed(BAOS baos, int[] xs) {
+ baos.packed1(xs.length);
+ for( int i=1; i<xs.length; i++ )
+ baos.packed4(xs[i]);
+ }
+ public static int[] packed(BAOS bais) {
+ int len = bais.packed1();
+ int[] xs = free(len);
+ for( int i=1; i<len; i++ )
+ xs[i] = bais.packed4();
+ return intern(xs);
+ }
+
+ // Next set bit, or -1
+ // for( int bit = XInt.next(bits,0); bit >=0; bit = XInt.next(bits,bit) ) {...bit...}
+ public static int next(int[] xs, int x) {
+ assert !isHigh(xs); // No iterating infinite bitsets
+ x++; // Skip current
+ for( ; !bit(xs,x); x++ )
+ if( idx(x)>=xs.length )
+ return -1;
+ return x;
+ }
+
+ // Remap all bits
+ public static int[] remap( int[] xs, AryInt map ) {
+ if( xs==FULL ) return xs;
+ assert !isHigh(xs);
+ int[] ys = free(xs.length);
+ for( int bit = next(xs,0); bit >=0; bit = next(xs,bit) ) {
+ int bat = map.at(bit);
+ ys[idx(bat)] |= mask(bat);
+ }
+ return intern(ys);
+ }
+
+
+
+ public static String str(int[] xs) { return str(new SB(),xs).toString(); }
+ public static SB str(SB sb, int[] xs) {
+ sb.p("[ ");
+ // Printing ranges;
+ // 1,2,3 prints 1-3
+ // 1,2,4 prints 1,2,4
+ //
+ int max = (xs.length-1)<<5;
+ int low = 0; // No low range
+ for( int i=1; i<max; i++ ) {
+ if( bit(xs,i) ) { // Set bit, start or extend range
+ if( low==0 ) low=i; // No range, start one
+ } else { // Break in sequence, ending a range
+ range(sb,low,i-1); // Print prior range, and start new one
+ low=0; // Reset range
+ }
+ }
+ // Print any partial range
+ range(sb,low,max-1).unchar();
+ if( isHigh(xs) )
+ sb.p(",...");
+ return sb.p("]");
+ }
+
+ private static SB range( SB sb, int low, int mid ) {
+ if( low==0 ) ;
+ else if( low==mid ) sb.p(low).p(',');
+ else if( low==mid-1 ) sb.p(low).p(',').p(mid).p(',');
+ else sb.p(low).p('-').p(mid).p(',');
+ return sb;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/util/BAOS.java b/src/main/java/com/seaofnodes/simple/util/BAOS.java
new file mode 100644
index 00000000..61ce7387
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/util/BAOS.java
@@ -0,0 +1,162 @@
+package com.seaofnodes.simple.util;
+
+import java.util.HashMap;
+import java.util.Arrays;
+
+// "ByteArrayOutputStream", and Input Stream and not synchronized and has
+// matching pack/unpack strategies.
+public class BAOS {
+
+ private byte[] _buf;
+ private int _len;
+
+ public BAOS() { _buf = new byte[1]; }
+ public BAOS(byte[] buf) { _buf = buf; }
+ public BAOS(byte[] buf, int x) { _buf = buf; _len = x; }
+
+ public byte[] toByteArray() {
+ return Arrays.copyOf(_buf,_len);
+ }
+
+ public byte[] buf() { return _buf; }
+ public int size() { return _len; }
+ public void set( byte[] buf, int len ) { _buf=buf; _len=len; }
+ public void clear() { _len=0; }
+
+ // -----------------------------------------------------------------------
+ // Basic read, write (expand)
+
+ private void grow(int len) {
+ while( _len+len >= _buf.length )
+ _buf = Arrays.copyOf(_buf,_buf.length*2);
+ }
+
+ // Write a byte, grow as needed; silent chop
+ public BAOS write(int b) {
+ grow(1);
+ _buf[_len++] = (byte)b; // Silent chop
+ return this;
+ }
+
+ // Read an UNSIGNED bytes at INDEX, AIOOBE if off end
+ public int read1(int idx) { return _buf[idx] & 0xFF; }
+
+ // Read an UNSIGNED byte, AIOOBE if off end
+ public int read() { return read1(_len++); }
+
+ // write 2 bytes
+ public BAOS write2( int x ) {
+ grow(2);
+ _buf[_len++] = (byte)(x>> 0);
+ _buf[_len++] = (byte)(x>> 8);
+ return this;
+ }
+
+ // Read a u16 in 2 bytes
+ public int read2(int idx) { return read1(idx) | (read1(idx+1)<<8); }
+ public int read2() { int rez = read2(_len); _len+=2; return rez; }
+
+ // write 4 bytes
+ public BAOS write4( int x ) {
+ grow(4);
+ _buf[_len++] = (byte)(x>> 0);
+ _buf[_len++] = (byte)(x>> 8);
+ _buf[_len++] = (byte)(x>>16);
+ _buf[_len++] = (byte)(x>>24);
+ return this;
+ }
+
+ // Read a i32 in 4 bytes
+ public int read4(int idx) { return read2(idx) | (read2(idx+2)<<16); }
+ public int read4() { int rez = read4(_len); _len+=4; return rez; }
+
+ // write 8 bytes
+ public BAOS write8( long x ) {
+ write4((int) x );
+ write4((int)(x>>32));
+ return this;
+ }
+
+ // Read a i64 in 8 bytes
+ public long read8(int idx) { return ((long)read4(idx) & 0xFFFFFFFFL) | ((long)read4(idx+4)<<32); }
+ public long read8() { long rez = read8(_len); _len+=8; return rez; }
+
+ // Write byte array
+ public void write( byte[] bs ) {
+ grow(bs.length);
+ System.arraycopy(bs,0,_buf,_len,bs.length);
+ _len += bs.length;
+ }
+
+ // Read/fill byte array
+ public byte[] read( byte[] bs ) {
+ System.arraycopy(_buf,_len,bs,0,bs.length);
+ _len += bs.length;
+ return bs;
+ }
+
+ // -----------------------------------------------------------------------
+ // Write a packed integer to the BAOS. Track a histogram to make more
+ // intelligent choices in the future.
+ private static final HashMap<Long,Integer> PACKED_HISTOGRAM = new HashMap<>();
+ private BAOS histo(long x) {
+ Integer cnt = PACKED_HISTOGRAM.get(x);
+ PACKED_HISTOGRAM.put(x,cnt==null ? 1 : cnt+1);
+ return this;
+ }
+
+ // Write a u8 (checked)
+ public void packed1(int x) {
+ assert 0 <= x && x <= 255;
+ histo(x).write(x);
+ }
+
+ public int packed1() { return read(); }
+
+ // Write a u16 (checked)
+ public void packed2(int x) {
+ assert 0 <= x && x <= 65535;
+ (( x <= 254 ) ? write(x) : write(0xFF).write2(x)).histo(x);
+ }
+
+ // Read a packed u16
+ public int packed2() {
+ int x = read();
+ return x==0xFF ? read2() : x;
+ }
+
+ // Write a packed i32
+ public void packed4(int x) {
+ ((-127 <= x && x <= 127 ) ? write(x) : write(-128).write4(x)).histo(x);
+ }
+
+ // Read a packed i32
+ public int packed4() {
+ int x = _buf[_len++]; // Signed byte read
+ return x== -128 ? read4() : x;
+ }
+
+ // Write a packed i64
+ public void packed8(long x) {
+ ((-127 <= x && x <= 127 ) ? write((int)x) : write(-128).write8(x)).histo(x);
+ }
+
+ // Read a packed i64
+ public long packed8() {
+ int x = _buf[_len++]; // Signed byte read
+ return x== -128 ? read8() : x;
+ }
+
+ public void packedS(String s) {
+ packed4(s.length());
+ for( int i=0; i<s.length(); i++ )
+ packed4(s.charAt(i));
+ }
+
+ public String packedS() {
+ int len = packed4();
+ String s = new String(_buf,_len,len).intern();
+ _len += len;
+ return s;
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
new file mode 100644
index 00000000..55aaa9f9
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
@@ -0,0 +1,65 @@
+package com.seaofnodes.simple.util;
+
+import java.util.Arrays;
+
+// Int-Int Hash Map
+public class IntHashMap {
+ private int[] _es;
+ private int _size;
+ private final int _sentinel;
+
+ // Pick a sentinel value; not allowed for keys or values. Sentinel is
+ // returned on a miss. Writing sentinel counts as a remove.
+ public IntHashMap(int sentinel) {
+ _sentinel = sentinel;
+ _es = new int[4];
+ Arrays.fill(_es,sentinel);
+ }
+
+ public int size() { return _size; }
+ public void clear() {
+ _size = 0;
+ Arrays.fill(_es,_sentinel);
+ }
+
+ private int cap() { return _es.length>>1; }
+
+ public int put( int key, int val ) {
+ int slot = probe(key);
+ int old = _es[(slot<<1)+1];
+ if( old == _sentinel && val != _sentinel ) _size++;
+ if( old != _sentinel && val == _sentinel ) _size--;
+ // Check cap
+ if( _size > ((cap()>>1) + (cap()>>2)) ) {
+ grow();
+ slot = probe(key);
+ }
+
+ _es[ slot<<1 ] = key;
+ _es[(slot<<1)+1] = val;
+ return old;
+ }
+
+ public int get( int key ) {
+ int slot = probe(key);
+ return _es[(slot<<1)+1];
+ }
+
+ int probe( int key ) {
+ int mask = cap()-1;
+ int slot = key & mask;
+ while( _es[slot<<1] != _sentinel && _es[slot<<1] != key )
+ slot = (slot + (key|1)) & mask;
+ return slot;
+ }
+
+ // Double underlying array size
+ private void grow() {
+ int[] old = _es;
+ _es = new int[_es.length<<1];
+ Arrays.fill(_es,_sentinel);
+ for( int i=0; i<old.length; i+=2 )
+ if( old[i] != _sentinel )
+ put(old[i],old[i+1]);
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/util/SB.java b/src/main/java/com/seaofnodes/simple/util/SB.java
index d7afd6eb..cc59f2a6 100644
--- a/src/main/java/com/seaofnodes/simple/util/SB.java
+++ b/src/main/java/com/seaofnodes/simple/util/SB.java
@@ -42,7 +42,7 @@ public final class SB {
// 4 bytes, 8 hex digits, 32 bits, Big Endian
public SB hex4(int s) { return hex2(s>>16).hex2(s); }
// 8 bytes, 16 hex digits, 64 bits, Big Endian
- public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); }
+ public SB hex8(long s) { return hex4((int)(s>>32)).hex4((int)s); }
// Fixed width field
public SB fix( int sz, String s ) {
diff --git a/src/main/smp/sys.smp b/src/main/smp/sys.smp
index cda93abe..815212ab 100644
--- a/src/main/smp/sys.smp
+++ b/src/main/smp/sys.smp
@@ -1,199 +1,9 @@
// -*- mode: java; -*-
-// top-level default import
-struct sys {
- // libc bindings
- /** https://www.man7.org/linux/man-pages/man2/<libc call>.2.html */
- struct libc {
- i32 errno = "C";
- // fd buf len -> len
- { i32 i64 u32 -> u32 } write = "C";
- // fd buf len -> len
- { i32 i64 u32 -> u32 } read = "C";
- // status
- { i32 -> i32 } _exit = "C";
- // addr len prot flags fd off -> void*
- { i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C";
- // mmap flags
- i32 PROT_EXEC = 4;
- i32 PROT_READ = 1;
- i32 PROT_WRITE= 2;
- i32 PROT_NONE = 0;
- i32 MAP_PRIVATE = 2;
- i32 MAP_ANON = 32;
- };
- struct ary {
- val copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
- for( int i=0; i<len; i++ )
- dst[i+doff] = src[i+soff];
- return dst;
- };
- };
+// top-level default standard library import
- // Ary/Vector of u8s - a resizable array of bytes.
- struct aryu8 {
- u32 !len = 0;
- u8[] !buf;
- // Grow as needed
- val _grow = { int len2 ->
- if( len+len2 < buf# ) return self;
- int newlen = buf# * 2;
- while( len+len2 >= newlen ) newlen *= 2;
- u8[] buf2 = new u8[newlen];
- sys.ary.copy(buf2,0,buf,0,buf#);
- buf = buf2;
- self;
- };
-
- // Add a character to aryu8
- val add = { u8 c ->
- _grow(1).buf[len++] = c;
- self;
- };
-
- // Add a plain array to the aryu8
- val addAry = { u8[~] ary ->
- _grow(ary#);
- sys.ary.copy(buf,len,ary,0,ary#);
- len += ary#;
- self;
- };
-
- // Remove x chars from the tail.
- val undo = { int x ->
- len = len >= x ? len-x : 0;
- self;
- };
-
- // Print ASCII number into aryu8
- val num = { i64 x ->
- if( !x ) { add('0'); return self; }
- if( x < 0 ) { add('-'); x = -x; }
- i64 ten = 1, log = 0;
- while( ten <= x ) { ten *= 10; log++; }
- for( int i=0; i<log; i++ ) {
- ten /= 10;
- i64 q = x/ten;
- u8 digit = q+'0';
- add(digit);
- x -= q*ten;
- }
- self;
- };
-
- // Write to fd, returns the number of bytes written (len).
- val write = { i32 fd -> i64 ptr = buf; sys.libc.write(fd,ptr,len); };
- };
-
-
- // Scanner; a simple string parser.
- struct Scan {
- int !x;
- u8[~] buf;
-
- // Skip whitespace
- val skip = { ->
- while( buf[x] <= ' ' )
- x++;
- return self;
- };
-
- // True if find required character, and skip it; else False.
- val match = { u8 c ->
- skip();
- if( buf[x] != c ) return false;
- x++;
- return true;
- };
-
- // Parse a simple number
- val number = { ->
- skip();
- bool neg = match('-');
- i64 n = 0;
- while( '0' <= buf[x] & buf[x] <= '9' )
- n = n*10 + (buf[x++] - '0');
- return neg ? -n : n;
- };
- };
-
- // Ary/Vector of i64s
- struct aryi64 {
- u32 !len = 0;
- i64[] !buf;
- // Grow as needed
- val _grow = { int len2 ->
- if( len+len2 < buf# ) return self;
- int newlen = buf# * 2;
- while( len+len2 >= newlen ) newlen *= 2;
- i64[] buf2 = new i64[newlen];
- for( int i=0; i<buf#; i++ )
- buf2[i] = buf[i];
- buf = buf2;
- self;
- };
-
- // Add a i64 to aryu8
- val add = { i64 c ->
- _grow(1).buf[len++] = c;
- self;
- };
-
- // Write to aryu8
- val str = { ->
- // TODO: clunky default constructor syntax
- var str = new sys.aryu8 { buf = new u8[1]; }.add('[');
- for( int i=0; i<len; i++ )
- str.num(buf[i]).add(',').add(' ');
- str.undo(2).add(']');
- };
- };
-
- // Parse a comma-separated list of numbers, wrapped with square brackets,
- // with optional whitespace e.g. "[ -17, 0,99,12456789 ] ".
- // parseAryI64 & aryi64.str are bijections modulo whitespace.
- // Returns null if the parse failed.
- val parseAryI64 = { u8[~] str ->
- sys.Scan !s = new sys.Scan{ buf = str; };
- if( !s.match('[') ) return null;
- // Array of parsed results
- var nums = new sys.aryi64{ buf=new i64[1]; };
- // Parse a list of numbers
- if( !s.match(']') ) { // Handle the empty list
- // Parse "123, " until a something else
- while( s.x < s.buf# ) {
- nums.add(s.number());
- if( !s.match(',') )
- break;
- };
- if( !s.match(']') ) return null; // Close the list
- }
- return nums;
- };
-
- struct io {
- // Print string to stdout
- val p = { u8[~] str ->
- i64 ptr = str; // cast array base to i64
- return sys.libc.write(1,ptr,str#);
- };
-
- // Return stdin as a u8[]
- val stdin = { ->
- var buf = new u8[8*1024];
- i64 ptr = buf;
- int len = 0;
- while( true ) {
- int rez = sys.libc.read(0,ptr,buf#);
- if( rez < 0 ) sys.libc._exit(sys.libc.errno);
- len += rez;
- if( rez < buf# )
- break;
- sys.libc._exit(-2); // Need to implement
- }
- // Compress
- var dst = new u8[len];
- return sys.ary.copy(dst,0,buf,0,len);
- };
- };
-};
+var libc = libc;
+var io = io;
+var aryu8 = aryu8;
+var Scan = Scan;
+var aryi64 = aryi64;
diff --git a/src/main/smp/sys/Ary.smp b/src/main/smp/sys/Ary.smp
new file mode 100644
index 00000000..0f667695
--- /dev/null
+++ b/src/main/smp/sys/Ary.smp
@@ -0,0 +1,10 @@
+// -*- mode: java; -*-
+
+// Array utilities
+
+// Copy byte array contents
+val copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
+ for( int i=0; i<len; i++ )
+ dst[i+doff] = src[i+soff];
+ return dst;
+};
diff --git a/src/main/smp/sys/Scan.smp b/src/main/smp/sys/Scan.smp
new file mode 100644
index 00000000..acf366f7
--- /dev/null
+++ b/src/main/smp/sys/Scan.smp
@@ -0,0 +1,36 @@
+// -*- mode: java; -*-
+
+// Scanner; a simple string parser.
+struct Scan {
+ int !x; // Cursor
+ u8[~] buf; // Immutable byte array, must be set in 2nd constructor
+
+ new Scan = { u8[~] b ->
+ buf = b;
+ };
+
+ // Skip whitespace
+ val skip = { ->
+ while( x < buf# && buf[x] <= ' ' )
+ x++;
+ return self;
+ };
+
+ // True if find required character, and skip it; else False.
+ val match = { u8 c ->
+ skip();
+ if( buf[x] != c ) return false;
+ x++;
+ return true;
+ };
+
+ // Parse a simple number
+ val number = { ->
+ skip();
+ bool neg = match('-');
+ i64 n = 0;
+ while( '0' <= buf[x] & buf[x] <= '9' )
+ n = n*10 + (buf[x++] - '0');
+ return neg ? -n : n;
+ };
+};
diff --git a/src/main/smp/sys/aryi64.smp b/src/main/smp/sys/aryi64.smp
new file mode 100644
index 00000000..8a154f34
--- /dev/null
+++ b/src/main/smp/sys/aryi64.smp
@@ -0,0 +1,70 @@
+// -*- mode: java; -*-
+
+// Ary/Vector of i64s
+struct aryi64 {
+ u32 !len = 0;
+ i64[] !buf;
+
+ new aryi64 = { i64[] b ->
+ buf = b;
+ };
+
+ // Grow as needed
+ val _grow = { int len2 ->
+ if( len+len2 < buf# ) return self;
+ int newlen = buf# * 2;
+ while( len+len2 >= newlen ) newlen *= 2;
+ i64[] !buf2 = new i64[newlen];
+ for( int i=0; i<buf#; i++ )
+ buf2[i] = buf[i];
+ buf = buf2;
+ self;
+ };
+
+ // Add a i64 to aryu8
+ val add = { i64 c ->
+ _grow(1).buf[len++] = c;
+ self;
+ };
+
+ // Write to aryu8
+ val str = { ->
+ // TODO: clunky default constructor syntax
+ var str = new aryu8(new u8[1]).add('[');
+ for( int i=0; i<len; i++ )
+ str.num(buf[i]).add(',').add(' ');
+ str.undo(2).add(']');
+ };
+
+ val swap = { int i, int j ->
+ i64 tmp = buf[i];
+ buf[i] = buf[j];
+ buf[j] = tmp;
+ self;
+ };
+};
+
+
+// Parse a comma-separated list of numbers, wrapped with square brackets,
+// with optional whitespace e.g. "[ -17, 0,99,12456789 ] ".
+// parseAryI64 & aryi64.str are bijections modulo whitespace.
+// Returns null if the parse failed, without giving a clue as to why.
+val parseAryI64 = { u8[~] str ->
+ Scan !s = new Scan(str);
+ if( !s.match('[') ) return null;
+ // Array of parsed results
+ var nums = new aryi64(new i64[1]);
+
+ // Parse a list of numbers
+ if( s.match(']') ) // Handle the empty list
+ return nums;
+
+ // Parse number commas "123, " until something else
+ while( s.x < s.buf# ) {
+ nums.add(s.number());
+ if( !s.match(',') )
+ break;
+ };
+ // Close the list
+ return s.match(']') ? nums : null;
+};
diff --git a/src/main/smp/sys/aryu8.smp b/src/main/smp/sys/aryu8.smp
new file mode 100644
index 00000000..0ce4bd23
--- /dev/null
+++ b/src/main/smp/sys/aryu8.smp
@@ -0,0 +1,66 @@
+// -*- mode: java; -*-
+
+// Ary/Vector of u8s - a resizable array of bytes.
+struct aryu8 {
+ u32 !len = 0;
+ u8[] !buf;
+
+ new aryu8 = { u8[] b ->
+ buf = b;
+ };
+
+ // Grow as needed
+ val _grow = { int len2 ->
+ if( len+len2 < buf# ) return self;
+ int newlen = buf# * 2;
+ while( len+len2 >= newlen ) newlen *= 2;
+ u8[] !buf2 = new u8[newlen];
+ Ary.copy(buf2,0,buf,0,buf#);
+ buf = buf2;
+ self;
+ };
+
+ val clear = { -> len = 0; self; };
+
+ // Add a character to aryu8
+ val add = { u8 c ->
+ _grow(1).buf[len++] = c;
+ self;
+ };
+
+ // Add all of a plain array to the aryu8
+ val addAry = { u8[~] ary ->
+ _grow(ary#);
+ Ary.copy(buf,len,ary,0,ary#);
+ len += ary#;
+ self;
+ };
+
+ // Remove x bytes from the tail.
+ val undo = { int x ->
+ len = len >= x ? len-x : 0;
+ self;
+ };
+
+ // Print ASCII number into aryu8
+ val num = { i64 x ->
+ if( !x ) { add('0'); return self; }
+ if( x < 0 ) { add('-'); x = -x; }
+ i64 ten = 1, log = 0;
+ while( ten <= x ) { ten *= 10; log++; }
+ for( int i=0; i<log; i++ ) {
+ ten /= 10;
+ i64 q = x/ten;
+ u8 digit = q+'0';
+ add(digit);
+ x -= q*ten;
+ }
+ self;
+ };
+
+ // Write to fd, returns the number of bytes written (len).
+ val write = { i32 fd ->
+ i64 ptr = buf; // Cast buffer pointer to i64 for native C call
+ libc.write(fd,ptr,len);
+ };
+};
diff --git a/src/main/smp/sys/io.smp b/src/main/smp/sys/io.smp
new file mode 100644
index 00000000..b94e89fa
--- /dev/null
+++ b/src/main/smp/sys/io.smp
@@ -0,0 +1,32 @@
+// -*- mode: java; -*-
+
+// Simple I/O Library
+
+// Write byte array to stdout
+val p = { u8[~] str ->
+ i64 ptr = str; // cast array base to i64
+ return libc.write(1,ptr,str#);
+};
+
+val p_noInline = { u8[~] str ->
+ i64 ptr = str; // cast array base to i64
+ return libc.write(1,ptr,str#);
+};
+
+// Return stdin as a u8[].
+val stdin = { ->
+ var buf = new u8[8*1024];
+ i64 ptr = buf;
+ int len = 0;
+ while( true ) {
+ int rez = libc.read(0,ptr,buf#);
+ if( rez < 0 ) libc._exit(libc.errno);
+ len += rez;
+ if( rez < buf# )
+ break;
+ libc._exit(-2); // Need to implement for more than 8K input
+ }
+ // Compress
+ var dst = new u8[len];
+ return Ary.copy(dst,0,buf,0,len);
+};
diff --git a/src/main/smp/sys/libc.smp b/src/main/smp/sys/libc.smp
new file mode 100644
index 00000000..42fc7313
--- /dev/null
+++ b/src/main/smp/sys/libc.smp
@@ -0,0 +1,20 @@
+// -*- mode: java; -*-
+
+// libc bindings:
+// https://www.man7.org/linux/man-pages/man2/<libc call>.2.html
+
+// fd buf len -> len
+{ i32 i64 u32 -> u32 } write = "C";
+// fd buf len -> len
+{ i32 i64 u32 -> u32 } read = "C";
+// status
+{ i32 -> i32 } _exit = "C";
+// addr len prot flags fd off -> void*
+{ i64 i64 i32 i32 i32 i32 -> i64 } mmap = "C";
+// mmap flags
+i32 PROT_EXEC = 4;
+i32 PROT_READ = 1;
+i32 PROT_WRITE= 2;
+i32 PROT_NONE = 0;
+i32 MAP_PRIVATE = 2;
+i32 MAP_ANON = 32;
diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
index de6c5a6b..0ebee2c4 100644
--- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
+++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -1,9 +1,7 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
+
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -23,24 +21,24 @@ val brain_fuck = { ->
for( int pc = 0; pc < program#; pc++ ) {
var command = program[pc];
- if (command == 62) {
+ if (command == '>') {
d++;
- } else if (command == 60) {
+ } else if (command == '<') {
d--;
- } else if (command == 43) {
+ } else if (command == '+') {
data[d]++;
- } else if (command == 45) {
+ } else if (command == '-') {
data[d]--;
- } else if (command == 46) {
+ } else if (command == '.') {
// Output a byte; increase the output array size
var old = output;
output = new u8[output# + 1];
for( int i = 0; i < old#; i++ )
output[i] = old[i];
output[old#] = data[d]; // Add the extra byte on the end
- } else if (command == 44) {
+ } else if (command == ',') {
data[d] = 42;
- } else if (command == 91) {
+ } else if (command == '[') {
if (data[d] == 0) {
for( int d = 1; d > 0; ) {
command = program[++pc];
@@ -48,7 +46,7 @@ val brain_fuck = { ->
if (command == 93) d--;
}
}
- } else if (command == 93) {
+ } else if (command == ']') {
if (data[d]) {
for( int d = 1; d > 0; ) {
command = program[--pc];
@@ -61,9 +59,9 @@ val brain_fuck = { ->
return output;
};
""";
- TestC.run(src,"brain_fuck", null, brain_fuck, 40);
+ TestC.runC(src,"brain_fuck", brain_fuck, 40);
- EvalRisc5 R5 = TestRisc5.build("brain_fuck", src, 0, 28, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "brain_fuck", 0, 28, false);
int trap = R5.step(100000);
assertEquals(0,trap);
int ptr = (int)R5.regs[com.seaofnodes.simple.node.cpus.riscv.riscv.A0];
diff --git a/src/test/java/com/seaofnodes/simple/Chapter01Test.java b/src/test/java/com/seaofnodes/simple/Chapter01Test.java
index ac9adacc..fa7fb68a 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter01Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter01Test.java
@@ -11,7 +11,7 @@ public class Chapter01Test {
@Test
public void testSimpleProgram() {
- CodeGen code = new CodeGen("return 1;").parse();
+ CodeGen code = new CodeGen("1;").parse();
Node expr = code.expr();
if( expr instanceof ConstantNode con ) {
assertEquals(code._start,con.in(0));
@@ -53,7 +53,7 @@ public class Chapter01Test {
@Test
public void testNotBad3() {
// this test used to fail in chapter 1
- assertEquals("return 12;", new CodeGen("return - -12;").parse()._stop.print());
+ assertEquals("return 12;", new CodeGen("return - -12;").parse().print());
}
@Test
@@ -62,14 +62,14 @@ public class Chapter01Test {
new CodeGen("return 100").parse();
fail();
} catch( RuntimeException e ) {
- assertEquals("Syntax error, expected ;: ",e.getMessage());
+ assertEquals("Syntax error, expected `;` but found `EOF`",e.getMessage());
}
}
@Test
public void testNotBad5() {
// this test used to fail in chapter 1
- assertEquals("return -100;", new CodeGen("return -100;").parse()._stop.print());
+ assertEquals("return -100;", new CodeGen("return -100;").parse().print());
}
@Test
@@ -88,7 +88,7 @@ public class Chapter01Test {
new CodeGen("return 1;}").parse();
fail();
} catch( RuntimeException e ) {
- assertEquals("Syntax error, unexpected: }",e.getMessage());
+ assertEquals("Syntax error, unexpected but found `}`",e.getMessage());
}
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter03Test.java b/src/test/java/com/seaofnodes/simple/Chapter03Test.java
index c4e4c91e..c96acddc 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter03Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter03Test.java
@@ -66,7 +66,7 @@ public class Chapter03Test {
new CodeGen("int a=1; int b=2; int !c=0; { int b=3; c=a+b;").parse();
fail();
} catch( RuntimeException e ) {
- assertEquals("Syntax error, expected }: ",e.getMessage());
+ assertEquals("Syntax error, expected `}` but found `EOF`",e.getMessage());
}
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter04Test.java b/src/test/java/com/seaofnodes/simple/Chapter04Test.java
index 1361d8b6..eaa959cb 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter04Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter04Test.java
@@ -63,7 +63,7 @@ public class Chapter04Test {
// Adding functions means `main` does not constant fold the incoming arg value
@Ignore @Test
public void testConstantArg() {
- CodeGen code = new CodeGen("return arg; ", TypeInteger.constant(2), 123L, true).parse();
+ CodeGen code = new CodeGen("return arg; ", TypeInteger.constant(2)).parse();
assertEquals("return 2;", code.expr().in(1).toString());
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter05Test.java b/src/test/java/com/seaofnodes/simple/Chapter05Test.java
index d1dfd216..6ac08fda 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter05Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter05Test.java
@@ -116,7 +116,10 @@ if( arg==0 )
return arg+a+b;
""");
code.parse().opto();
- assertEquals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));", code.print());
+ String ir = code.print();
+ assertTrue(ir,
+ ir.equals("return ((arg+Phi(Region,1,0))+Phi(Region,2,0));") ||
+ ir.equals("return (Phi(Region,((!=0)arg+1),0)+Phi(Region,2,0));"));
}
@Test
@@ -162,7 +165,7 @@ return a;
@Test
public void testBadNum() {
try { new CodeGen("return 1-;").parse(); fail(); }
- catch( RuntimeException e ) { assertEquals("Syntax error, expected an identifier or expression: ;",e.getMessage()); }
+ catch( RuntimeException e ) { assertEquals("Syntax error, expected `an identifier or expression` but found `;`",e.getMessage()); }
}
@Test
diff --git a/src/test/java/com/seaofnodes/simple/Chapter06Test.java b/src/test/java/com/seaofnodes/simple/Chapter06Test.java
index 1ff875b4..fdf6963f 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter06Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter06Test.java
@@ -104,7 +104,7 @@ else if( arg==3 )
else
a=5;
return a;
-""", TypeInteger.constant(2), 123L, true).parse().opto();
+""", TypeInteger.constant(2)).parse().opto();
assertEquals("return 5;", code.print());
}
@@ -124,7 +124,7 @@ else if( arg==3 )
else
a=5;
return a;
-""", TypeInteger.constant(1), 123L, true).parse().opto();
+""", TypeInteger.constant(1)).parse().opto();
assertEquals("return 3;", code.print());
}
@@ -164,7 +164,7 @@ else if( arg==3 )
else
a=5;
return a;
-""", TypeInteger.constant(1), 123L, true);
+""", TypeInteger.constant(1));
code.parse().opto();
assertEquals("return 3;", code.print());
}
@@ -185,7 +185,7 @@ else if( arg==3 )
else
a=5;
return a;
-""", TypeInteger.constant(3), 123L, true);
+""", TypeInteger.constant(3));
code.parse().opto();
assertEquals("return 4;", code.print());
}
@@ -220,7 +220,7 @@ if( arg ) {
else b = 3;
}
return a+b;
-""", TypeInteger.constant(1), 123L, true);
+""", TypeInteger.constant(1));
code.parse().opto();
assertEquals("return 4;", code.print());
}
@@ -238,7 +238,7 @@ if( arg ) {
else b = 3;
}
return a+b;
-""", TypeInteger.constant(0), 123L, true);
+""", TypeInteger.constant(0));
code.parse().opto();
assertEquals("return 1;", code.print());
}
@@ -277,7 +277,7 @@ if( arg ) {
else b = 3;
}
return a+b+c;
-""", TypeInteger.constant(1), 123L, true);
+""", TypeInteger.constant(1));
code.parse().opto();
assertEquals("return 6;", code.print());
}
@@ -297,7 +297,7 @@ if( arg ) {
else b = 3;
}
return a+b+c;
-""", TypeInteger.constant(2), 123L, true);
+""", TypeInteger.constant(2));
code.parse().opto();
assertEquals("return 5;", code.print());
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter07Test.java b/src/test/java/com/seaofnodes/simple/Chapter07Test.java
index 88554323..2ca6f8dd 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter07Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter07Test.java
@@ -17,7 +17,7 @@ while(arg < 10) {
}
return arg;
""");
- code.parse();
+ code.parse().opto();
assertEquals("return Phi(Loop,arg,(Phi_arg+1));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -106,7 +106,7 @@ while(a < 10) {
}
return a;
""");
- code.parse();
+ code.parse().opto();
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
@@ -164,7 +164,7 @@ while(a < 10) {
}
return a;
""");
- code.parse();
+ code.parse().opto();
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();
+ code.parse().opto();
assertEquals("return Phi(Loop,1,(Phi_a+3));", code.print());
assertTrue(code.ctrl() instanceof CProjNode);
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter09Test.java b/src/test/java/com/seaofnodes/simple/Chapter09Test.java
index 54a14a25..d36cdf7f 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter09Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter09Test.java
@@ -209,9 +209,9 @@ return -0+0+0;
@Test
public void testFuzz3() {
- CodeGen code = new CodeGen("int v0=0; while(0==69) while(v0) return 0; return 0;");
+ CodeGen code = new CodeGen("int v0=0; while(0==69) while(v0) return 0; return 1;");
code.parse().opto();
- assertEquals("return 0;", code.print());
+ assertEquals("return 1;", code.print());
}
@Test
@@ -255,7 +255,7 @@ return 0!=0;
int v0=0;
while(0==1) while(v0)
v0=1+v0;
-return 0;
+return 0;
""");
code.parse().opto();
assertEquals("return 0;", code.print());
diff --git a/src/test/java/com/seaofnodes/simple/Chapter10Test.java b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
index ec363239..6b237b18 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -35,15 +35,15 @@ return p-r;
@Test
public void testStruct() {
CodeGen code = new CodeGen("""
-struct Bar {
+struct _Bar {
int a;
int b;
};
-struct Foo {
+struct _Foo {
int x;
};
-Foo? foo = null;
-Bar !bar = new Bar;
+_Foo? foo = null;
+_Bar !bar = new _Bar;
bar.a = 1;
bar.a = 2;
return bar.a;
@@ -65,7 +65,7 @@ else
return v;
""");
code.parse().opto();
- assertEquals("return Vector2D;", code.print());
+ assertEquals("Stop[ return Test.Vector2D; return MEM[ 2:.x=0; 3:.y=0;]; ]", code.print());
}
@Test
@@ -77,8 +77,8 @@ struct s0 {
s0? v1=null;
int v3=v1.zAicm;
""");
- try { code.parse(); fail(); }
- catch( Exception e ) { assertEquals("Accessing unknown field 'zAicm' from 'null'",e.getMessage()); }
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Might be null accessing 'zAicm'",e.getMessage()); }
}
@Test
@@ -103,7 +103,7 @@ while (arg) {
return bar.a;
""");
code.parse().opto();
- assertEquals("return Phi(Loop,0,(Phi_a+2));", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi_a+2)); return MEM[ 2:.a=0;]; ]", code.print());
}
@Test
@@ -116,7 +116,7 @@ bar.a = 1;
return bar.a;
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Type null is not of declared type *Bar",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar",e.getMessage()); }
}
@Test
@@ -142,28 +142,28 @@ bar.a = 1;
return bar.a;
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Type null is not of declared type *Bar", e.getMessage()); }
+ catch( Exception e ) { assertEquals("Type null is not of declared type *Test.Bar", e.getMessage()); }
}
@Test
public void testIfOrNull() {
CodeGen code = new CodeGen("""
-struct Bar { int a; };
-Bar? !bar = new Bar;
+struct _Bar { int a; };
+_Bar? !bar = new _Bar;
if (arg) bar = null;
if( bar ) bar.a = 1;
-return bar;
+return bar.a;
""");
code.parse().opto();
- assertEquals("return Phi(Region,null,Bar);", code.print());
+ assertEquals("return Phi(Region,1,.a);", code.print());
}
@Test
public void testIfOrNull2() {
CodeGen code = new CodeGen(
"""
-struct Bar { int a; };
-Bar? !bar = new Bar;
+struct _Bar { int a; };
+_Bar? !bar = new _Bar;
if (arg) bar = null;
int rez = 3;
if( !bar ) rez=4;
@@ -225,7 +225,7 @@ while( i.x < i.len ) {
return sum;
""");
code.parse().opto();
- assertEquals("return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum));", code.print());
+ assertEquals("Stop[ return Phi(Loop,0,(Phi(Loop,0,(Phi_x+1))+Phi_sum)); return MEM[ 2:.x=0; 3:.len=0;]; ]", code.print());
}
@@ -244,44 +244,63 @@ while(arg) {
return ret;
""");
code.parse().opto();
- assertEquals("return Phi(Loop,s0,Phi(Region,s0,Phi_ret));", code.print());
+ assertEquals("Stop[ return Phi(Loop,Test.s0,Phi(Region,Test.s0,Phi_ret)); return MEM[ 2:.v0=0;]; ]", code.print());
}
@Test
public void test2() {
CodeGen code = new CodeGen("""
-struct s0 {int v0;};
-s0 !ret = new s0;
-s0 !v0 = new s0;
+struct _s0 {int v0;};
+_s0 !ret = new _s0;
+_s0 !v0 = new _s0;
while(arg) {
v0.v0 = arg;
arg = arg-1;
if (arg==5) ret=v0;
-
}
-return ret;
+return ret.v0;
""");
code.parse().opto();
- assertEquals("return Phi(Loop,s0,Phi(Region,s0,Phi_ret));", code.print());
+ assertEquals("return .v0;", code.print());
}
@Test
public void test3() {
CodeGen code = new CodeGen("""
-struct s0 {int v0;};
-s0 !ret = new s0;
+struct _s0 {int v0;};
+_s0 !ret = new _s0;
while(arg < 10) {
- s0 !v0 = new s0;
- if (arg == 5) ret=v0;
+ _s0 !v1 = new _s0;
+ if (arg == 5) ret=v1;
arg = arg + 1;
}
-return ret;
+return ret.v0;
""");
code.parse().opto();
- assertEquals("return Phi(Loop,s0,Phi(Region,s0,Phi_ret));", code.print());
+ assertEquals("return .v0;", code.print());
}
+
+
+ @Test
+ public void testModGlobalInConstructor() {
+ CodeGen code = new CodeGen(
+"""
+int cnt = 1;
+// cnt is incremented exactly once, it is not part of s0 constructor
+// hence s0.x is always 1, and cnt remains at 2
+struct s0 { int x = cnt++; };
+s0 a = new s0();
+s0 b = new s0();
+// a.x==1, b.x==1, cnt==2
+return a.x * 100 + b.x * 10 + cnt;
+""");
+ code.parse().iter().opto();
+ assertEquals("Stop[ return 112; return MEM[ 2:___ 3:.x=1;]; ]", code.print());
+ }
+
+
@Test
public void testBug3() {
CodeGen code = new CodeGen("""
@@ -290,7 +309,7 @@ return new s0;
int v0=null.f0;
""");
try { code.parse(); fail(); }
- catch( Exception e ) { assertEquals("Syntax error, expected ;: .",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `;` but found `.`",e.getMessage()); }
}
@Test
@@ -320,8 +339,8 @@ if(0) return 0;
else return new s0;
if(new s0.f0) return 0;
""");
- code.parse().opto();
- assertEquals("return s0;", code.print());
+ code.parse().iter().opto().typeCheck();
+ assertEquals("Stop[ return Test.s0; return MEM[ 2:.f0=0;]; ]", code.print());
}
@Test
@@ -349,7 +368,7 @@ s0 v1 = v0;
return v1;
""");
code.parse().opto();
- assertEquals("return s0;", code.print());
+ assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:.f0=0;]; ]", code.print());
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java
index 99aa9579..ab730b45 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter11Test.java
@@ -44,13 +44,30 @@ return primeCount;
}
@Test
+ public void testClassField() {
+ CodeGen code = new CodeGen(
+"""
+if( arg < 2 ) return 0;
+int primeCount = 1;
+while( arg-- )
+ primeCount++;
+return primeCount;
+""");
+ code.driver(CodeGen.Phase.LocalSched);
+ assertEquals("0", Eval2.eval(code, 1)); // No primes 1 or below
+ assertEquals("3", Eval2.eval(code, 2)); //
+}
+
+
+ @Test
public void testAntiDeps1() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v=new S;
+struct _S { int f; };
+_S !v=new _S;
v.f = 2;
-int i=new S.f;
+_S q=new _S;
+int i=q.f;
i=v.f;
if (arg) v.f=1;
return i;
@@ -65,10 +82,10 @@ return i;
public void testAntiDeps2() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
+struct _S { int f; };
+_S !v = new _S;
v.f = arg;
-S !t = new S;
+_S !t = new _S;
int i = 0;
if (arg) {
if (arg+1) v = t;
@@ -86,19 +103,19 @@ return i;
public void testAntiDeps3() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v0 = new S;
-S? v1;
-if (arg) v1 = new S;
+struct _S { int f; };
+_S !v0 = new _S;
+_S? v1;
+if (arg) v1 = new _S;
if (v1) {
v0.f = v1.f;
} else {
v0.f = 2;
}
-return v0;
+return v0.f;
""");
code.parse().opto();
- assertEquals("return S;", code.print());
+ assertEquals("return Phi(Region,.f,2);", code.print());
}
@@ -106,10 +123,10 @@ return v0;
public void testAntiDeps4() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
+struct _S { int f; };
+_S !v = new _S;
v.f = arg;
-S t = new S;
+_S t = new _S;
int i = v.f;
if (arg+1) arg= 0;
while (arg) v.f = 2;
@@ -123,27 +140,27 @@ return i;
public void testAntiDeps5() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
+struct _S { int f; };
+_S !v = new _S;
while(1) {
while(arg+1) { arg=arg-1; }
if (arg) break;
v.f = 2;
}
-return v;
+return v.f;
""");
code.parse().opto();
- assertEquals("return S;", code.print());
+ assertEquals("return Phi(Loop,0,2);", code.print());
}
@Test
public void testAntiDeps6() {
CodeGen code = new CodeGen(
"""
-struct s { int v; };
-s !ptr=new s;
+struct _S { int v; };
+_S !ptr=new _S;
while( -arg )
- ptr = new s;
+ ptr = new _S;
while(1)
arg = arg+ptr.v;
""");
@@ -155,9 +172,9 @@ while(1)
public void testAntiDeps7() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
-S t = new S;
+struct _S { int f; };
+_S !v = new _S;
+_S t = new _S;
int i = v.f;
while (arg) {
v.f = arg;
@@ -173,9 +190,9 @@ return i;
public void testAntiDeps8() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
-S t = new S;
+struct _S { int f; };
+_S !v = new _S;
+_S t = new _S;
while(arg) {
arg=arg-1;
int f = v.f;
@@ -193,26 +210,26 @@ return arg;
public void testAntiDeps9() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
-S t = new S;
+struct _S { int f; };
+_S !v = new _S;
+_S t = new _S;
if (arg) {
v.f=2;
int i=t.f;
v.f=i;
}
-return v;
+return v.f;
""");
code.parse().opto();
- assertEquals("return S;", code.print());
+ assertEquals("return 0;", code.print());
}
@Test
public void testExample2() {
CodeGen code = new CodeGen(
"""
-struct S { int f; };
-S !v = new S;
+struct _S { int f; };
+_S !v = new _S;
int i = arg;
while (arg > 0) {
int j = i/3;
@@ -223,7 +240,7 @@ while (arg > 0) {
return v;
""");
code.parse().opto();
- //assertEquals("return new S;", code.print());
+ //assertEquals("return new _S;", code.print());
}
@Test
diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index 8069ddaa..85317644 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -1,32 +1,31 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.StopNode;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
public class Chapter12Test {
@Test
public void testJig() {
CodeGen code = new CodeGen(
"""
-return 3.14;
+return 0;
""");
code.parse().opto();
- assertEquals("return 3.14;", code.print());
- assertEquals("3.14", Eval2.eval(code, 0));
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
}
@Test
public void testFloat() {
CodeGen code = new CodeGen(
"""
-return 3.14;
+flt x = 3.14;
+return 0;
""");
code.parse().opto();
- assertEquals("return 3.14;", code.print());
- assertEquals("3.14", Eval2.eval(code, 0));
+ assertEquals("return 0;", code.print());
+ assertEquals("0", Eval2.eval(code, 0));
}
@Test
@@ -41,8 +40,10 @@ while( 1 ) {
}
return guess;
""");
+ // TODO: One of the problems with <clinit> returning an OS error code,
+ // is it chops all results to int.
code.parse().opto();
- assertEquals("return Phi(Loop,(flt)arg,(((ToFloat/Phi_guess)+Phi_guess)*0.5f));", code.print());
+ 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));
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java
index 4e1db3b4..8bbcf21c 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java
@@ -10,11 +10,11 @@ public class Chapter13Test {
public void testJig() {
CodeGen code = new CodeGen(
"""
-return 3.14;
+return 3;
""");
code.parse().opto();
- assertEquals("return 3.14;", code.print());
- assertEquals("3.14", Eval2.eval(code, 0));
+ assertEquals("return 3;", code.print());
+ assertEquals("3", Eval2.eval(code, 0));
}
@Test
@@ -40,22 +40,22 @@ return head.next.i;
public void testLinkedList1() {
CodeGen code = new CodeGen(
"""
-struct LLI { LLI? next; int q; };
-LLI? !head = null;
+struct _LLI { _LLI? next; int q; };
+_LLI? !head = null;
while( arg ) {
- LLI !x = new LLI;
+ _LLI !x = new _LLI;
x.next = head;
x.q = arg;
head = x;
arg = arg-1;
}
if( !head ) return 0;
-LLI? next = head.next;
+_LLI? next = head.next;
if( next==null ) return 1;
return next.q;
""");
code.parse().opto().typeCheck();
- assertEquals("return Phi(Region,0,1,.q);", code.print());
+ assertEquals("return Phi(Region,1,.q,0);", code.print());
assertEquals("2", Eval2.eval(code, 3));
}
@@ -63,17 +63,17 @@ return next.q;
public void testCoRecur() {
CodeGen code = new CodeGen(
"""
-struct int0 { int i; flt0? f; };
-struct flt0 { flt f; int0? i; };
-int0 !i0 = new int0;
+struct _int0 { int i; Test._flt0? f; };
+struct _flt0 { flt f; _int0? i; };
+_int0 !i0 = new _int0;
i0.i = 17;
-flt0 !f0 = new flt0;
+_flt0 !f0 = new _flt0;
f0.f = 3.14;
i0.f = f0;
f0.i = i0;
return f0.i.f.i.i;
""");
- code.parse().opto();
+ code.parse().opto().typeCheck();
assertEquals("return 17;", code.print());
}
@@ -81,8 +81,8 @@ return f0.i.f.i.i;
public void testNullRef0() {
CodeGen code = new CodeGen(
"""
-struct N { N? next; int i; };
-N n = new N;
+struct _N { _N? next; int i; };
+_N n = new _N;
return n.next;
""");
code.parse().opto();
@@ -94,12 +94,12 @@ return n.next;
CodeGen code = new CodeGen(
"""
struct M { int m; };
-struct N { M next; int i; };
-N n = new N { next = new M; };
+struct N { M next; int i; new N = { M m -> next = m; }; };
+N n = new N(new M);
return n.next;
""");
code.parse().opto();
- assertEquals("return (const)M;", code.print());
+ assertEquals("Stop[ return (const)Test.M; return MEM[ 2:.m=0;]; return MEM[ 2:___ 3:___ 4:___ 5:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 6:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.i=0;]; ]", code.print());
}
@Test
@@ -107,12 +107,12 @@ return n.next;
CodeGen code = new CodeGen(
"""
struct M { int m; };
-struct N { M next; int i; };
-N n = new N { next = null; };
+struct N { M next; int i; new N = { M m -> next = m; }; };
+N n = new N(null);
return n.next;
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Type null is not of declared type *M",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Argument #0 isa null, but must be a *Test.M {i64 !m; }",e.getMessage()); }
}
@Test
@@ -132,20 +132,20 @@ return n.i;
public void testNullRef4() {
CodeGen code = new CodeGen("return -null-5/null-5;");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Cannot '//' null",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Cannot 5 // null",e.getMessage()); }
}
@Test public void testNullRef5() {
CodeGen code = new CodeGen("return null+42;");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Cannot '+' null",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Cannot null + 42",e.getMessage()); }
}
@Test
public void testEmpty() {
CodeGen code = new CodeGen(
"""
-struct S{};
+struct _S{};
return 0;
""");
code.parse().opto();
@@ -160,7 +160,7 @@ return 0;
struct S1 { S2? s; };
return new S2;
""");
- try { code.parse().opto(); fail(); }
+ try { code.parse().opto().typeCheck(); fail(); }
catch( Exception e ) { assertEquals("Unknown struct type 'S2'",e.getMessage()); }
}
@@ -168,12 +168,12 @@ return new S2;
public void testForwardRef1() {
CodeGen code = new CodeGen(
"""
-struct S1 { S2? s; };
-struct S2 { int x; };
-return new S1.s=new S2;
+struct _S1 { _S2? s; };
+struct _S2 { int x; };
+return (new _S1).s=new _S2;
""");
code.parse().opto();
- assertEquals("return S2;", code.print());
+ assertEquals("return _S2;", code.print());
}
@Test
@@ -197,9 +197,9 @@ return p1.pi.i + 1;
public void testCoRecur2() {
CodeGen code = new CodeGen(
"""
-struct A { B? f0; C? f1; }; A !a = new A;
-struct B { C? f0; A? f1; }; B !b = new B;
-struct C { A? f0; B? f1; }; C !c = new C;
+struct _A { Test._B? f0; Test._C? f1; }; _A !a = new _A;
+struct _B { Test._C? f0; Test._A? f1; }; _B !b = new _B;
+struct _C { Test._A? f0; Test._B? f1; }; _C !c = new _C;
a.f0=b; a.f1=c;
b.f0=c; b.f1=a;
@@ -209,40 +209,40 @@ return a.f0.f1.f0.f1.f0;
""");
code.parse().opto().typeCheck().GCM().localSched();
- assertEquals("return B;", code._stop.toString());
- assertEquals("B{f0=C},f1=A{f0=$cyclic,f1=$cyclic}}", Eval2.eval(code, 0));
+ 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));
}
@Test
public void testCoRecur3() {
CodeGen code = new CodeGen(
"""
-struct A { L? a; T? b; F? c; }; A !a = new A;
-struct B { M? a; U? b; G? c; }; B !b = new B;
-struct C { N? a; V? b; H? c; }; C !c = new C;
-struct D { O? a; W? b; I? c; }; D !d = new D;
-struct E { P? a; X? b; J? c; }; E !e = new E;
-struct F { Q? a; Y? b; K? c; }; F !f = new F;
-struct G { R? a; Z? b; L? c; }; G !g = new G;
-struct H { S? a; A? b; M? c; }; H !h = new H;
-struct I { T? a; B? b; N? c; }; I !i = new I;
-struct J { U? a; C? b; O? c; }; J !j = new J;
-struct K { V? a; D? b; P? c; }; K !k = new K;
-struct L { W? a; E? b; Q? c; }; L !l = new L;
-struct M { X? a; F? b; R? c; }; M !m = new M;
-struct N { Y? a; G? b; S? c; }; N !n = new N;
-struct O { Z? a; H? b; T? c; }; O !o = new O;
-struct P { A? a; I? b; U? c; }; P !p = new P;
-struct Q { B? a; J? b; V? c; }; Q !q = new Q;
-struct R { C? a; K? b; W? c; }; R !r = new R;
-struct S { D? a; L? b; X? c; }; S !s = new S;
-struct T { E? a; M? b; Y? c; }; T !t = new T;
-struct U { F? a; N? b; Z? c; }; U !u = new U;
-struct V { G? a; O? b; A? c; }; V !v = new V;
-struct W { H? a; P? b; B? c; }; W !w = new W;
-struct X { I? a; Q? b; C? c; }; X !x = new X;
-struct Y { J? a; R? b; D? c; }; Y !y = new Y;
-struct Z { K? a; S? b; E? c; }; Z !z = new Z;
+struct _A { Test._L? a; Test._T? b; Test._F? c; }; _A !a = new _A;
+struct _B { Test._M? a; Test._U? b; Test._G? c; }; _B !b = new _B;
+struct _C { Test._N? a; Test._V? b; Test._H? c; }; _C !c = new _C;
+struct _D { Test._O? a; Test._W? b; Test._I? c; }; _D !d = new _D;
+struct _E { Test._P? a; Test._X? b; Test._J? c; }; _E !e = new _E;
+struct _F { Test._Q? a; Test._Y? b; Test._K? c; }; _F !f = new _F;
+struct _G { Test._R? a; Test._Z? b; Test._L? c; }; _G !g = new _G;
+struct _H { Test._S? a; Test._A? b; Test._M? c; }; _H !h = new _H;
+struct _I { Test._T? a; Test._B? b; Test._N? c; }; _I !i = new _I;
+struct _J { Test._U? a; Test._C? b; Test._O? c; }; _J !j = new _J;
+struct _K { Test._V? a; Test._D? b; Test._P? c; }; _K !k = new _K;
+struct _L { Test._W? a; Test._E? b; Test._Q? c; }; _L !l = new _L;
+struct _M { Test._X? a; Test._F? b; Test._R? c; }; _M !m = new _M;
+struct _N { Test._Y? a; Test._G? b; Test._S? c; }; _N !n = new _N;
+struct _O { Test._Z? a; Test._H? b; Test._T? c; }; _O !o = new _O;
+struct _P { Test._A? a; Test._I? b; Test._U? c; }; _P !p = new _P;
+struct _Q { Test._B? a; Test._J? b; Test._V? c; }; _Q !q = new _Q;
+struct _R { Test._C? a; Test._K? b; Test._W? c; }; _R !r = new _R;
+struct _S { Test._D? a; Test._L? b; Test._X? c; }; _S !s = new _S;
+struct _T { Test._E? a; Test._M? b; Test._Y? c; }; _T !t = new _T;
+struct _U { Test._F? a; Test._N? b; Test._Z? c; }; _U !u = new _U;
+struct _V { Test._G? a; Test._O? b; Test._A? c; }; _V !v = new _V;
+struct _W { Test._H? a; Test._P? b; Test._B? c; }; _W !w = new _W;
+struct _X { Test._I? a; Test._Q? b; Test._C? c; }; _X !x = new _X;
+struct _Y { Test._J? a; Test._R? b; Test._D? c; }; _Y !y = new _Y;
+struct _Z { Test._K? a; Test._S? b; Test._E? c; }; _Z !z = new _Z;
a.a=l; a.b=t; a.c=f;
b.a=m; b.b=u; b.c=g;
@@ -275,7 +275,7 @@ return a.b.c.a.b.c.a.b.c.a.b.c.a.b.c.a.b.c;
""");
code.parse().opto().typeCheck().GCM().localSched();
- assertEquals("return R;", code._stop.toString());
- assertEquals("R{a=C{a=N},b=V},c=H}},b=K{a=$cyclic,b=D{a=O},b=W},c=I}},c=P}},c=$cyclic}", Eval2.eval(code, 0));
+ 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/src/test/java/com/seaofnodes/simple/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index 7af35b56..807905a8 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -92,12 +92,12 @@ return b;
public void testRefLoad() {
CodeGen code = new CodeGen(
"""
-struct Foo { u1 b; };
-Foo !f = new Foo;
+struct _Foo { u1 b; };
+_Foo !f = new _Foo;
f.b = 123;
return f.b;
""");
- code.parse().opto();
+ code.parse().opto().typeCheck();
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter15Test.java b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
index 6ba4e2b1..d4790b3b 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -25,14 +25,14 @@ return 3.14;
public void testCyclic() {
CodeGen code = new CodeGen(
"""
-struct C { C? l; };
-C !c = new C;
+struct _C { _C? l; };
+_C !c = new _C;
c.l = c;
return c;
""");
code.parse().opto();
- assertEquals("return C;", code.print());
- assertEquals("C{l=$cyclic}", Eval2.eval(code, 0));
+ assertEquals("return Test._C;", code.print());
+ assertEquals("Test._C{l=$cyclic}", Eval2.eval(code, 0));
}
@Test
@@ -97,34 +97,34 @@ return a[0];
public void testBasic4() {
CodeGen code = new CodeGen(
"""
-struct A { int i; };
-A?[] !a = new A?[2];
+struct _A { int i; };
+_A?[] !a = new _A?[2];
return a;
""");
code.parse().opto();
- assertEquals("return []*A?;", code.print());
- assertEquals("*A {i64 !i; }?[ null,null]", Eval2.eval(code, 0));
+ assertEquals("return []*Test._A?;", code.print());
+ assertEquals("*Test._A {i64 !i; }?[ null,null]", Eval2.eval(code, 0));
}
@Test
public void testBasic5() {
CodeGen code = new CodeGen(
"""
-struct S { int x; flt y; };
-// A new S
-S !s = new S; s.x=99; s.y = 3.14;
+struct _S { int x; flt y; };
+// A new _S
+_S !s = new _S; s.x=99; s.y = 3.14;
-// Double-d array of Ss. Fill in one row.
-S?[]?[] !iss = new S?[]?[2];
-iss[0] = new S?[7];
+// Double-d array of _Ss. Fill in one row.
+_S?[]?[] !iss = new _S?[]?[2];
+iss[0] = new _S?[7];
iss[0][2] = s;
// Now pull out the filled-in value, with null checks
flt rez;
-S?[]? is = iss[arg];
+_S?[]? is = iss[arg];
if( !is ) rez = 1.2;
else {
- S? i = is[2];
+ _S? i = is[2];
if( !i ) rez = 2.3;
else rez = i.y;
}
@@ -140,13 +140,13 @@ return rez;
public void testBasic6() {
CodeGen code = new CodeGen(
"""
-struct S { int x; flt y; };
-// A new S
-S !s = new S; s.x=99; s.y = 3.14;
+struct _S { int x; flt y; };
+// A new _S
+_S !s = new _S; s.x=99; s.y = 3.14;
-// Double-d array of Ss. Fill in one row.
-S?[]?[] !iss = new S?[]?[2];
-iss[0] = new S?[7];
+// Double-d array of _Ss. Fill in one row.
+_S?[]?[] !iss = new _S?[]?[2];
+iss[0] = new _S?[7];
iss[0][2] = s;
// Now pull out the filled-in value, with null checks
@@ -167,22 +167,22 @@ return rez;""");
CodeGen code = new CodeGen(
"""
// Can we define a forward-reference array?
-struct Tree { Tree?[]? _kids; };
-Tree !root = new Tree;
-root._kids = new Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES????
-root._kids[0] = new Tree;
+struct _Tree { _Tree?[]? _kids; };
+_Tree !root = new _Tree;
+root._kids = new _Tree?[2]; // NO BANG SO ARRAY IS OF IMMUTABLE TREES????
+root._kids[0] = new _Tree;
return root;
""");
- code.parse().opto();
- assertEquals("return Tree;", code.print());
- assertEquals("Tree{_kids=*Tree {*[]*Tree?? !_kids; }?[ Tree{_kids=null},null]}", Eval2.eval(code, 0));
+ code.parse().opto().typeCheck();
+ assertEquals("return Test._Tree;", code.print());
+ assertEquals("Test._Tree{_kids=*Test._Tree {*[]*Test._Tree?? !_kids; }?[ Test._Tree{_kids=null},null]}", Eval2.eval(code, 0));
}
@Test
public void testNestedStructAddMemProj() {
CodeGen code = new CodeGen(
"""
-struct S { int a; int[] b; };
+struct _S { int a; int[] b; };
return 0;
""");
code.parse().opto();
@@ -208,7 +208,7 @@ while( i < ary# - 1 ) {
}
return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
""");
- code.parse().opto();
+ code.parse().opto().typeCheck();
assertEquals("return (.[]+(.[]*1000));", code.print());
assertEquals("1006", Eval2.eval(code, 4));
}
@@ -226,12 +226,12 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
public void testNewNodeInit() {
CodeGen code = new CodeGen(
"""
-struct S {int i; flt f;};
-S !s1 = new S;
-S !s2 = new S;
+struct _S {int i; flt f;};
+_S !s1 = new _S;
+_S !s2 = new _S;
s2.i = 3;
s2.f = 2.0;
-if (arg) s1 = new S;
+if (arg) s1 = new _S;
return s1.i + s1.f;
""");
code.parse().opto();
@@ -268,8 +268,8 @@ return is;
int[] is = new int[3.14];
return is[1];
""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("Cannot allocate an array with length 3.14",e.getMessage()); }
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Cannot '<<' 3.14",e.getMessage()); }
}
@Test
diff --git a/src/test/java/com/seaofnodes/simple/Chapter16Test.java b/src/test/java/com/seaofnodes/simple/Chapter16Test.java
index 2623fee5..80b0a2bf 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -71,8 +71,8 @@ return y;
@Test
public void testConstruct0() {
CodeGen code = new CodeGen("""
-struct X { int x=3; };
-X z = new X;
+struct _X { int x=3; };
+_X z = new _X;
return z.x;
""");
code.parse().opto();
@@ -83,8 +83,8 @@ return z.x;
@Test
public void testConstruct1() {
CodeGen code = new CodeGen("""
-struct X { int !x; };
-X z = new X { x=3; };
+struct _X { int !x; new _X = { int xx -> x=xx; }; };
+_X z = new _X(3);
return z.x;
""");
code.parse().opto();
@@ -95,8 +95,8 @@ return z.x;
@Test
public void testConstruct2() {
CodeGen code = new CodeGen("""
-struct X { int x=3; };
-X z = new X { x = 4; };
+struct _X { int x=3; new _X = { int xx -> x=xx; }; };
+_X z = new _X(4);
return z.x;
""");
code.parse().opto();
@@ -108,20 +108,20 @@ return z.x;
@Test
public void testStructFinal0() {
CodeGen code = new CodeGen("""
-struct Point { int !x, !y; };
-Point p = new Point { x=3; y=4; };
+struct _Point { int !x, !y; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
+_Point p = new _Point(3,4);
return p;
""");
code.parse().opto();
- assertEquals("return Point;", code.print());
- assertEquals("Point{x=3,y=4}", Eval2.eval(code, 0));
+ assertEquals("return (const)Test._Point;", code.print());
+ assertEquals("Test._Point{x=3,y=4}", Eval2.eval(code, 0));
}
@Test
public void testStructFinal1() {
CodeGen code = new CodeGen("""
-struct Point { int x=3, y=4; };
-val p = new Point { x=5; y=6; };
+struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
+val p = new _Point(5,6);
p.x++;
return p;
""");
@@ -132,8 +132,8 @@ return p;
@Test
public void testStructFinal2() {
CodeGen code = new CodeGen("""
-struct Point { int x=3, y=4; };
-val p = new Point;
+struct _Point { int x=3, y=4; };
+val p = new _Point;
p.x++;
return p;
""");
@@ -144,20 +144,20 @@ return p;
@Test
public void testStructFinal3() {
CodeGen code = new CodeGen("""
-struct Point { var x; var y; };
-Point p = new Point;
+struct _Point { var x; var y; };
+_Point p = new _Point;
p.x++;
return p;
""");
try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
+ catch( Exception e ) { assertEquals("'Test._Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
}
@Test
public void testStructFinal4() {
CodeGen code = new CodeGen("""
-struct Point { val x=3; val y=4; };
-Point p = new Point;
+struct _Point { val x=2; val y=4; };
+_Point p = new _Point;
p.x++;
return p;
""");
@@ -168,14 +168,14 @@ return p;
@Test
public void testStructFinal5() {
CodeGen code = new CodeGen("""
-struct Point { var x=3; var y=4; };
-Point !p = new Point;
+struct _Point { var x=3; var y=4; };
+_Point !p = new _Point;
p.x++;
return p;
""");
code.parse().opto();
- assertEquals("return Point;", code.print());
- assertEquals("Point{x=4,y=4}", Eval2.eval(code, 0));
+ assertEquals("return Test._Point;", code.print());
+ assertEquals("Test._Point{x=4,y=4}", Eval2.eval(code, 0));
}
// Same as the Chapter13 test with the same name, but using the new
@@ -184,19 +184,19 @@ return p;
public void testLinkedList1() {
CodeGen code = new CodeGen(
"""
-struct LLI { LLI? next; int i; };
-LLI? !head = null;
+struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; };
+_LLI? !head = null;
while( arg ) {
- head = new LLI { next=head; i=arg; };
+ head = new _LLI(head,arg);
arg = arg-1;
}
if( !head ) return 0;
-LLI? next = head.next;
+_LLI? next = head.next;
if( !next ) return 1;
return next.i;
""");
code.parse().opto();
- assertEquals("return Phi(Region,0,1,.i);", code.print());
+ assertEquals("return Phi(Region,1,.i,0);", code.print());
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
assertEquals("2", Eval2.eval(code, 3));
@@ -206,28 +206,27 @@ return next.i;
public void testLinkedList2() {
CodeGen code = new CodeGen(
"""
-struct LLI { LLI? next; int i; };
-LLI? !head = null;
+struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int a ->
+ next=n;
+ int !tmp=a;
+ while( a > 10 ) {
+ tmp = tmp + a;
+ a = a - 1;
+ }
+ i=tmp;
+}; };
+_LLI? !head = null;
while( arg ) {
- head = new LLI {
- next=head;
- // Any old code in the constructor
- int !tmp=arg;
- while( arg > 10 ) {
- tmp = tmp + arg;
- arg = arg - 1;
- }
- i=tmp;
- };
+ head = new _LLI(head,arg);
arg = arg-1;
}
if( !head ) return 0;
-LLI? next = head.next;
+_LLI? next = head.next;
if( !next ) return 1;
return next.i;
""");
code.parse().opto();
- assertEquals("return Phi(Region,0,1,.i);", code.print());
+ assertEquals("return Phi(Region,1,.i,0);", code.print());
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
assertEquals("2", Eval2.eval(code, 11));
@@ -237,7 +236,7 @@ return next.i;
public void testSquare() {
CodeGen code = new CodeGen(
"""
-struct Square {
+struct _Square {
flt !side = arg;
// Newtons approximation to the square root, computed in a constructor.
// The actual allocation will copy in this result as the initial
@@ -249,11 +248,11 @@ struct Square {
diag = next;
}
};
-return new Square;
+return new _Square;
""");
code.parse().opto();
- assertEquals("return Square;", code.print());
- assertEquals("Square{side=3.0,diag=1.7320508075688772}", Eval2.eval(code, 3));
- assertEquals("Square{side=4.0,diag=2.0}", Eval2.eval(code, 4));
+ 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/src/test/java/com/seaofnodes/simple/Chapter17Test.java b/src/test/java/com/seaofnodes/simple/Chapter17Test.java
index 53c23ecd..24daa164 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter17Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter17Test.java
@@ -79,8 +79,8 @@ return xs[0];
@Test
public void testInc5() {
CodeGen code = new CodeGen("""
-struct S { u16 x; };
-S !s = new S;
+struct _S { u16 x; };
+_S !s = new _S;
s.x--;
return s.x;
""");
@@ -132,13 +132,13 @@ return s.x;
@Test public void testVar0() {
CodeGen code = new CodeGen("var d; return d;");
try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
}
@Test public void testVar1() {
CodeGen code = new CodeGen("val d; return d;");
try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
}
@Test public void testVar2() {
@@ -156,14 +156,14 @@ return s.x;
}
@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 ");
+ CodeGen code = new CodeGen("struct _S{int x;}; _S? s; s=new _S; s.x++; return s.x; // Ok, no initializer so x is mutable ");
code.parse().opto();
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@Test public void testVar5() {
- CodeGen code = new CodeGen("struct S{int x;}; S? s; s=new S{x=3;}; s.x++; return s.x; // Ok, no initializer so x is mutable ");
+ CodeGen code = new CodeGen("struct _S{int x; new _S={ int xx -> x=xx; };}; _S? s; s=new _S(3); s.x++; return s.x; // Ok, no initializer so x is mutable ");
code.parse().opto();
assertEquals("return 4;", code.print());
assertEquals("4", Eval2.eval(code, 0));
@@ -176,20 +176,20 @@ return s.x;
}
@Test public void testVar7() {
- CodeGen code = new CodeGen("struct S{int x;}; val s = new S{x=3;}; s.x++; return s.x; // Error initializer so x is immutable ");
+ CodeGen code = new CodeGen("struct S{int x; new S={ int xx -> x=xx; };}; val s = new S(3); s.x++; return s.x; // Error initializer so x is immutable ");
try { code.parse().opto().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 ");
+ CodeGen code = new CodeGen("struct _S{int x;}; _S !s=new _S; s.x++; return s.x; // Ok, has '!' so s.x is mutable ");
code.parse().opto();
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 ");
+ CodeGen code = new CodeGen("struct _S{int x;}; var s=new _S; s.x++; return s.x; // Ok, has var so s.x is mutable ");
code.parse().opto();
assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
@@ -207,8 +207,8 @@ struct Bar { int x; };
Bar !bar = new Bar;
bar.x = 3; // Ok, bar is mutable
-struct Foo { Bar? !bar; int y; };
-Foo !foo = new Foo { bar = bar; };
+struct Foo { Bar? !bar; int y; new Foo = { Bar? b -> bar = b; }; };
+Foo !foo = new Foo(bar);
foo.bar = bar; // Ok foo is mutable
foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable
@@ -222,12 +222,12 @@ return xfoo.bar.x;
@Test public void testVar12() {
CodeGen code = new CodeGen("""
-struct Bar { int x; };
-Bar !bar = new Bar;
+struct _Bar { int x; };
+_Bar !bar = new _Bar;
bar.x = 3; // Ok, bar is mutable
-struct Foo { Bar? !bar; int y; };
-Foo !foo = new Foo;
+struct _Foo { _Bar? !bar; int y; };
+_Foo !foo = new _Foo;
foo.bar = bar; // Ok bar is mutable
foo.bar.x++; // Ok foo and foo.bar and foo.bar.x are all mutable
@@ -256,41 +256,41 @@ int i,i++;
public void testVar14() {
CodeGen code = new CodeGen("""
struct B {};
-struct A { B b; };
-A x = new A {
+struct A { B b; new A = { ->
return b; // read before init
b = new B;
-};
+}; };
+A x = new A;
""");
- try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("Cannot read uninitialized field 'b'",e.getMessage()); }
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("Field 'b' might not be initialized",e.getMessage()); }
}
@Test
public void testVar15() {
CodeGen code = new CodeGen("""
struct B {};
-struct A { B b; };
-return new A {
+struct A { B b; new A = { ->
if (arg) b = new B; // Constructor ends with partial init of b
-}.b;
+}; };
+return (new A).b;
""");
- try { code.parse(); fail(); }
- catch( Exception e ) { assertEquals("'A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); }
+ try { code.parse().opto().typeCheck(); fail(); }
+ catch( Exception e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
}
@Test
public void testVar16() {
CodeGen code = new CodeGen(
"""
-struct S{};
+struct S{ new S = { -> x = 2; }; };
val x = 1;
-val s = new S{x = 2;};
+val s = new S;
return x;
"""
);
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Cannot reassign final 'x'",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Undefined name 'x'",e.getMessage()); }
}
@Test
@@ -309,7 +309,7 @@ return me;
"""
);
try { code.parse(); fail(); }
- catch( Exception e ) { assertEquals("Syntax error, expected =expression: ;",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `=expression` but found `;`",e.getMessage()); }
}
// ---------------------------------------------------------------
@@ -337,21 +337,22 @@ return arg ? 0 : arg;
@Test
public void testTrinary2() {
CodeGen code = new CodeGen("""
-struct Bar { int x; };
-var b = arg ? new Bar : null;
+struct _Bar { int x; };
+var b = arg ? new _Bar : null;
return b ? b.x++ + b.x++ : -1;
""");
code.parse().opto();
assertEquals("return Phi(Region,((.x<<1)+1),-1);", code.print());
assertEquals("-1", Eval2.eval(code, 0));
- assertEquals("1", Eval2.eval(code, 1));
+
+ assertEquals("1", Eval2.eval(code, 1));
}
@Test
public void testTrinary3() {
CodeGen code = new CodeGen("""
-struct Bar { int x; };
-var b = arg ? new Bar;
+struct _Bar { int x; };
+var b = arg ? new _Bar;
return b ? b.x++ + b.x++ : -1;
""");
code.parse().opto();
@@ -364,12 +365,12 @@ return b ? b.x++ + b.x++ : -1;
public void testTrinary4() {
// This test case will benefit from an unzipping transformation
CodeGen code = new CodeGen("""
-struct Bar { Bar? next; int x; };
-var b = arg ? new Bar { next = (arg==2) ? new Bar{x=2;}; x=1; };
+struct _Bar { _Bar? next; int x; new _Bar = { _Bar? n, int xx -> next=n; x=xx; }; };
+var b = arg ? new _Bar((arg==2) ? new _Bar(null,2), 1);
return b ? b.next ? b.next.x : b.x; // parses "b ? (b.next ? b.next.x : b.x) : 0"
""");
code.parse().opto();
- assertEquals("return Phi(Region,.x,0);", code.print());
+ assertEquals("return Phi(Region,.x,0,.x);", code.print());
assertEquals("0", Eval2.eval(code, 0));
assertEquals("1", Eval2.eval(code, 1));
assertEquals("2", Eval2.eval(code, 2));
@@ -394,7 +395,7 @@ struct S{};
return arg ? 7 : new S;
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("No common type amongst int and reference",e.getMessage()); }
+ catch( Exception e ) { assertEquals("No common type amongst 7 and *Test.S {}",e.getMessage()); }
}
// ---------------------------------------------------------------
@@ -469,7 +470,7 @@ return i;
for(;;arg++;) {}
""");
try { code.parse().opto(); fail(); }
- catch( Exception e ) { assertEquals("Syntax error, expected Unexpected code after expression: ;",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Syntax error, expected `Unexpected code after expression` but found `;`",e.getMessage()); }
}
@@ -478,39 +479,40 @@ for(;;arg++;) {}
@Test
public void testForward0() {
CodeGen code = new CodeGen("""
-struct A{
+struct _A {
B? f1;
B? f2;
};
-return new A;
+return new _A;
""");
code.parse().opto();
- assertEquals("return A;", code.print());
- assertEquals("A{f1=null,f2=null}", Eval2.eval(code,0));
+ assertEquals("return Test._A;", code.print());
+ assertEquals("Test._A{f1=null,f2=null}", Eval2.eval(code,0));
}
@Test
public void testForward1() {
CodeGen code = new CodeGen("""
-struct A {
+struct _A {
B?[]? nil_array_of_b;
B?[] array_of_b;
+ new _A = { B?[] bs -> array_of_b = bs; };
};
-return new A{array_of_b = new B?[0]; }.array_of_b;
+return new _A(new B?[0]).array_of_b;
""");
code.parse().opto();
- assertEquals("return (const)[]*B?;", code.print());
- assertEquals("*B {... }?[]", Eval2.eval(code,0));
+ assertEquals("return []*B?;", code.print());
+ assertEquals("*B {}?[]", Eval2.eval(code,0));
}
// ---------------------------------------------------------------
@Test
public void testLinkedList2() {
CodeGen code = new CodeGen("""
-struct LLI { LLI? next; int i; };
-LLI? !head = null;
+struct _LLI { _LLI? next; int i; new _LLI = { _LLI? n, int ii -> next=n; i=ii; }; };
+_LLI? !head = null;
while( arg-- )
- head = new LLI { next=head; i=arg; };
+ head = new _LLI(head,arg);
int sum=0;
var ptr = head; // A read-only ptr, to be assigned from read-only next fields
for( ; ptr; ptr = ptr.next )
diff --git a/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/src/test/java/com/seaofnodes/simple/Chapter18Test.java
index 00b6292e..b65e101b 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -16,7 +16,7 @@ public class Chapter18Test {
return 0;
""");
code.driver(Phase.LocalSched);
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 2));
}
@@ -34,7 +34,7 @@ while(arg--) {
return a;
""");
code.parse().opto().typeCheck();
- assertEquals("return Phi(Loop,1,Phi(Loop,2,Phi_a));", code._stop.toString());
+ assertEquals("return Phi(Loop,1,Phi(Loop,2,Phi_a));", code.print());
assertEquals("1", Eval2.eval(code, 0));
assertEquals("2", Eval2.eval(code, 1));
assertEquals("1", Eval2.eval(code, 2));
@@ -51,7 +51,7 @@ return a;
return x2;
""");
code.parse().opto();
- assertEquals("return null;", code._stop.toString());
+ assertEquals("return null;", code.print());
assertEquals("null", Eval2.eval(code, 0 ) );
}
@@ -64,31 +64,50 @@ return x2;
};
""");
code.parse().opto();
- assertEquals("return (Parm_x(sq,i64)*x);", code._stop.toString());
- //assertEquals("{ int -> int #1}", Eval2.eval(code, 3));
+ assertEquals("Stop[ return { sq}; return (Parm_x(sq,i64)*x); ]", code.print());
+ assertEquals("{ ptr i64 -> i64 #[ 3]}", Eval2.eval(code, 3));
}
@Test
public void testFcn1() {
CodeGen code = new CodeGen(
"""
-var sq = { int x ->
+var _sq = { int x ->
x*x;
};
-return sq(arg)+sq(3);
+return _sq(arg)+_sq(3);
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return (#2+#2); return (Parm_x(sq,i64)*x); ]", code._stop.toString());
+ assertEquals("return ((arg*arg)+9);", code.print());
assertEquals("13", Eval2.eval(code, 2));
}
// Function scope test
@Test
- public void testFcn2() {
+ public void testFcn2A() {
CodeGen code = new CodeGen(
"""
-int cnt=1;
-return { -> cnt; };
+int cnt=1; // Global scope; exactly one of these
+return { -> cnt++; }; // Escape global-scope variable is OK
+""");
+ code.parse().opto();
+ assertEquals("Stop[ return { ptr -> i64 #[ 3]}; return .cnt; ]", code.print());
+ assertEquals("{ ptr -> i64 #[ 3]}", Eval2.eval(code, 0));
+ }
+
+ // Function scope test
+ @Test
+ public void testFcn2B() {
+ CodeGen code = new CodeGen(
+"""
+val ctr = { ->
+ int cnt; // Function-local var
+ return { -> cnt=cnt+1; cnt; }; // Escape cnt out of scope
+};
+val A = ctr(); // Make a counter
+val B = ctr(); // Make a counter
+A(); // Increment counter A
+return A()*10 + B(); // Incr A and B; result: 2*10+1 == 21
""");
try { code.parse().opto(); fail(); }
catch( Exception e ) { assertEquals("Variable 'cnt' is out of function scope and must be a final constant",e.getMessage()); }
@@ -103,7 +122,7 @@ val cnt=2;
return { -> cnt; }();
""");
code.parse().opto();
- assertEquals("return 2;", code._stop.toString());
+ assertEquals("return 2;", code.print());
assertEquals("2", Eval2.eval(code, 0));
}
@@ -116,7 +135,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; };
return fcn(3);
""");
code.parse().opto();
- assertEquals("Stop[ return #2; return 9; return 6; ]", code._stop.toString());
+ 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));
}
@@ -124,9 +143,11 @@ return fcn(3);
// Recursive factorial test
@Test
public void testFcn5() {
- CodeGen code = new CodeGen("val fact = { int x -> x <= 1 ? 1 : x*fact(x-1); }; return fact(arg);");
+ CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1 : x*_fact(x-1); }; return _fact(arg);");
code.parse().opto().typeCheck();
- assertEquals("Stop[ return #2; return Phi(Region,1,(Parm_x(fact,i64)*#2)); ]", code._stop.toString());
+ String ir = code.print();
+ assertTrue(ir.equals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]") ||
+ ir.equals("Stop[ return Phi(Region,1,(arg*Phi(Region,1,((arg-1)*#2)))); return Phi(Region,1,(Parm_x(_fact,(Sub-1),(x-1))*#2)); ]"));
assertEquals( "1", Eval2.eval(code, 0));
assertEquals( "1", Eval2.eval(code, 1));
assertEquals( "2", Eval2.eval(code, 2));
@@ -134,31 +155,55 @@ return fcn(3);
assertEquals("24", Eval2.eval(code, 4));
}
+ // The multiply initially sees one integer input and one unresolved
+ // The multiply initially sees one integer input and one unresolvedfcn8
+ // recursive result, but the floating base case requires a floating mode.
+ @Test
+ public void testFcn5Float() {
+ CodeGen code = new CodeGen("val _fact = { int x -> x <= 1 ? 1.0 : x*_fact(x-1); }; return _fact(arg);");
+ code.parse().opto().typeCheck();
+ assertEquals("1.0", Eval2.eval(code, 0));
+ assertEquals("1.0", Eval2.eval(code, 1));
+ assertEquals("2.0", Eval2.eval(code, 2));
+ assertEquals("6.0", Eval2.eval(code, 3));
+ assertEquals("24.0", Eval2.eval(code, 4));
+ }
+
+ @Test
+ public void testFcn5Minus() {
+ CodeGen code = new CodeGen("val _neg = { int x -> x ? -_neg(x-1) : 1; }; return _neg(arg);");
+ code.parse().opto().typeCheck();
+ assertEquals( "1", Eval2.eval(code, 0));
+ assertEquals("-1", Eval2.eval(code, 1));
+ assertEquals( "1", Eval2.eval(code, 2));
+ assertEquals("-1", Eval2.eval(code, 3));
+ }
+
@Test
public void testFcn6() {
CodeGen code = new CodeGen(
"""
-struct S { int i; };
-val newS = { int x -> return new S { i=x; }; };
-return newS(1).i;
+struct _S { int i; new _S = { int x -> i=x; }; };
+val _newS = { int x -> return new _S(x); };
+return _newS(1).i;
""");
code.parse().opto().typeCheck();
- assertEquals("return 1;", code._stop.toString());
+ assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
- // Double forward reference
+ // Only forward refs to direct function calls allowed
@Test
public void testFcn7() {
CodeGen code = new CodeGen(
"""
-if( arg ? f : g ) return 1;
-val f = {->1;};
-val g = {->2;};
+val _f = {->1;};
+val _g = {->2;};
+if( arg ? _f : _g ) return 1;
return 2;
""");
code.parse().opto().typeCheck();
- assertEquals("return 1;", code._stop.toString());
+ assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 0));
}
@@ -166,17 +211,17 @@ return 2;
public void testFcn8() {
CodeGen code = new CodeGen(
"""
-{int -> int}? i2i = null;
-var id = {{int->int} f-> return f;};
+{int -> int}? _i2i = null;
+var _id = {{int->int} f-> return f;};
for(;;) {
- if (i2i) return i2i(arg);
- var x = {int i-> return i;};
- arg = x(3);
- i2i = id(x);
+ if (_i2i) return _i2i(arg);
+ var _x = {int i-> return i;};
+ arg = _x(3);
+ _i2i = _id(_x);
}
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return #2; return Parm_i(x,i64); ]", code._stop.toString());
+ assertEquals("return Phi(Loop,arg,3);", code.print());
assertEquals("3", Eval2.eval(code, 0));
}
@@ -192,8 +237,8 @@ for(;;) {
}
""");
code.driver(Phase.LocalSched);
- assertEquals("return Top;", code._stop.toString());
- assertEquals("null", Eval2.eval(code, 0));
+ assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print());
+ assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits
}
@@ -201,23 +246,23 @@ for(;;) {
public void testFcn10() {
CodeGen code = new CodeGen(
"""
-struct Person {
+struct _Person {
int age;
};
-val fcn = { Person?[] ps, int x ->
+val _fcn = { _Person?[] ps, int x ->
val tmp = ps[x];
if( ps[x] )
ps[x].age++;
};
-var ps = new Person?[2];
-ps[0] = new Person;
-ps[1] = new Person;
-return fcn(ps,1);
+var ps = new _Person?[2];
+ps[0] = new _Person;
+ps[1] = new _Person;
+return _fcn(ps,1);
""");
code.driver(Phase.LocalSched);
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return .age;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -252,7 +297,7 @@ for(;;) {
return 0;
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Might be null calling { i64 -> i64 #21}?",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Might be null calling { ptr i64 -> i64 #[ 3]}?",e.getMessage()); }
}
@@ -264,7 +309,7 @@ val f = { int i, int j -> return i+j; };
return f();
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Expecting 2 arguments, but found 0",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Expecting 3 arguments, but found 1",e.getMessage()); }
}
@@ -276,16 +321,14 @@ struct S {
{int} f = { -> x(); return 0; }; // Do not let fref x be a field
};
val x = { -> return 1; };
-S? s = null;
-for(;;) {
- if (s) return s.x;
-}
+S? s = new S;
+return s.x;
""");
try { code.parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*S'",e.getMessage()); }
+ catch( Exception e ) { assertEquals("Accessing unknown field 'x' from '*Test.S'",e.getMessage()); }
}
- @Test
+ @Test @Ignore
public void testErr5() {
CodeGen code = new CodeGen(
"""
@@ -308,8 +351,8 @@ val is_even = { int x -> x ? is_odd (x-1) : true ; };
val is_odd = { int x -> x ? is_even(x-1) : false; };
return is_even(arg);
""");
- code.parse().opto();
- assertEquals("Stop[ return is_even( arg); return Phi(Region,Phi(Region,is_even( ((Parm_x(is_even,int,arg,Sub)-1)-1)),0),1); ]", code._stop.toString());
+ code.parse().opto().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));
assertEquals("1", Eval2.eval(code, 2));
@@ -331,7 +374,7 @@ for(;;) {
}
""");
code.parse().opto().typeCheck().GCM();
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -340,15 +383,15 @@ for(;;) {
public void testInline() {
CodeGen code = new CodeGen(
"""
-{int->int}?! i2i = {int i->return i;};
-{{int->int}->{int->int}}! f2f = {{int->int} f->return f;};
-val o = i2i;
-if (arg) i2i = null;
-if (i2i) return i2i(arg);
-return f2f(o)(1);
+{int->int}?! _i2i_noInline = {int i->return i;};
+{{int->int}->{int->int}}! _f2f = {{int->int} f->return f;};
+val _o = _i2i_noInline;
+if (arg) _i2i_noInline = null;
+if (_i2i_noInline) return _i2i_noInline(arg);
+return _f2f(_o)(1);
""");
code.driver(Phase.LocalSched);
- assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(i2i.o,i64); ]", code._stop.toString());
+ assertEquals("Stop[ return Phi(Region,#2,#2); return Parm_i(_i2i_noInline,i64); ]", code.print());
assertEquals("1", Eval2.eval(code, 2));
}
@@ -356,15 +399,15 @@ return f2f(o)(1);
public void testOperField() {
CodeGen code = new CodeGen(
"""
-struct Person {
+struct _Person {
int coffee_count;
};
-Person !p = new Person;
+_Person !p = new _Person;
p.coffee_count += 1;
return p.coffee_count;
""");
code.driver(Phase.LocalSched);
- assertEquals("return 1;", code._stop.toString());
+ assertEquals("return 1;", code.print());
assertEquals("1", Eval2.eval(code, 2));
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter19Test.java b/src/test/java/com/seaofnodes/simple/Chapter19Test.java
index 6d751946..eaf76041 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -3,8 +3,6 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen.Phase;
import com.seaofnodes.simple.codegen.CodeGen;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -16,18 +14,19 @@ public class Chapter19Test {
return 0;
""");
code.parse().opto().typeCheck();
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 2));
}
@Test
- public void testString() throws IOException {
+ public void testString() {
String src =
"""
struct String {
- u8[] cs;
+ u8[~] cs;
int _hashCode;
+ new String = { u8[~] s -> cs=s; };
};
// Compare two Strings
@@ -40,12 +39,6 @@ val equals = { String self, String s ->
return true;
};
-// Return the String hashCode (cached, and never 0)
-val hashCode = { String self ->
- self._hashCode
- ? self._hashCode
- : (self._hashCode = _hashCodeString(self));
-};
val _hashCodeString = { String self ->
int hash=0;
@@ -54,107 +47,141 @@ val _hashCodeString = { String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// Return the String hashCode (cached, and never 0)
+val hashCode = { String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+hashCode(new String("Hello, World!"));
""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck().GCM().localSched();
- assertEquals("Stop[ return Phi(Region,1,0,0,1); return Phi(Region,._hashCode,Phi(Region,123456789,Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash))))); ]", code._stop.toString());
- //assertEquals("-4898613127354160978", Eval2.eval(code, 2));
+ CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
+ assertEquals("Stop[ return Phi(Region,123456789,(!=0)Phi(Loop,0,(.[]+((Phi_hash<<5)-Phi_hash)))); return MEM[ 2:___ 3:___ 4:.cs=Parm_s(Test.String.String,*[]u8); 5:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:._hashCode=0;]; return Phi(Region,0,1,0,1); return Phi(Region,(!=0)._hashCode,Phi(Region,123456789,(!=0)Phi(Loop,0,(((Phi_hash<<5)-Phi_hash)+.[])))); ]", code.print());
+ assertEquals("4029215624828139541", Eval2.eval(code, 2));
+ }
+
+ @Test
+ public void testTernaryStoreCall() {
+ String src =
+"""
+struct String {
+ u8[~] cs;
+ int _hashCode;
+ new String = { u8[~] s -> cs=s; };
+};
+
+val f = { String self -> 7; };
+
+val hashCode = { String self ->
+ self._hashCode
+ ? 3
+ : (self._hashCode = f(self));
+};
+
+hashCode(new String("Hello"));
+""";
+ CodeGen code = new CodeGen(src).driver(Phase.LocalSched);
+ assertFalse(code.print().contains("return Top"));
+ assertEquals("7", Eval2.eval(code, 2));
}
@Test
public void testBasic0() {
CodeGen code = new CodeGen("return 0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return 0;", code.print());
}
@Test
public void testBasic1() {
CodeGen code = new CodeGen("return arg+1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (inc,arg);", code._stop.toString());
+ assertEquals("return (inc,arg);", code.print());
}
@Test
public void testBasic2() {
CodeGen code = new CodeGen("return -17;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return -17;", code._stop.toString());
+ assertEquals("return -17;", code.print());
}
@Test
public void testBasic3() {
CodeGen code = new CodeGen("return arg==1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (set==,(cmp,arg));", code._stop.toString());
+ assertEquals("return (set==,(cmp,arg));", code.print());
}
@Test
public void testBasic4() {
CodeGen code = new CodeGen("return arg<<1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (shli,arg);", code._stop.toString());
+ assertEquals("return (shli,arg);", code.print());
}
@Test
public void testBasic5() {
CodeGen code = new CodeGen("return arg >> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (sari,arg);", code._stop.toString());
+ assertEquals("return (sari,arg);", code.print());
}
@Test
public void testBasic6() {
CodeGen code = new CodeGen("return arg >>> 1;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (shri,arg);", code._stop.toString());
+ assertEquals("return (shri,arg);", code.print());
}
@Test
public void testBasic7() {
CodeGen code = new CodeGen("return arg / 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (div,arg,2);", code._stop.toString());
+ assertEquals("return (div,arg,2);", code.print());
}
@Test
public void testBasic8() {
CodeGen code = new CodeGen("return arg * 6;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (muli,arg);", code._stop.toString());
+ assertEquals("return (muli,arg);", code.print());
}
@Test
public void testBasic9() {
CodeGen code = new CodeGen("return arg & 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (andi,arg);", code._stop.toString());
+ assertEquals("return (andi,arg);", code.print());
}
@Test
public void testBasic10() {
CodeGen code = new CodeGen("return arg | 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (ori,arg);", code._stop.toString());
+ assertEquals("return (ori,arg);", code.print());
}
@Test
public void testBasic11() {
CodeGen code = new CodeGen("return arg ^ 2;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (xori,arg);", code._stop.toString());
+ assertEquals("return (xori,arg);", code.print());
}
@Test
public void testBasic12() {
CodeGen code = new CodeGen("return arg + 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString());
+ assertEquals("return (addf,(cvtf,arg),2.0f);", code.print());
}
@Test
public void testBasic13() {
CodeGen code = new CodeGen("return arg - 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (subf,(cvtf,arg),2.0f);", code._stop.toString());
+ assertEquals("return (subf,(cvtf,arg),2.0f);", code.print());
}
@Test
public void testBasic14() {
CodeGen code = new CodeGen("return arg * 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (mulf,(cvtf,arg),2.0f);", code._stop.toString());
+ assertEquals("return (mulf,(cvtf,arg),2.0f);", code.print());
}
@Test
public void testBasic15() {
CodeGen code = new CodeGen("return arg / 2.0;").driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (mulf,(cvtf,arg),0.5f);", code._stop.toString());
+ assertEquals("return (mulf,(cvtf,arg),0.5f);", code.print());
}
@Test
@@ -164,7 +191,7 @@ val _hashCodeString = { String self ->
int arg1 = arg + 1;
return arg1 / arg;""");
code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (div,(inc,arg),arg);", code._stop.toString());
+ assertEquals("return (div,(inc,arg),arg);", code.print());
}
@Test
@@ -175,7 +202,7 @@ int arg1 = arg + 1;
return arg1 * arg;
""");
code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (mul,(inc,arg),arg);", code._stop.toString());
+ assertEquals("return (mul,(inc,arg),arg);", code.print());
}
@Test
@@ -185,7 +212,7 @@ int a = arg;
return a + 2.0;
"""
).driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return (addf,(cvtf,arg),2.0f);", code._stop.toString());
+ assertEquals("return (addf,(cvtf,arg),2.0f);", code.print());
}
@Test
@@ -234,10 +261,10 @@ return sum;""");
public void testAlloc1() {
CodeGen code = new CodeGen(
"""
-struct S { int a; S? c; };
-return new S;""");
+struct _S { int a; _S? c; };
+return new _S;""");
code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return S;", code.print());
+ assertEquals("return Test._S;", code.print());
}
@Test
@@ -325,7 +352,7 @@ return A[1];
public void testNewton() throws IOException {
String src =
"""
-val test_sqrt = { flt x ->
+val _test_sqrt = { flt x ->
flt epsilon = 1e-15;
flt guess = x;
while( 1 ) {
@@ -335,15 +362,16 @@ val test_sqrt = { flt x ->
guess = next;
}
};
-flt farg = arg; return test_sqrt(farg);
+flt farg = arg;
+return _test_sqrt(farg);
""";
CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,(divf,cvtf,Phi_guess),Phi_guess),0.5f));", code.print());
+ assertEquals("return Phi(Loop,(cvtf,arg),(mulf,(addf,Phi_guess,(divf,cvtf,Phi_guess)),0.5f));", code.print());
};
@Test
- public void sieveOfEratosthenes() throws IOException {
+ public void sieveOfEratosthenes() {
String src =
"""
val sieve = { int N ->
@@ -378,8 +406,8 @@ val sieve = { int N ->
};
""";
CodeGen code = new CodeGen(src).driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("return []u32;", code.print());
- //assertEquals("u32[ 2,3,5,7,11,13,17,19]",Eval2.eval(code, 20));
+ assertEquals("Stop[ return { sieve}; return []u32; ]", code.print());
+ //assertEquals("{ ptr i64 -> *[]u32 #[ 3]}",Eval2.eval(code, 20));
}
@@ -387,21 +415,21 @@ val sieve = { int N ->
public void testFcn1() {
CodeGen code = new CodeGen(
"""
-val fcn = arg ? { int x -> x*x; } : { int x -> x+x; };
-return fcn(2)*10 + fcn(3);
+val _fcn = arg ? { int x -> x*x; } : { int x -> x+x; };
+return _fcn(2)*10 + _fcn(3);
""");
code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun21,[2-3]),x); return (shli,Parm_x($fun22,[2-3])); ]", code.print());
+ assertEquals("Stop[ return (add,#2,(muli,#2)); return (mul,Parm_x($fun3,[2-3]),x); return (shli,Parm_x($fun4,[2-3])); ]", code.print());
}
@Test
public void testFcn2() {
CodeGen code = new CodeGen(
"""
-val sq = { int x -> x*x; };
-return sq(arg) + sq(3);
+val _sq = { int x -> x*x; };
+return _sq(arg) + _sq(3);
""");
code.driver(Phase.LocalSched,"x86_64_v2", "SystemV");
- assertEquals("Stop[ return (add,#2,#2); return (mul,Parm_x(sq,i64),x); ]", code.print());
+ assertEquals("return (addi,(mul,arg,arg));", code.print());
}
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter20Test.java b/src/test/java/com/seaofnodes/simple/Chapter20Test.java
index 02de57a2..17b0fd0a 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -1,7 +1,6 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
-import java.io.IOException;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -11,7 +10,7 @@ public class Chapter20Test {
public void testJig() {
CodeGen code = new CodeGen("return 0;");
code.parse().opto().typeCheck();
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 2));
}
@@ -20,10 +19,10 @@ public class Chapter20Test {
code.driver(CodeGen.Phase.RegAlloc,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
- assertEquals(stop, code._stop.toString());
+ assertEquals(stop, code.print());
}
private static void testAllCPUs( String src, int spills, String stop ) {
@@ -50,41 +49,40 @@ public class Chapter20Test {
String src =
"""
// Newtons approximation to the square root
-val sqrt = { int x ->
+val _sqrt = { int x ->
int guess = x;
while( 1 ) {
- int next = (x/guess + guess)/2;
+ int next = (x/guess + guess)>>1;
if( next == guess ) return guess;
guess = next;
}
};
-int cast_int = arg+2;
-return sqrt(arg) + sqrt(cast_int);
+return _sqrt(arg) + _sqrt(arg+2);
""";
- testCPU(src,"x86_64_v2", "Win64" ,55,null);
+ testCPU(src,"x86_64_v2", "win64" ,38,null);
testCPU(src,"riscv" , "SystemV",19,null);
testCPU(src,"arm" , "SystemV",19,null);
}
@Test
- public void testNewtonFloat() throws IOException {
+ public void testNewtonFloat() {
String src =
"""
-val test_sqrt = { flt x ->
+val _test_sqrt_noInline = { flt x ->
flt epsilon = 1e-15;
flt guess = x;
while( 1 ) {
flt next = (x/guess + guess)/2;
- if( guess-epsilon <= next & next <= guess+epsilon ) return guess;
- //if( guess==next ) return guess;
+ if( guess-epsilon <= next && next <= guess+epsilon )
+ return guess;
guess = next;
}
};
-flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);
+flt farg = arg; return _test_sqrt_noInline(farg) + _test_sqrt_noInline(farg+2.0);
""";
- testCPU(src,"x86_64_v2", "SystemV",39,null);
- testCPU(src,"riscv" , "SystemV",17,null);
- testCPU(src,"arm" , "SystemV",18,null);
+ testCPU(src,"x86_64_v2", "SystemV",65,null);
+ testCPU(src,"riscv" , "SystemV",35,null);
+ testCPU(src,"arm" , "SystemV",35,null);
}
@Test
@@ -98,7 +96,7 @@ flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);
@Test
- public void testArray1() throws IOException {
+ public void testArray1() {
String src =
"""
int[] !ary = new int[arg];
@@ -111,13 +109,13 @@ for( int i=0; i<ary#-1; i++ )
return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
""";
testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
- testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));");
+ testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));");
testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
}
@Test
- public void testString() throws IOException {
+ public void testString() {
String src =
"""
struct String {
@@ -150,17 +148,17 @@ val _hashCodeString = { String self ->
return hash;
};
""";
- testCPU(src,"x86_64_v2", "SystemV", 9,null);
- testCPU(src,"riscv" , "SystemV", 3,null);
- testCPU(src,"arm" , "SystemV", 3,null);
+ testCPU(src,"x86_64_v2", "SystemV",18,null);
+ testCPU(src,"riscv" , "SystemV", 5,null);
+ testCPU(src,"arm" , "SystemV", 4,null);
}
@Test
public void testCast() {
String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;";
- testCPU(src,"x86_64_v2", "SystemV",2,null);
- testCPU(src,"riscv" , "SystemV",2,null);
- testCPU(src,"arm" , "SystemV",2,null);
+ testCPU(src,"x86_64_v2", "SystemV",3,null);
+ testCPU(src,"riscv" , "SystemV",3,null);
+ testCPU(src,"arm" , "SystemV",3,null);
}
@Test
@@ -172,15 +170,15 @@ val _hashCodeString = { String self ->
@Test
public void testFlags1() {
String src = """
-bool b1 = arg == 1;
+bool _b1 = arg == 1;
bool b2 = arg == 2;
-if (b2) if (b1) return 1;
-if (b1) return 2;
+if (b2) if (_b1) return 1;
+if (_b1) return 2;
return 0;
""";
- testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);");
- testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);");
- testCPU(src,"arm" , "SystemV",0,"return Phi(Region,1,2,0);");
+ testCPU(src,"x86_64_v2", "SystemV",0,"return Phi(Region,2,0,1);");
+ testCPU(src,"riscv" , "SystemV",0,"return Phi(Region,2,0,1);");
+ testCPU(src,"arm" , "SystemV",0,"return Phi(Region,2,0,1);");
}
@Test
diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index ec55735e..e4d49e7f 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -5,9 +5,6 @@ import com.seaofnodes.simple.node.cpus.arm.arm;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
import com.seaofnodes.simple.util.SB;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -17,24 +14,24 @@ public class Chapter21Test {
public void testJig() throws IOException {
String src =
"""
-struct s0 {
+struct _s0 {
bool v1;
i16 v2;
int v3;
i8 v4;
byte v5;
};
-while(new s0.v3)
- while(new s0.v5<<new s0.v4) {}
+while(new _s0.v3)
+ while(new _s0.v5<<new _s0.v4) {}
if(0) {
if(0) {
- flt !P5ZUD4=new s0.v2;
+ flt !P5ZUD4=new _s0.v2;
}
while(0) {}
}
-return new s0.v1;
+return new _s0.v1;
""";
- testCPU(src,"x86_64_v2", "Win64" ,-1,null);
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
testCPU(src,"riscv" , "SystemV",-1,null);
testCPU(src,"arm" , "SystemV",-1,null);
}
@@ -43,10 +40,10 @@ return new s0.v1;
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
- assertEquals(stop, code._stop.toString());
+ assertEquals(stop, code.print());
}
@@ -58,14 +55,14 @@ return new s0.v1;
}
@Test public void testInfinite() {
- String src = "struct S { int i; }; S !s = new S; while(1) s.i++; return s.i;";
+ String src = "struct _S { int i; }; _S !s = new _S; while(1) s.i++; return s.i;";
testCPU(src,"x86_64_v2", "SystemV",0,"return Top;");
testCPU(src,"riscv" , "SystemV",2,"return Top;");
testCPU(src,"arm" , "SystemV",2,"return Top;");
}
@Test
- public void testArray1() throws IOException {
+ public void testArray1() {
String src =
"""
int[] !ary = new int[arg];
@@ -83,13 +80,13 @@ return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
}
@Test
- public void testAntiDeps1() throws IOException {
+ public void testAntiDeps1() {
String src =
"""
-struct S { int f; };
-var v0 = new S;
-S? v1;
-if (arg) v1 = new S;
+struct _S { int f; };
+var v0 = new _S;
+_S? v1;
+if (arg) v1 = new _S;
if (v1) {
v0.f = v1.f;
} else {
@@ -97,13 +94,13 @@ if (v1) {
}
return v0;
""";
- testCPU(src,"x86_64_v2", "SystemV", 7,"return mov(mov(S));");
- testCPU(src,"riscv" , "SystemV",10,"return mov(mov(S));");
- testCPU(src,"arm" , "SystemV",10,"return mov(mov(S));");
+ testCPU(src,"x86_64_v2", "SystemV",10,"return mov(mov(Test._S));");
+ testCPU(src,"riscv" , "SystemV",10,"return mov(mov(Test._S));");
+ testCPU(src,"arm" , "SystemV",10,"return mov(mov(Test._S));");
}
@Test
- public void testString() throws IOException {
+ public void testString() {
String src =
"""
struct String {
@@ -136,9 +133,9 @@ val _hashCodeString = { String self ->
return hash;
};
""";
- testCPU(src,"x86_64_v2", "SystemV", 9,null);
- testCPU(src,"riscv" , "SystemV", 3,null);
- testCPU(src,"arm" , "SystemV", 3,null);
+ testCPU(src,"x86_64_v2", "SystemV",18,null);
+ testCPU(src,"riscv" , "SystemV", 5,null);
+ testCPU(src,"arm" , "SystemV", 4,null);
}
@Test public void testStringExport() throws IOException {
@@ -174,10 +171,10 @@ val _hashCodeString = { String self ->
return hash;
};
""";
- TestC.run(src, "stringHash",null, "", 9);
+ TestC.runC(src, "stringHash", "",18);
}
- @Test public void testLoop2() throws IOException {
+ @Test public void testLoop2() {
String src =
"""
int i = 0;
@@ -190,9 +187,9 @@ while(true) {
}
return i;
""";
- testCPU(src,"x86_64_v2", "Win64" ,0,"return (inc,Phi(Loop,0,inc));");
- testCPU(src,"riscv" , "SystemV",0,"return ( Phi(Loop,0,addi) + #1 );");
- testCPU(src,"arm" , "SystemV",0,"return (inc,Phi(Loop,0,inc));");
+ testCPU(src,"x86_64_v2", "win64" ,0,"return (!=0)(inc,Phi(Loop,0,Phi(Region,(!=0),(!=0),0)));");
+ testCPU(src,"riscv" , "SystemV",0,"return (!=0)( Phi(Loop,0,Phi(Region,(!=0),(!=0),0)) + #1 );");
+ testCPU(src,"arm" , "SystemV",0,"return (!=0)(inc,Phi(Loop,0,Phi(Region,(!=0),(!=0),0)));");
}
@Test public void testNewtonExport() throws IOException {
@@ -221,9 +218,9 @@ val test_sqrt = { flt x ->
}
};
""";
- TestC.run(src, "newtonFloat", null, result, 34);
+ TestC.runC(src, "newtonFloat", result, 42);
- EvalRisc5 R5 = TestRisc5.build("newtonFloat",src, 0, 10, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "test_sqrt", 0, 10, false);
R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 3.0;
int trap_r5 = R5.step(1000);
assertEquals(0,trap_r5);
@@ -231,7 +228,7 @@ val test_sqrt = { flt x ->
assertEquals(1.732051,R5.fregs[riscv.FA0 - riscv.F_OFFSET], 0.00001);
// arm
- EvalArm64 A5 = TestArm64.build("newtonFloat", src,0, 10, false);
+ EvalArm64 A5 = TestArm64.build("test_sqrt", src,0, 10, false);
A5.fregs[arm.D0 - arm.D_OFFSET] = 3.0;
int trap_arm = A5.step(1000);
assertEquals(0,trap_arm);
@@ -281,11 +278,11 @@ val sieve = { int N ->
String sprimes = sb.p("]").toString();
// Compile, link against native C; expect the above string of primes to be printed out by C
- TestC.run(src, "sieve", null, sprimes, 257);
+ TestC.runC(src, "sieve", sprimes, 186);
// Evaluate on RISC5 emulator; expect return of an array of primes in
// the simulated heap.
- EvalRisc5 R5 = TestRisc5.build("sieve", src, 100, 160, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "sieve", 100, 90, false);
int trap = R5.step(10000);
assertEquals(0,trap);
// Return register A0 holds sieve(100)
@@ -297,7 +294,7 @@ val sieve = { int N ->
// Evaluate on ARM5 emulator; expect return of an array of primes in
// the simulated heap.
- EvalArm64 A5 = TestArm64.build("sieve", src, 100, 160, false);
+ EvalArm64 A5 = TestArm64.build("sieve", src, 100, 105, false);
int trap_arm = A5.step(10000);
assertEquals(0, trap_arm);
int ary_arm = (int)A5.regs[arm.X0];
@@ -310,7 +307,7 @@ val sieve = { int N ->
@Test public void testFibExport() throws IOException {
String src =
"""
-val fib = {int n ->
+val fib = { int n ->
int f1=1;
int f2=1;
while( n-- > 1 ){
@@ -322,9 +319,9 @@ val fib = {int n ->
};
""";
String fib = "[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]";
- TestC.run(src, "fib", null, fib, 24);
+ TestC.runC(src, "fib", fib, 24);
- EvalRisc5 R5 = TestRisc5.build("fib", src, 9, 17, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "fib", 9, 17, false);
int trap = R5.step(100);
assertEquals(0,trap);
// Return register A0 holds fib(8)==55
@@ -351,7 +348,7 @@ val fib = {int n ->
};
""";
String person = "6\n";
- TestC.run(src, "person", null, person, 0);
+ TestC.run(src, "person1", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2);
// Memory layout starting at PS:
int ps = 1<<16; // Person array pointer starts at heap start
@@ -362,7 +359,7 @@ val fib = {int n ->
int p1 = ps+4*8+1*8;
// P2 = { age } // sizeof=8
int p2 = ps+4*8+2*8;
- EvalRisc5 R5 = TestRisc5.build("person", src, ps, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 0, false);
R5.regs[riscv.A1] = 1; // Index 1
R5.st8(ps,3); // Length
R5.st8(ps+1*8,p0);
@@ -378,7 +375,7 @@ val fib = {int n ->
assertEquals(17+1,R5.ld8(p1));
assertEquals(60+0,R5.ld8(p2));
- EvalArm64 A5 = TestArm64.build("person", src, ps, 0, false);
+ EvalArm64 A5 = TestArm64.build("fcn", src, ps, 0, false);
A5.regs[arm.X1] = 1; // Index 1
A5.st8(ps, 3);
A5.st8(ps+1*8,p0);
@@ -396,8 +393,8 @@ val fib = {int n ->
}
@Test public void testArgCount() throws IOException {
- // Test passes more args than registers in Sys5, which is far far more
- // than what Win64 allows - so Win64 gets a lot more spills here.
+ // Test passes more args than registers in Sys5, which is far, far more
+ // than what win64 allows - so win64 gets a lot more spills here.
String src =
"""
val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15, int x16, flt f17 int x18, flt f19 ->
@@ -408,9 +405,9 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i
""";
String arg_count = "191.000000\n";
- TestC.run(src, "arg_count", null, arg_count, TestC.CALL_CONVENTION.equals("Win64") ? 42 : 15);
+ TestC.runC(src, "arg_count", arg_count, TestC.CALL_CONVENTION.equals("win64") ? 31 : 15);
- EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", src, 0, 4, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "addAll", 0, 4, false);
// Todo: handle stack(imaginary stack in emulator)
// pass in float arguments
@@ -423,14 +420,13 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i
R5.fregs[riscv.FA6 - riscv.F_OFFSET] = 1.1;
R5.fregs[riscv.FA7 - riscv.F_OFFSET] = 1.1;
- // a0 is passed in arg
+ R5.regs[riscv.A0] = 2;
R5.regs[riscv.A1] = 2;
R5.regs[riscv.A2] = 2;
R5.regs[riscv.A3] = 2;
R5.regs[riscv.A4] = 2;
R5.regs[riscv.A5] = 2;
R5.regs[riscv.A6] = 2;
- R5.regs[riscv.A7] = 2;
int trap = R5.step(100);
assertEquals(0,trap);
@@ -440,7 +436,7 @@ val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, i
assertEquals(22.8, result, 0.00001);
// arm
- EvalArm64 A5 = TestArm64.build("no_stack_arg_count", src, 0, 4, false);
+ EvalArm64 A5 = TestArm64.build("addAll", src, 0, 4, false);
A5.fregs[arm.D0 - arm.D_OFFSET] = 1.1;
A5.fregs[arm.D1 - arm.D_OFFSET] = 1.1;
diff --git a/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/src/test/java/com/seaofnodes/simple/Chapter22Test.java
index 78b9e7a1..91b8310d 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -1,12 +1,10 @@
package com.seaofnodes.simple;
+import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.codegen.CodeGen;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
import com.seaofnodes.simple.node.cpus.arm.arm;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import java.io.IOException;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -15,25 +13,25 @@ public class Chapter22Test {
@Test
public void testJig() throws IOException {
- String src =
-"""
-val fib = {int n ->
- int temp=0;
- int f1=1;
- int f2=1;
- int i=n;
- while( i>1 ){
- temp = f1+f2;
- f1=f2;
- f2=temp;
- i=i-1;
- }
- return f2;
+ String src = """
+struct s0 {
+ bool v1;
+ i16 v2;
+ int v3;
+ i8 v4;
+ byte v5;
};
-
-fib(10);
+while(new s0.v3)
+ while(new s0.v5<<new s0.v4) {}
+if(0) {
+ if(0) {
+ flt !P5ZUD4=new s0.v2;
+ }
+ while(0) {}
+}
+return new s0.v1;
""";
- testCPU(src,"x86_64_v2", "Win64" ,-1,null);
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
testCPU(src,"riscv" , "SystemV",-1,null);
testCPU(src,"arm" , "SystemV",-1,null);
}
@@ -42,32 +40,32 @@ fib(10);
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
- assertEquals(stop, code._stop.toString());
+ assertEquals(stop, code.print());
return code;
}
- static int testCPUSize( String src, String cpu, String os, int spills, String stop ) {
- return testCPU(src,cpu,os,spills,stop)._encoding._bits.size();
+ static int testCPUSize( String src, String cpu, String os, int spills ) {
+ return testCPU(src,cpu,os,spills, "return 0;" )._encoding._bits.size();
}
// Should not fold away
@Test public void testSextFail() throws IOException {
String src = """
-struct Person { i32 age;};
-Person !p = new Person;
+struct _Person { i32 age;};
+_Person !p = new _Person;
p.age = (arg<<17)>>17;
return 0;
""";
- assertEquals(46, testCPUSize(src, "x86_64_v2","Win64",2,"return 0;"));
- assertEquals(60, testCPUSize(src, "riscv","SystemV",4,"return 0;"));
- assertEquals(56, testCPUSize(src, "arm","SystemV",4,"return 0;"));
+ assertEquals(83, testCPUSize(src, "x86_64_v2","win64", 3 ));
+ assertEquals(96, testCPUSize(src, "riscv", "SystemV", 4 ));
+ assertEquals(96, testCPUSize(src, "arm" , "SystemV", 4 ));
// do assertEquals here
- EvalRisc5 R5 = TestRisc5.build("sext_str_not_fold_away", src,0, 4, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away", 0, 4, false);
int trap = R5.step(100);
assertEquals(0,trap);
@@ -80,14 +78,13 @@ return 0;
// Should not fold away
@Test public void testSextFail2() throws IOException {
String src = """
-
- struct Person { i32 age;};
- Person !p = new Person;
+ struct _Person { i32 age;};
+ _Person !p = new _Person;
p.age = (arg<<48)>>48;
return 0;
""";
- EvalRisc5 R5 = TestRisc5.build("sext_str_not_fold_away_2", src, 0, 4, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away_2", 0, 4, false);
int trap = R5.step(100);
assertEquals(0,trap);
@@ -95,9 +92,9 @@ return 0;
int trap_arm = A5.step(100);
assertEquals(0,trap_arm);
- assertEquals(46, testCPUSize(src, "x86_64_v2","Win64",2,"return 0;"));
- assertEquals(60, testCPUSize(src, "riscv","SystemV",4,"return 0;"));
- assertEquals(56, testCPUSize(src, "arm","SystemV",4,"return 0;"));
+ assertEquals(83, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(96, testCPUSize(src, "riscv", "SystemV",4 ));
+ assertEquals(96, testCPUSize(src, "arm", "SystemV",4 ));
}
@@ -105,13 +102,13 @@ return 0;
@Test public void testSextSuccess() throws IOException {
String src = """
// Should fold away sign extend
-struct Person { i8 age;};
-Person !p = new Person;
+struct _Person { i8 age;};
+_Person !p = new _Person;
p.age = (arg<<48)>>48;
return 0;
- """;
+""";
- EvalRisc5 R5 = TestRisc5.build("sext_str_fold_away", src, 0, 5, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "sext_str_fold_away", 0, 5, false);
int trap = R5.step(100);
assertEquals(0,trap);
@@ -119,9 +116,9 @@ return 0;
int trap_arm = A5.step(100);
assertEquals(0,trap_arm);
- assertEquals(41, testCPUSize(src, "x86_64_v2","Win64",2,"return 0;"));
- assertEquals(56, testCPUSize(src, "riscv","SystemV",5,"return 0;"));
- assertEquals(52, testCPUSize(src, "arm","SystemV",5,"return 0;"));
+ assertEquals(71, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(92, testCPUSize(src, "riscv", "SystemV",5 ));
+ assertEquals(92, testCPUSize(src, "arm", "SystemV",5 ));
// do assertEquals here
}
@@ -130,17 +127,17 @@ return 0;
@Test public void testPerson() throws IOException {
String src =
"""
-struct Person {
+struct Person { // Exports a field typed singleton+final *class:Person{age:i32}
i32 age;
};
-val fcn = { Person?[] ps, int x ->
+val fcn = { Person?[] ps, int x -> // exports a field with a constant fcn ptr; exports the fcn also
if( ps[x] )
ps[x].age++;
};
""";
String person = "6\n";
- TestC.run(src, "person", null, person, 0);
+ TestC.run(src, "person2", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2);
// Memory layout starting at PS:
int ps = 1<<16; // Person array pointer starts at heap start
@@ -151,7 +148,7 @@ val fcn = { Person?[] ps, int x ->
int p1 = ps+4*8+1*8;
// P2 = { age } // sizeof=8
int p2 = ps+4*8+2*8;
- EvalRisc5 R5 = TestRisc5.build("person", src, ps, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 0, false);
R5.regs[riscv.A1] = 1; // Index 1
R5.st8(ps,3); // Length
R5.st8(ps+1*8,p0);
@@ -167,7 +164,7 @@ val fcn = { Person?[] ps, int x ->
assertEquals(17+1,R5.ld8(p1));
assertEquals(60+0,R5.ld8(p2));
- EvalArm64 A5 = TestArm64.build("person", src, ps, 0, false);
+ EvalArm64 A5 = TestArm64.build("fcn", src, ps, 0, false);
A5.regs[arm.X1] = 1; // Index 1
A5.st8(ps, 3);
A5.st8(ps+1*8,p0);
@@ -187,41 +184,47 @@ val fcn = { Person?[] ps, int x ->
@Test
public void testCoRecur() {
String src = """
-struct A { B? b; C? c; i64 ax; val az = x*2; };
-struct B { A? a; C? c; f32 bx; val bz = x*3; };
-struct C { A? a; B? b; f64 cx; val cz = x*x; };
-A !aa = new A{ ax=17; };
-B !bb = new B{ bx=3.14; a = aa; };
-C !cc = new C{ cx=2.73; a = aa; b = bb; };
+val x = 5; // aa.az; // Error to self-define forward ref
+struct _A { Test._B? b; Test._C? c; i64 ax; val az = x*2; new _A = { i64 x -> ax=x; }; };
+struct _B { Test._A? a; Test._C? c; f32 bx; val bz = x*3; new _B = { f32 x, Test._A? aa -> bx=x; a=aa; }; };
+struct _C { Test._A? a; Test._B? b; f64 cx; val cz = x*x; new _C = { f64 x, Test._A? aa, Test._B? bb -> cx=x; a=aa; b=bb; }; };
+_A !aa = new _A(17);
+_B !bb = new _B(3.14f, aa);
+_C !cc = new _C(2.73, aa, bb);
aa.b = bb;
aa.c = cc;
bb.c = cc;
-val x = 5; // aa.az; // Error to self-define forward ref
return cc.cz;
""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return 25;", code._stop.toString());
+ assertEquals("return 25;", code.print());
assertEquals("25", Eval2.eval(code, 0));
}
@Test
public void testHelloWorld() throws IOException {
- String src =
-"""
-sys.io.p("Hello, World!");
+ String src = """
+// fd buf len -> len
+{ i32 i64 u32 -> u32 } write = "C";
+// Write byte array to stdout
+val p = { u8[~] str ->
+ i64 ptr = str; // cast array base to i64
+ return write(1,ptr,str#);
+};
+p("Hello, World!");
return 0;
""";
- TestC.run(src,TestC.CALL_CONVENTION,null, null,null,"build/objs/helloWorld","","Hello, World!",0);
+ TestC.runSF(src,"helloWorld", "Hello, World!",2);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("helloWorld", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "helloWorld", 0, 5, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
assertEquals("Hello, World!",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("helloWorld", src,0, 2, false);
+ EvalArm64 arm = TestArm64.build("helloWorld", src,0, 5, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
@@ -235,16 +238,16 @@ int N=4;
i32[] !is = new i32[N];
for( int i=0; i<N; i++ )
is[i] = i*i;
-val sum = { i32[~] is -> // final array
+val _sum = { i32[~] is -> // final array
int sum=0;
for( int i=0; i<is#; i++ )
sum += is[i];
return sum;
};
-return sum(is);
+return _sum(is);
""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code._stop.toString());
+ assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code.print());
assertEquals("14", Eval2.eval(code, 0));
}
@@ -256,10 +259,10 @@ return sum(is);
// Echo stdin to stdout.
return sys.io.p( sys.io.stdin() );
""";
- TestC.run(src,TestC.CALL_CONVENTION,null, null,null,"build/objs/echo","","",0);
+ TestC.runSF(src,"echo", "",0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("echo", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "echo", 0, 2, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
diff --git a/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/src/test/java/com/seaofnodes/simple/Chapter23Test.java
index 94e13a87..08acfd32 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter23Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -2,7 +2,6 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
-import com.seaofnodes.simple.type.TypeInteger;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -12,11 +11,11 @@ import static org.junit.Assert.*;
public class Chapter23Test {
- @Test
+ @Test @Ignore
public void testJig() throws IOException {
//String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
String src = Files.readString(Path.of("docs/examples/BubbleSort.smp"));
- testCPU(src,"x86_64_v2", "Win64" ,-1,null);
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
testCPU(src,"riscv" , "SystemV",-1,null);
testCPU(src,"arm" , "SystemV",-1,null);
}
@@ -26,278 +25,218 @@ public class Chapter23Test {
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
- assertEquals(stop, code._stop.toString());
+ assertEquals(stop, code.print());
return code;
}
@Test
public void testOr() throws IOException {
- String src =
- """
- int a = 1;
- int b = 0;
-
- if(a++ || b++ ) {
- if(b == 0 && a == 2) {
- sys.io.p("Or");
- }
- } else{
- sys.io.p("And");
- }
- return 0;
- """;
-
- TestC.runSF("or1", src, null, "Or", 0);
+ String src = """
+int a = 1;
+int b = 0;
+int rez = 2;
+
+if( a++ || b++ ) {
+ if( b == 0 && a == 2 )
+ rez = 0; // Expected answer
+} else {
+ rez = 1;
+}
+return rez;
+""";
+
+ TestC.runSF( src, "or1", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("or1", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "or1", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Or",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("or1", src,0, 2, false);
+ EvalArm64 arm = TestArm64.build("or1", src,0, 0, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("Or",arm._stdout.toString());
}
@Test
public void testAnd() throws IOException {
- String src =
- """
- int a = 1;
- int b = 1;
-
- if(a && b) {
- sys.io.p("And");
- } else {
- sys.io.p("Or");
- }
- return 0;
- """;
- TestC.runSF("and1", src, null, "And", 0);
+ String src = """
+int a = 1;
+int b = 1;
+return a && b ? 0 : 1; // Expected answer 0
+""";
+ TestC.runSF( src, "and1", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("and1", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "and1", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("And",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("and1", src, 0, 2, false);
+ EvalArm64 arm = TestArm64.build("and1", src, 0, 0, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("And",arm._stdout.toString());
}
@Test
public void testAndPtr() throws IOException {
// Todo: have one src here
- String src =
- """
- struct S { S? fld; };
- val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};};
- return ptr && ptr.fld ? "true" : "false";
- """;
- String src2 =
- """
- struct S { S? fld; };
-
- val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};};
- if( ptr && ptr.fld ) {
- sys.io.p("true");
- } else {
- sys.io.p("false");
- }
-
- return 0;
- """;
+ String src = """
+struct S { S? fld; new S = { S? f -> fld=f; }; };
+val ptr = arg == 1 ? null : new S(arg==1 ? null : new S(null));
+return ptr && ptr.fld ? "true" : "false";
+""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("[97-117][ 116,114,117,101]", Eval2.eval(code, 0));
+ assertEquals("true", Eval2.eval(code, 0));
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("ptrand",src2, 1, 8, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "class:Test.<clinit>", 1, 8, false);
int trap = R5.step(100);
assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("false",R5._stdout.toString());
+ int adr = (int)R5.regs[riscv.A0]; // Returns a Simple *u8[~] string
+ assertEquals(4,R5.ld4s(adr)); // String length
+ assertEquals(0x65757274,R5.ld4s(adr+4)); // "true"
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("ptrand", src2, 1, 8, false);
+ EvalArm64 arm = TestArm64.build("class:Test.<clinit>", src, 1, 8, false);
trap = arm.step(100);
assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("false",arm._stdout.toString());
+ int adr2 = (int)arm.regs[0]; // Returns a Simple *u8[~] string
+ assertEquals(4,arm.ld4s(adr2)); // String length
+ assertEquals(0x65757274,arm.ld4s(adr2+4)); // "true"
}
// conditional side effect
@Test
public void testCondSideEffAnd() throws IOException {
- String src =
- """
- int a = 1;
- int b = 1;
-
- int x=1;
- int y=1;
- int z=0;
-
- int g = x++ && y++ && z++;
-
- if(x == 2 && y == 2 && z == 1 && g == 0) {
- sys.io.p("Effected");
- } else {
- sys.io.p("Not effected");
- }
- return 0;
- """;
-
- TestC.runSF("and2", src, null, "Effected", 0);
+ String src = """
+int x=1;
+int y=1;
+int z=0;
+
+int g = x++ && y++ && z++;
+return x == 2 && y == 2 && z == 1 && g == 0
+ ? 0 // Effected, Expected
+ : 1; // Error, not effected
+""";
+
+ TestC.runSF( src, "and2", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("and2", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "and2", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Effected",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("and2", src, 0, 2, false);
+ EvalArm64 arm = TestArm64.build("and2", src, 0, 0, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("Effected",arm._stdout.toString());
}
@Test
public void testCondSideEffOr() throws IOException {
- String src =
- """
- int a = 1;
- int b = 1;
-
- int x = -1;
- int y=1;
- int z= -1;
-
- int g = x++ || y++ || z++;
-
- int switch = 0;
- if(x == 4 || y == 4 || z == 4) {
- switch = -1;
- } else {
- switch = 1;
- }
- int cd = 1;
- if((x == 0 && y == 1 && z==-1) && switch && g) {
- sys.io.p("Effected");
- } else {
- sys.io.p("Not effected");
- }
- return 0;
- """;
- TestC.runSF("or2", src, null, "Effected", 0);
+ String src = """
+int x = -1;
+int y = 1;
+int z = -1;
+int g = x++ || y++ || z++;
+int switch = (x == 4 || y == 4 || z == 4) ? -1 : 1;
+return (x == 0 && y == 1 && z==-1) && switch && g
+ ? 0 // Effected, expected
+ : 1; // Error
+""";
+ TestC.runSF( src, "or2", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("or2", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "or2", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Effected",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("or2", src, 0, 2, false);
+ EvalArm64 arm = TestArm64.build("or2", src, 0, 0, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("Effected",arm._stdout.toString());
}
// test it with function calls
@Test
public void testFuncCall() throws IOException {
- String src =
- """
- // -*- mode: java; -*-
- int a = 1;
- int b = 1;
-
- var sq = { int x ->
- x*x;
- };
-
- if(a && sq(0)) {
- sys.io.p("And");
- } else {
- sys.io.p("Or");
- }
- return 0;
- """;
- TestC.runSF("and3", src, null, "Or", 0);
+ String src = """
+int a = 1;
+int b = 1;
+var sq_noInline = { int x ->
+ x*x;
+};
+return (a && sq_noInline(0))
+ ? 1 // Error
+ : 0; // Expected
+""";
+ TestC.runSF( src, "and3", "", 2);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("and3", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "and3", 0, 5, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Or",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("and3", src, 0, 2, false);
+ EvalArm64 arm = TestArm64.build("and3", src, 0, 5, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("Or",arm._stdout.toString());
}
@Test
public void testFRefFields() {
- String src =
-"""
+ String src = """
// A hypothetical scanner class
-struct Scan {
+struct _Scan {
int !x;
u8[~] buf;
+ new _Scan = { u8[~] b -> buf=b; };
// Skip whitespace
- val skip = { Scan s ->
- while( s.buf[s.x] <= ' ' )
- s.x++;
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
};
// Peek a character; if matched consume it, else false.
- val peek = { Scan s, u8 c ->
- skip(s);
- if( s.buf[s.x] != c ) return false;
- s.x++;
+ val peek = { u8 c ->
+ skip();
+ if( buf[x] != c ) return false;
+ x++;
return true;
};
};
-Scan !s = new Scan{ buf = " q"; };
-return Scan.peek(s,'q');
- """;
+_Scan !_s = new _Scan(" q");
+return _s.peek('q');
+""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return Phi(Region,0,1);", code._stop.toString());
+ assertEquals("return Phi(Region,0,1);", code.print());
assertEquals("1", Eval2.eval(code, 0));
- testCPU(src,"x86_64_v2", "Win64" ,17,null);
+ testCPU(src,"x86_64_v2", "win64",1,null);
};
@Test
public void testFRefFields2() {
- String src =
-"""
+ String src ="""
// A hypothetical scanner class
-struct Scan {
+struct _Scan {
int !x;
u8[~] buf;
+ new _Scan = { u8[~] b -> buf=b; };
// Skip whitespace
val skip = { ->
while( buf[x] <= ' ' )
@@ -305,23 +244,26 @@ struct Scan {
return self;
};
};
-val s = new Scan{ buf = " q"; };
+val s = new _Scan(" q");
return s.skip().x;
- """;
+""";
try { new CodeGen(src).parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Argument #0 isa *Scan {i64 x; *[]u8 buf; { *Scan -> *Scan {i64 !x; *[]u8 buf; {21} skip; } #21} skip; }, but must be a *Scan {i64 !x; *[]u8 buf; ... }",e.getMessage()); }
+ catch( Exception e ) {
+ // Bad error message, but basically requires a mutable 'x' field.
+ assertEquals("Argument #0 isa *Test._Scan {i64 x; *[]u8 buf; { *Test._Scan -> *Test._Scan {i64 !x; *[]u8 buf; {[ 5]} skip; } #[ 5]} skip; }, but must be a *Test._Scan {i64 !x; *[]u8 buf; { *Test._Scan -> *Test._Scan #[ 5]} skip; }",e.getMessage());
+ }
};
@Test
public void testMethod() {
- String src =
-"""
+ String src = """
// A hypothetical scanner class
-struct Scan {
+struct _Scan {
int !x;
u8[~] buf;
+ new _Scan = { u8[~] b -> buf=b; };
// Skip whitespace
val skip = { ->
while( buf[x] <= ' ' )
@@ -332,14 +274,14 @@ struct Scan {
buf[x++]==ch;
};
};
-Scan !s = new Scan{ buf = " [1,2]"; };
-return s.require('[');
- """;
+_Scan !_s = new _Scan(" [1,2]");
+return _s.require('[');
+""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return (.[]==91);", code._stop.toString());
+ assertEquals("return (.[]==91);", code.print());
assertEquals("1", Eval2.eval(code, 0));
- testCPU(src,"x86_64_v2", "Win64" ,16,null);
+ testCPU(src,"x86_64_v2", "win64", 0, null);
};
diff --git a/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 1d67949a..fe8aaf2e 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -11,7 +11,7 @@ public class Chapter24Test {
@Test
public void testJig() throws IOException {
String src = "int v0=0; v0 = arg && 0== 0 !=0;";
- testCPU(src,"x86_64_v2", "Win64" ,-1,null);
+ testCPU(src,"x86_64_v2", "win64" ,-1,null);
testCPU(src,"riscv" , "SystemV",-1,null);
testCPU(src,"arm" , "SystemV",-1,null);
}
@@ -20,428 +20,101 @@ public class Chapter24Test {
CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
if( stop != null )
- assertEquals(stop, code._stop.toString());
+ assertEquals(stop, code.print());
return code;
}
- @Test
- public void testOr() throws IOException {
- String src =
-"""
-int a = 1;
-int b = 0;
-
-if(a++ || b++ ) {
- if(b == 0 && a == 2) {
- sys.io.p("Or");
- }
-} else{
- sys.io.p("And");
-}
-return 0;
-""";
-
- TestC.runSF("or1", src, null, "Or", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("or1", src, 0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Or",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("or1", src,0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("Or",arm._stdout.toString());
- }
-
- @Test
- public void testAnd() throws IOException {
- String src =
-"""
-int a = 1;
-int b = 1;
-
-if(a && b) {
- sys.io.p("And");
-} else {
- sys.io.p("Or");
-}
-return 0;
-""";
- TestC.runSF("and1", src, null, "And", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("and1", src, 0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("And",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("and1", src, 0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("And",arm._stdout.toString());
- }
-
- @Test
- public void testAndPtr() throws IOException {
- // Todo: have one src here
- String src =
-"""
-struct S { S? fld; };
-val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};};
-return ptr && ptr.fld ? "true" : "false";
-""";
- String src2 =
-"""
-struct S { S? fld; };
-
-val ptr = arg == 1 ? null : new S{fld = arg==1 ? null : new S{fld = null;};};
-if( ptr && ptr.fld ) {
- sys.io.p("true");
-} else {
- sys.io.p("false");
-}
-
-return 0;
-""";
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("[97-117][ 116,114,117,101]", Eval2.eval(code, 0));
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("ptrand",src2, 1, 8, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("false",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("ptrand", src2, 1, 8, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("false",arm._stdout.toString());
- }
-
- // conditional side effect
- @Test
- public void testCondSideEffAnd() throws IOException {
- String src =
-"""
-int a = 1;
-int b = 1;
-
-int x=1;
-int y=1;
-int z=0;
-
-int g = x++ && y++ && z++;
-
-if(x == 2 && y == 2 && z == 1 && g == 0) {
- sys.io.p("Effected");
-} else {
- sys.io.p("Not effected");
-}
-return 0;
-""";
-
- TestC.runSF("and2", src, null, "Effected", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("and2", src, 0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Effected",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("and2", src, 0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("Effected",arm._stdout.toString());
- }
-
-
- @Test
- public void testCondSideEffOr() throws IOException {
- String src =
-"""
-int a = 1;
-int b = 1;
-
-int x = -1;
-int y=1;
-int z= -1;
-
-int g = x++ || y++ || z++;
-
-int switch = 0;
-if(x == 4 || y == 4 || z == 4) {
- switch = -1;
-} else {
- switch = 1;
-}
-int cd = 1;
-if((x == 0 && y == 1 && z==-1) && switch && g) {
- sys.io.p("Effected");
-} else {
- sys.io.p("Not effected");
-}
-return 0;
-""";
- TestC.runSF("or2", src, null, "Effected", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("or2", src, 0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Effected",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("or2", src, 0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("Effected",arm._stdout.toString());
- }
-
- // test it with function calls
- @Test
- public void testFuncCall() throws IOException {
- String src =
-"""
-// -*- mode: java; -*-
-int a = 1;
-int b = 1;
-
-var sq = { int x ->
- x*x;
-};
-
-if(a && sq(0)) {
- sys.io.p("And");
-} else {
- sys.io.p("Or");
-}
-return 0;
-""";
- TestC.runSF("and3", src, null, "Or", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("and3", src, 0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Or",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("and3", src, 0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("Or",arm._stdout.toString());
- }
-
// test it with function calls
@Test
public void testStack1() throws IOException {
- String src =
-"""
+ String src ="""
int something_specific = 11;
-
-if (10 < something_specific < 20) {
- sys.io.p("In Range");
-} else {
- sys.io.p("Out of Range");
-}
-return 0;
-""";
-
- TestC.runSF("stacked_r_1", src, null, "In Range", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_1", src, 0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("In Range",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("stacked_r_1", src, 0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("In Range",arm._stdout.toString());
- }
-
- @Test
- public void testStack2() throws IOException {
- String src =
-"""
-int score = 75;
-
-if (60 <= score < 90) {
- sys.io.p("In Range");
-} else {
-sys.io.p("Out of range");
-}
-return 0;
+return (10 < something_specific < 20)
+ ? 0 // Expected
+ : 1; // Error
""";
-
- TestC.runSF("stacked_r_2", src, null, "In Range", 0);
+ TestC.runSF( src, "stacked_r_1", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_2", src, 0, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_1", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("In Range",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("stacked_r_2", src, 0, 2, false);
+ EvalArm64 arm = TestArm64.build("stacked_r_1", src, 0, 0, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("In Range",arm._stdout.toString());
}
@Test
public void testStack3() throws IOException {
- String src =
-"""
-var sq = { int x ->
+ String src ="""
+var sq_noInline = { int x ->
x*x;
};
-
-int score = 5;
-
-val str = (sq(2) <= score < sq(3)) ? "In Range" : "Out of range";
-sys.io.p(str);
-return 0;
+int score = 4;
+return (sq_noInline(2) <= score < sq_noInline(3))
+ ? 0 // Expected
+ : 1; // Error
""";
-
- TestC.runSF("stacked_r_3", src, null, "In Range", 5);
+ TestC.runSF( src, "stacked_r_3", "", 4);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_3", src, 0, 5, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_3", 0, 5, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("In Range",R5._stdout.toString());
// Evaluate on ARM emulator
EvalArm64 arm = TestArm64.build("stacked_r_3", src, 0, 5, false);
int trap_1 = arm.step(100);
assertEquals(0,trap_1);
assertEquals(0,arm.regs[0]);
- assertEquals("In Range",arm._stdout.toString());
}
@Test
public void testStack4() throws IOException {
- String src =
-"""
+ String src ="""
int something_specific = 9;
-
-if (10 < something_specific < 20) {
- sys.io.p("In Range");
-} else {
- sys.io.p("Out of Range");
-}
-return 0;
+return (10 < something_specific < 20)
+ ? 1 // Error
+ : 0; // Expected
""";
- TestC.runSF("stacked_r_4", src, null, "Out of Range", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_4", src,0, 2, false);
- int trap = R5.step(100);
- assertEquals(0,trap);
- assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("Out of Range",R5._stdout.toString());
-
- // Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 2, false);
- trap = arm.step(100);
- assertEquals(0,trap);
- assertEquals(0,arm.regs[0]);
- assertEquals("Out of Range",arm._stdout.toString());
- }
-
- @Test
- public void testStack5() throws IOException {
- String src =
-"""
-var sq = { int x ->
- x*x;
-};
-
-int score = 5;
-
-if (sq(2) <= score < sq(3)) {
-sys.io.p("In Range");
-} else {
-sys.io.p("Out of range");
-}
-
-return 0;
-""";
- TestC.runSF("stacked_r_5", src, null, "In Range", 5);
+ TestC.runSF( src, "stacked_r_4", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_5", src, 0, 7, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_4", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(0,R5.regs[riscv.A0]);
- assertEquals("In Range",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("stacked_r_5", src, 0, 7, false);
+ EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 0, false);
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(0,arm.regs[0]);
- assertEquals("In Range",arm._stdout.toString());
}
-
@Test
public void testStack6() {
- try { new CodeGen(
- """
- return 0 < arg > 1;
- """
- ).parse(); fail(); }
+ try { new CodeGen("return 0 < arg > 1;" ).parse(); fail(); }
catch( RuntimeException e ) { assertEquals("Mixing relational directions in a chained relational test",e.getMessage()); }
}
@Test
public void testStack9() throws IOException {
- String stack9 = "1";
- String src =
-"""
-var stack9 = { int x ->
- (x < 1) == 1;
-};
-""";
- String src2 =
-"""
-return (0 < 1) == 1;
-""";
- TestC.run(src, "stacked_r_9x", null, stack9,0);
+ String src = "var stack9 = { int x -> (x < 1) == 1; };";
+ String src2 = "return (0 < 1) == 1;";
+ TestC.runC(src, "stacked_r_9x", "1", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_9", src2, 0, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_9", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(1,R5.regs[riscv.A0]);
@@ -455,21 +128,12 @@ return (0 < 1) == 1;
@Test
public void testStack10() throws IOException {
- String stack10 = "1";
- String src =
-"""
-var stack10 = { int x ->
- (x < 1) == (1>x);
-};
-""";
- String src2 =
-"""
-return (0 < 1) == (1>0);
-""";
- TestC.run(src, "stacked_r_10x", null, stack10,0);
+ String src ="var stack10 = { int x -> (x < 1) == (1>x);};";
+ String src2 ="return (0 < 1) == (1>0);";
+ TestC.runC(src, "stacked_r_10x", "1",0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_10", src2, 0, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_10", 0, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(1,R5.regs[riscv.A0]);
@@ -483,27 +147,20 @@ return (0 < 1) == (1>0);
@Test
public void testStack11() throws IOException {
- String stack11 = "1";
- String src =
-"""
-var stack11 = { int x ->
- 0 < x < x+1 < 4;
-};
-""";
- String src2 =
-"""
-return 0 < arg < arg+1 < 4;
-""";
- TestC.run(src, "stacked_r_11x", null, stack11,0);
+ String src = "var stack11 = { int x -> 0 < x < x+1 < 4;}; ";
+ String src2 = "return 0 < arg < arg+1 < 4;";
+ TestC.runC(src, "stacked_r_11x", "1", 2);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_11", src2, 1, 2, false);
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_11", 1, 0, false);
+ R5.regs[riscv.A1] = 1; // Arg in A1, Test.class in A0
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(1,R5.regs[riscv.A0]);
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 2, false);
+ EvalArm64 arm = TestArm64.build("stacked_r_11", src2, 1, 0, false);
+ arm.regs[1] = 1; // Arg in r1, Test.class in r0
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(1,arm.regs[0]);
@@ -513,21 +170,12 @@ return 0 < arg < arg+1 < 4;
// ((0 != x) != 2)
@Test
public void testStack12() throws IOException {
- String stack11 = "1";
- String src =
- """
- var stack12 = { int x ->
- 0 != x != 2;
- };
- """;
- String src2 =
- """
- return 0 != arg != 2;
- """;
- TestC.run(src, "stacked_r_12x", null, stack11,0);
+ String src = "var stack12 = { int x -> 0 != x != 2; };";
+ String src2 = "return 0 != arg != 2;";
+ TestC.runC(src, "stacked_r_12x", "1",0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_12", src2, 1, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_12", 1, 0, false);
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(1,R5.regs[riscv.A0]);
@@ -544,116 +192,26 @@ return 0 < arg < arg+1 < 4;
// ((0 == x) == 1)
@Test
public void testStack13() throws IOException {
- String stack12 = "1";
- String src =
- """
- var stack13 = { int x ->
- 0 == x == 1;
- };
- """;
- String src2 =
- """
- return 0 == arg == 1;
- """;
- TestC.run(src, "stacked_r_13x", null, stack12,0);
+ String src = "var stack13 = { int x -> 0 == x == 1; };";
+ String src2 = "return 0 == arg == 1;";
+ TestC.runC(src, "stacked_r_13x", "1",0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_13", src2, 1, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_13", 1, 0, false);
+ R5.regs[riscv.A1] = 1; // Arg in A1, Test.class in A0
int trap = R5.step(100);
assertEquals(0,trap);
assertEquals(1,R5.regs[riscv.A0]);
// Evaluate on ARM emulator
EvalArm64 arm = TestArm64.build("stacked_r_13", src2, 1, 0, false);
+ arm.regs[1] = 1; // Arg in r1, Test.class in r0
trap = arm.step(100);
assertEquals(0,trap);
assertEquals(1,arm.regs[0]);
}
@Test
- public void testFRefFields() {
- String src =
-"""
-// A hypothetical scanner class
-struct Scan {
- int !x;
- u8[~] buf;
- // Skip whitespace
- val skip = { Scan s ->
- while( s.buf[s.x] <= ' ' )
- s.x++;
- };
- // Peek a character; if matched consume it, else false.
- val peek = { Scan s, u8 c ->
- skip(s);
- if( s.buf[s.x] != c ) return false;
- s.x++;
- return true;
- };
-};
-Scan !s = new Scan{ buf = " q"; };
-return Scan.peek(s,'q');
-""";
-
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return Phi(Region,0,1);", code._stop.toString());
- assertEquals("1", Eval2.eval(code, 0));
- testCPU(src,"x86_64_v2", "Win64" ,17,null);
- };
-
- @Test
- public void testFRefFields2() {
- String src =
-"""
-// A hypothetical scanner class
-struct Scan {
- int !x;
- u8[~] buf;
- // Skip whitespace
- val skip = { ->
- while( buf[x] <= ' ' )
- x++;
- return self;
- };
-};
-val s = new Scan{ buf = " q"; };
-return s.skip().x;
-""";
-
- try { new CodeGen(src).parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { assertEquals("Argument #0 isa *Scan {i64 x; *[]u8 buf; { *Scan -> *Scan {i64 !x; *[]u8 buf; {21} skip; } #21} skip; }, but must be a *Scan {i64 !x; *[]u8 buf; ... }",e.getMessage()); }
- };
-
-
- @Test
- public void testMethod() {
- String src =
-"""
-// A hypothetical scanner class
-struct Scan {
- int !x;
- u8[~] buf;
- // Skip whitespace
- val skip = { ->
- while( buf[x] <= ' ' )
- x++;
- };
- val require = { u8 ch ->
- skip();
- buf[x++]==ch;
- };
-};
-Scan !s = new Scan{ buf = " [1,2]"; };
-return s.require('[');
-""";
-
- CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return (.[]==91);", code._stop.toString());
- assertEquals("1", Eval2.eval(code, 0));
- testCPU(src,"x86_64_v2", "Win64" ,16,null);
- };
-
- @Test
public void testSCCP0() {
String src =
"""
@@ -668,7 +226,7 @@ while (cnt) {
return x;
""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return 1;", code._stop.toString());
+ assertEquals("return 1;", code.print());
}
@Test
@@ -683,7 +241,7 @@ while( cnt-- )
return x;
""";
CodeGen code = new CodeGen(src).parse().opto().typeCheck();
- assertEquals("return 1;", code._stop.toString());
+ assertEquals("return 1;", code.print());
}
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter25Test.java b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
new file mode 100644
index 00000000..313ae1cf
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -0,0 +1,286 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.ElfReader;
+import com.seaofnodes.simple.codegen.ParseAll;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.concurrent.TimeUnit;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter25Test {
+
+ private static final String SYS_MODDIR = "src/main/smp";
+ private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI;
+ private static final String RELEASE_SYS_BLDDIR = TestC.RELEASE_SYS_DIR;
+
+ @Test
+ public void testPostfixFieldUpdate() {
+ String src = """
+ struct V {
+ u32 !len = 0;
+ i64[] !buf;
+ new V = { i64[] b -> buf=b; };
+ val grow = { i64 n -> self; };
+ val add = { i64 x -> grow(1).buf[len++] = x; self; };
+ };
+ return new V(new i64[1]).add(7).len;
+ """;
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("1",Eval2.eval(code,0));
+ }
+
+ @Test
+ public void testStringEscapes() {
+ String src = "u8[~] s=\"A\\n\\t\\r\\\\\\\"B\"; " +
+ "return s#==7 && s[0]=='A' && s[1]==10 && s[2]==9 && s[3]==13 && " +
+ "s[4]==92 && s[5]==34 && s[6]=='B';";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("1",Eval2.eval(code,0));
+ }
+
+
+ @Test @Ignore
+ public void testModule0() throws IOException {
+ String MODDIR = "src/test/java/com/seaofnodes/simple/test0";
+ String BLDDIR = "build/objs/test0";
+ String a_obj = BLDDIR+"/A.o";
+ String ab_obj = BLDDIR+"/A/B.o";
+ // Remove any prior results so the test runs from scratch.
+ delELFiles(new File(BLDDIR));
+ writeB(MODDIR,5);
+
+ // Compile MODDIR/A.smp into MODDIR/A.o
+ // Since A refers to B also:
+ // Compile MODDIR/A/B.smp into MODDIR/A/B.o
+ CodeGen code1 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ // Verify produces A.o, A/B.o
+ File a_file = new File( a_obj);
+ File ab_file = new File(ab_obj);
+ assertTrue( a_file.exists() );
+ assertTrue( ab_file.exists() );
+ long a_msec1 = a_file.lastModified();
+ long ab_msec1 = ab_file.lastModified();
+
+ // Link and execute: arg is true, so compute "5+1" as the exit code
+ String rez1 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez1);
+
+ // Compile again A, expecting both A.o and A/B.o to be up-to-date and not compiled
+ CodeGen code2 = new CodeGen(MODDIR, BLDDIR, null,
+ "A",null,123L,TypeInteger.BOT);
+ code2.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ assertTrue( a_file.exists() );
+ assertTrue( ab_file.exists() );
+ long a_msec2 = a_file.lastModified();
+ long ab_msec2 = ab_file.lastModified();
+
+ assertEquals( a_msec1, a_msec2 );
+ assertEquals( ab_msec1, ab_msec2 );
+
+ // Touch A.smp and recompile. A/B.o should not recompile.
+ new File(MODDIR+"/A.smp").setLastModified(System.currentTimeMillis());
+ CodeGen code3 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,TypeInteger.BOT);
+ code3.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec3 = a_file.lastModified();
+ long ab_msec3 = ab_file.lastModified();
+ assertTrue ( a_msec1 < a_msec3 );
+ assertEquals( ab_msec1, ab_msec3 );
+
+ // Link and execute: arg is true, so compute "5+1" as the exit code
+ String rez3 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez3);
+
+
+ // Modify B.smp and recompile A/B.o; it should recompile and A.o should not.
+ writeB(MODDIR,7);
+ CodeGen code4 = new CodeGen(MODDIR, BLDDIR,null,
+ "A/B",null,123L,TypeInteger.BOT);
+ code4.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec4 = a_file.lastModified();
+ long ab_msec4 = ab_file.lastModified();
+ assertEquals( a_msec3, a_msec4 );
+ assertTrue ( ab_msec3 < ab_msec4 );
+
+ // Link and execute: uses stale A.o, so remains '6' not '7+1' == 8
+ String rez4 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 6",rez4);
+
+ // Recompile A.o, it should recompile despite not being touched because
+ // it depends on A/B.o which recompiled in the prior step.
+ CodeGen code5 = new CodeGen(MODDIR, BLDDIR,null,
+ "A",null,123L,TypeInteger.BOT);
+ code5.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+
+ long a_msec5 = a_file.lastModified();
+ long ab_msec5 = ab_file.lastModified();
+ assertTrue ( a_msec4 < a_msec5 );
+ assertEquals( ab_msec4, ab_msec5 );
+
+ // Link and execute: updates A.o from B.o inlining, without compiling B.o
+ String rez5 = TestC.gcc("A", 1.2, a_obj, ab_obj);
+ assertEquals("exec exit code: 8",rez5);
+ // Reset for next time
+ writeB(MODDIR,5);
+ }
+
+ private void writeB(String MODDIR, int x) throws IOException {
+ var bsmp = new FileWriter(MODDIR+"/A/B.smp");
+ bsmp.write("val x="+x+";\n");
+ bsmp.close();
+ }
+
+
+ // Recursive search (TODO: gzip, archives) and delete all .o files
+ private void delELFiles( File dir) {
+ if( dir.isDirectory() )
+ for( File f : dir.listFiles() )
+ delELFiles(f);
+ else if( dir.getName().endsWith(".o") )
+ dir.delete();
+ }
+
+
+ private File buildTestSys(boolean clean) {
+ if( clean )
+ delELFiles(new File(SYS_BLDDIR));
+ File sys_file = new File(SYS_BLDDIR+"/sys.o" );
+ if( sys_file.exists() )
+ return sys_file;
+
+ // Compile SYS_MODDIR/sys.smp into SYS_BLDDIR/sys.o. Chapter25
+ // compiler tests intentionally depend on this freshly built sys.o.
+ CodeGen code1 = new CodeGen(SYS_MODDIR, SYS_BLDDIR,null,
+ "sys",null,123L,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false,0);
+ assertTrue( sys_file.exists() );
+ return sys_file;
+ }
+
+ @Test
+ public void testSys() {
+ delELFiles(new File(SYS_BLDDIR));
+
+ CodeGen code1 = new CodeGen(SYS_MODDIR, SYS_BLDDIR,null,
+ "sys",null,123L,TypeInteger.BOT);
+ code1.driver(CodeGen.Phase.Export,TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false,0);
+
+ // Verify produces sys.o, sys/io.o
+ File sys_file = new File(SYS_BLDDIR+"/sys.o" );
+ assertTrue( sys_file.exists() );
+
+ // Can read the ELF files
+ ElfReader sys_elf = ElfReader.load( sys_file, null);
+ sys_elf.loadSimple(code1);
+
+ // Elf files are sane
+
+ // Sys depends on io, libc, aryi64, aryu8
+ assertEquals(6,sys_elf._deps.length);
+ assertSame("sys/aryu8" ,sys_elf._deps[0]);
+ assertSame("sys/io" ,sys_elf._deps[1]);
+ assertSame("sys/aryi64",sys_elf._deps[2]);
+ assertSame("sys/Ary" ,sys_elf._deps[3]);
+ assertSame("sys/Scan" ,sys_elf._deps[4]);
+ assertSame("sys/libc" ,sys_elf._deps[5]);
+ assertSame("class:sys" ,sys_elf._clz._name);
+ }
+
+ @Test
+ public void testHelloWorld() throws IOException {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testHelloWorld depends on testSys building it", sys_file.exists());
+
+ String expected = "Hello, World!\n";
+ String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";";
+ TestC.run(prog,"helloWorld",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null, expected,0);
+ }
+
+ @Test
+ public void testHelloWorldDriver() throws Exception {
+ File sys_file = new File(RELEASE_SYS_BLDDIR+"/sys.o");
+ assertTrue("Missing "+sys_file+"; run make release or make tests_sys first", sys_file.exists());
+ Simple.main(new String[]{"-L",RELEASE_SYS_BLDDIR,"--norun","docs/examples/A_helloWorld.smp"});
+ }
+
+ @Test
+ public void testHelloWorldDriverLibFile() throws Exception {
+ File sys_file = new File(RELEASE_SYS_BLDDIR+"/sys.o");
+ assertTrue("Missing "+sys_file+"; run make release or make tests_sys first", sys_file.exists());
+ Simple.main(new String[]{"-L",sys_file.toString(),"docs/examples/A_helloWorld.smp"});
+ }
+
+ @Test
+ public void testHelloWorldNoInline() throws Exception {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testHelloWorldNoInline depends on testSys building it", sys_file.exists());
+
+ String base = "helloWorldNoInline";
+ String expected = "Hello, World!\n";
+ String prog = "return sys.io.p_noInline(\""+expected+"\") - "+expected.length()+";";
+ CodeGen code = new CodeGen(null,"build/objs",new Ary<>(new String[]{SYS_BLDDIR}),
+ base,prog,123L,TypeInteger.BOT);
+ code.driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,true);
+
+ String obj = "build/objs/"+base+".o";
+ String exe = "build/objs/"+base+(TestC.OS.startsWith("Windows") ? ".exe" : "");
+ String syms = run(new String[]{"nm",obj});
+ assertTrue(syms, syms.contains(" U sys.io.p_noInline"));
+
+ String out = run(new String[]{"gcc",obj,sys_file.toString(),"-lm","-g","-o",exe});
+ assertEquals("",out);
+ String rez = run(new String[]{exe});
+ assertEquals(expected,rez);
+ }
+
+ private static String run(String[] cmd) throws Exception {
+ Process p = new ProcessBuilder(cmd).redirectErrorStream(true).start();
+ boolean normal = p.waitFor(5, TimeUnit.SECONDS);
+ String out = new String(p.getInputStream().readAllBytes());
+ assertTrue(out, normal);
+ assertEquals(out,0,p.exitValue());
+ return out;
+ }
+
+ @Test
+ public void testRedirectedRead() throws IOException {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testRedirectedRead depends on testSys building it", sys_file.exists());
+ String src = """
+u8[] buf = new u8[10];
+i64 ptr = buf;
+int rez = sys.libc.read(0,ptr,buf#);
+return rez < buf# ? 0 : sys.libc._exit(-2);
+""";
+ TestC.run(src,"redirectedRead",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null,
+ "abc", "", -1);
+ }
+
+ @Test
+ public void testBubbles() throws IOException {
+ File sys_file = buildTestSys(false);
+ assertTrue("Missing "+sys_file+"; testBubbles depends on testSys building it", sys_file.exists());
+ String src = Files.readString( Path.of("docs/examples/BubbleSort.smp"));
+ TestC.run(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null,
+ "[3, 2,-17, 999 ] ", "[-17, 2, 3, 999]", -1);
+ }
+
+}
diff --git a/src/test/java/com/seaofnodes/simple/Compile.java b/src/test/java/com/seaofnodes/simple/Compile.java
deleted file mode 100644
index 088055f9..00000000
--- a/src/test/java/com/seaofnodes/simple/Compile.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.seaofnodes.simple;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.FunNode;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import org.junit.Ignore;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-public class Compile {
-
- private static Process run(String simple, String c) throws IOException, InterruptedException {
- boolean USE_WSL = System.getProperty("os.name").startsWith("Windows");
- CodeGen code = new CodeGen(simple);
- code.parse().opto().typeCheck().instSelect("x86_64_v2", "SystemV").GCM().localSched().regAlloc().encode();
- if (c != null) for (var n:code._start._outputs) if (n instanceof FunNode f && f._name.equals("main")) f._name = "simple_main";
- code.exportELF("test.o");
- var params = new ArrayList<String>();
- if (USE_WSL) params.add("wsl.exe");
- params.add("gcc");
- params.add("-o");
- params.add("test");
- if (c != null) {
- Files.writeString(Path.of("test.c"), c);
- params.add("test.c");
- }
- params.add("test.o");
- Process p = Runtime.getRuntime().exec(params.toArray(String[]::new));
- assertEquals(0, p.waitFor());
- return Runtime.getRuntime().exec(USE_WSL ? new String[]{"wsl.exe", "./test"}:new String[]{"./test"});
- }
-
- @Test
- @Ignore
- public void testCompile() throws IOException, InterruptedException {
- var p = run("""
- val testMy = { flt x ->
- flt guess = x;
- while( 1 ) {
- flt next = (x/guess + guess)/2;
- if( next == guess ) return guess;
- guess = next;
- }
- };
- ""","""
-
- #include <stdio.h>
-
- extern double testMy(double);
-
- int main(int argc, char** argv) {
- printf("%f\\n", testMy(2.0));
- return 0;
- }
-
- """);
- assertEquals(0, p.waitFor());
- assertEquals("1.414214\n", new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8));
- }
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/Eval2.java b/src/test/java/com/seaofnodes/simple/Eval2.java
index 5994e7e1..e88e9caa 100644
--- a/src/test/java/com/seaofnodes/simple/Eval2.java
+++ b/src/test/java/com/seaofnodes/simple/Eval2.java
@@ -117,7 +117,7 @@ public abstract class Eval2 {
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
- SB trace = null; // = new SB(); // TRACE, set to null for off, new SB() for on
+ SB trace = null; // new SB(); // TRACE, set to null for off, new SB() for on
// Force local scheduling phase
code.driver(CodeGen.Phase.LocalSched);
// Set global, so don't have to pass everywhere
@@ -134,6 +134,14 @@ public abstract class Eval2 {
CFGNode BB = code._start, prior = null;
// Return from main hits Stop
F.put(BB,new Closure(code._stop,F));
+ // For all <clinit> / Clazzes, install their uninitialized memory
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun.isClz() ) {
+ 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));
+ }
// ------------
@@ -182,28 +190,37 @@ public abstract class Eval2 {
int path = r._inputs.find( prior );
// Parameters read from prior frame, Phis from local frame
Frame frame = (r instanceof FunNode ? F._prior : F);
- boolean isMain = r instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main);
+ boolean isClz = r instanceof FunNode fun && fun.isClz();
// Parallel assign Phis. First parallel read and cache
int i;
for( i = 0; i < r.nOuts(); i++ ) {
+ // Due to scheduling, all Phis are first in the outputs; first
+ // non-Phi is the end of all Phis.
if( !(r.out(i) instanceof PhiNode phi) ) break;
- if( isMain && phi instanceof ParmNode parm && parm._idx==2 )
- PHICACHE[i] = arg; // Reading the initial arguments to main()
+
+ // Parms read from Call args -
+ // - except RPC is the actual prior frame
+ // - except <clinit> takes the singleton class object and external args
+ Object val;
+ if( isClz && phi instanceof ParmNode parm && parm._idx==3 )
+ PHICACHE[i] = arg; // Reading the initial arguments to <clinit>
else {
Node n = phi instanceof ParmNode parm
- // RPC reads the Call directly;
+ // RPC uses the prior Call directly;
// Parms may not be linked, so read call args directly
? (parm._idx==0 ? prior : prior.in(parm._idx))
// Phis read from path input
: phi.in(path);
+ // Read the frame before overwriting
PHICACHE[i] = frame.get(n);
}
}
// Parallel assign; might assign before read, so read from cache
for( int j=0; j < i; j++ )
traceData(F.put0(r.out(j),PHICACHE[j]),trace);
- // Return point in basic block past last Phi
+ // Return point in basic block past last Phi; the Phis have been
+ // evaluated here, and now we need to evaluate the rest of the block
return i;
}
@@ -252,36 +269,43 @@ public abstract class Eval2 {
// From here down shamelessly copied from Evaluator, written by @Xmilia
private static Object compute( Node n ) {
return switch( n ) {
- case AddFNode adf -> d(adf.in(1)) + d(adf.in(2));
- case AddNode add -> x(add.in(1)) + x(add.in(2));
+ case AddNode add -> add.mode()==1
+ ? (Object)(x(add.in(1)) + x(add.in(2)))
+ : (Object)(d(add.in(1)) + d(add.in(2)));
case AndNode and -> x(and.in(1)) & x(and.in(2));
- case BoolNode.EQF eqf -> d(eqf.in(1)) == d(eqf.in(2)) ? 1L : 0L;
- case BoolNode.LEF lef -> d(lef.in(1)) <= d(lef.in(2)) ? 1L : 0L;
- case BoolNode.LTF ltf -> d(ltf.in(1)) < d(ltf.in(2)) ? 1L : 0L;
case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L; // Bool EQ supports pointers, nil, and integers
- case BoolNode.LE le -> x(le .in(1)) <= x(le .in(2)) ? 1L : 0L;
- case BoolNode.LT lt -> x(lt .in(1)) < x(lt .in(2)) ? 1L : 0L;
- case CastNode cast -> val(cast.in(1));
+ case BoolNode.LE le -> le.mode()==1
+ ? (x(le.in(1)) <= x(le.in(2)) ? 1L : 0L)
+ : (d(le.in(1)) <= d(le.in(2)) ? 1L : 0L);
+ case BoolNode.LT lt -> lt.mode()==1
+ ? (x(lt.in(1)) < x(lt.in(2)) ? 1L : 0L)
+ : (d(lt.in(1)) < d(lt.in(2)) ? 1L : 0L);
+ case CheckCastNode cast -> val(cast.in(1));
+ case GuardNode guard -> val(guard.in(1));
case ConstantNode con -> con(con._con);
- case DivFNode dvf -> d(dvf.in(2))==0 ? 0D : d(dvf.in(1)) / d(dvf.in(2));
- case DivNode div -> x(div.in(2))==0 ? 0L : x(div.in(1)) / x(div.in(2));
+ case DivNode div -> div.mode()==1
+ ? (Object)(x(div.in(1)) / x(div.in(2)))
+ : (Object)((d(div.in(2))==0 ? 0D : d(div.in(1))) / d(div.in(2)));
+ case EscapeNode esc -> esc(esc);
+ case FunPtrNode fptr -> con(fptr._con);
case LoadNode ld -> load(ld);
- case MinusFNode mnf -> - d(mnf.in(1));
- case MinusNode sub -> - x(sub.in(1));
- case MulFNode mlf -> d(mlf.in(1)) * d(mlf.in(2));
- case MulNode mul -> x(mul.in(1)) * x(mul.in(2));
+ case MemMergeNode merge-> "$mem";
+ case MinusNode sub -> sub.mode()==1 ? (Object)(- x(sub.in(1))) : (Object)(- d(sub.in(1)));
+ case MulNode mul -> mul.mode()==1
+ ? (Object)(x(mul.in(1)) * x(mul.in(2)))
+ : (Object)(d(mul.in(1)) * d(mul.in(2)));
case NewNode alloc-> alloc(alloc);
- case NotNode not -> x(not.in(1)) == 0 ? 1L : 0L;
+ case NotNode not -> not(not);
case OrNode or -> x(or .in(1)) | x(or .in(2));
case ProjNode proj -> proj._type instanceof TypeMem ? "$mem" : val(proj.in(0));
case ReadOnlyNode read -> val(read.in(1));
case SarNode sar -> x(sar.in(1)) >> x(sar.in(2));
- case MemMergeNode merge-> "$mem";
case ShlNode shl -> x(shl.in(1)) << x(shl.in(2));
case ShrNode shr -> x(shr.in(1)) >>>x(shr.in(2));
case StoreNode st -> store(st);
- case SubNode sub -> x(sub.in(1)) - x(sub.in(2));
- case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2));
+ case SubNode sub -> sub.mode()==1
+ ? (Object)(x(sub.in(1)) - x(sub.in(2)))
+ : (Object)(d(sub.in(1)) - d(sub.in(2)));
case ToFloatNode toflt-> (double)x(toflt.in(1));
case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2));
default -> throw Utils.TODO();
@@ -295,7 +319,9 @@ public abstract class Eval2 {
// Fetch and unbox as primitive double
static double d( Node n ) { Object d = F.get(n); return d==null ? 0 : (Double)d; }
// Fetch and unbox a function constant
- static TypeFunPtr tfp(Node n) { return (TypeFunPtr)F.get(n); }
+ static TypeFunPtr tfp(Node n) {
+ return (TypeFunPtr)F.get(n);
+ }
// Fetch and unbox a closure
static Closure clj(Node n) { return (Closure)F.get(n); }
@@ -321,16 +347,33 @@ public abstract class Eval2 {
}
} else {
- // Generic TMP (since Simple is not currently making actual
- // memory constants), used as a default input to a function;
- // should never execute.
- yield tmp.toString();
+ // Constant object with all zeros
+ yield alloc(tmp._obj);
}
}
default -> null;
};
}
+ // Escapes of constant memory actually needs to set the memory
+ private static Object esc(EscapeNode esc) {
+ if( esc.priv() instanceof ConstantNode con ) {
+ TypeMem mem = (TypeMem)con._type;
+ Object val = con(mem._t);
+ TypeMemPtr tmp = (TypeMemPtr)esc.self()._type;
+ int idx = tmp._obj.findAlias(esc.fld()._alias);
+ Object[] fs = (Object[])val(esc.self());
+ if( tmp._obj.isAry() ) {
+ assert idx==0; // length field
+ assert (Long)val==fs.length;
+ } else {
+ fs[idx] = val;
+ }
+ }
+ return "$mem";
+ }
+
+
// Convert array size to array element count
private static int offToIdx( long off, TypeStruct t) {
off -= t.aryBase();
@@ -340,8 +383,8 @@ public abstract class Eval2 {
}
// Builds and returns a pointer Object
- private static Object alloc(NewNode alloc) {
- TypeStruct type = alloc._ptr._obj;
+ private static Object[] alloc(NewNode alloc) {
+ TypeStruct type = alloc._ts;
if( type.isAry() ) {
long sz = (Long)val(alloc.in(1));
long x = offToIdx(sz, type);
@@ -360,6 +403,10 @@ public abstract class Eval2 {
return ary;
}
+ return alloc(type);
+ }
+ private static Object[] alloc(TypeStruct type) {
+ assert !type.isAry();
int num = type._fields.length;
Object[] ptr = new Object[num];
for( int i=0; i<num; i++ )
@@ -367,15 +414,16 @@ public abstract class Eval2 {
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 )
+ ? 1L : 0L;
+ }
+
private static Object load( LoadNode ld ) {
Object f = val(ld.ptr());
// Check for dense constant array
if( f instanceof TypeMemPtr tmp ) {
- //assert tmp._obj._con != TypeConAry.BOT;
- //if( ld._name.equals("#") )
- // return (long)tmp._obj._con.len();
- //int idx = offToIdx(x(ld.off()),tmp._obj);
- //return tmp._obj._con.at(idx);
throw Utils.TODO();
}
Object[] fs = (Object[])f;
@@ -434,12 +482,11 @@ public abstract class Eval2 {
case TypeMemPtr tmp -> {
if( visit.containsKey(x) ) yield sb.p("$cyclic");
visit.put(x,x);
- assert !tmp.isFRef();
Object[] xs = (Object[])x; // Array of fields
if( tmp._obj.isAry() ) {
Type elem = tmp._obj._fields[1]._t;
- if( elem == TypeInteger.U8 ) {
+ if( elem.isa(TypeInteger.U8) ) {
// Shortcut u8[] as a String
for( Object o : xs )
sb.p((char)(long)(Long)o);
diff --git a/src/test/java/com/seaofnodes/simple/EvalArm64.java b/src/test/java/com/seaofnodes/simple/EvalArm64.java
index 9575d9fe..32a5b152 100644
--- a/src/test/java/com/seaofnodes/simple/EvalArm64.java
+++ b/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -63,9 +63,9 @@ public class EvalArm64 {
double frval = 0;
int pc = _pc;
int cycle = _cycle;
- boolean is_f = false;
outer:
for(int icount = 0; icount < maxops; icount++) {
+ boolean is_f = false;
int ir = 0;
rval = 0;
frval = 0;
@@ -96,29 +96,28 @@ public class EvalArm64 {
}
case 0x1E: { // floats
is_f = true;
- int rs1 = (ir >> 5) & 0x1F;
+ int rs1 = (ir >> 5) & 0x1F;
int rs2 = (ir >> 16) & 0x1F;
int op = (ir >> 10) & 0x3F;
switch(op) {
case 16: frval = fregs[rs1]; break; // fmov
- case 10:frval = fregs[rs1] + fregs[rs2]; break; // fadd
+ case 10: frval = fregs[rs1] + fregs[rs2]; break; // fadd
case 6: if (fregs[rs2] == 0) frval = 0; else frval = fregs[rs1] / fregs[rs2]; break; // fdiv
case 8: {
// fcmp
- double lhs = fregs[rs1];
+ double lhs = fregs[rs1];
double rhs = fregs[rs2];
Z = (lhs == rhs);
- N = (lhs < rhs);
- C = lhs >= rhs;
+ N = (lhs < rhs);
+ C = (lhs > rhs);
V = Double.isNaN(lhs) || Double.isNaN(rhs);
rdid = -1;
-
break;
}
- case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul
+ case 2: frval = fregs[rs1] * fregs[rs2]; break; // fmul
case 14: frval = fregs[rs1] - fregs[rs2]; break; // fsub
default: trap = (2+1);
}
@@ -298,10 +297,12 @@ public class EvalArm64 {
break;
}
- case 0x90: { // adrp
- int immhi = ( (ir& ~0x1F) << 8 ) >> 8;
+ case 0x90, 0xB0, 0xD0, 0xF0: { // adrp
+ int immhi = ( ir << 8 ) >>> (8+5);
int immlo = (ir>>29)& 3;
- int imm = ((immhi<<2) | immlo) << 12;
+ int imm = ((immhi<<2) | immlo);
+ imm = (imm << 11) >> 11;
+ imm <<= 12;
rval = (pc & ~0xFFF) + imm;
break;
}
@@ -361,13 +362,11 @@ public class EvalArm64 {
int encodedCond = (ir >> 12) & 0xF;
int decodedCond = encodedCond ^ 1;
// conditional select(csel)
- int rm = (ir >> 16) & 0x1F;
- int rn = (ir >> 5) & 0x1F;
boolean cond = switch (decodedCond) {
case 0x0 -> Z ; // eq
case 0x1 -> !Z ; // ne
case 0x2 -> C ; // cs
- case 0x3 -> !C ;
+ case 0x3 -> !C ; // cc
case 0x4 -> N ; // mi
case 0x5 -> !N ; // pl
case 0x6 -> V ; // vs
@@ -382,11 +381,7 @@ public class EvalArm64 {
case 0xF -> false ; // never
default -> throw Utils.TODO();
};
-// int reg = cond ? rn : rm;
-// rval = reg==31 ? 0 : regs[reg];
-// if( cond ) rval++;
- if(cond) rval = 1;
- else rval = 0;
+ if( cond ) rval = 1;
break;
}
case 0x9B: { // mul
@@ -753,16 +748,15 @@ public class EvalArm64 {
break;
}
if(rdid != -1) {
- if(is_f) {fregs[rdid] = frval; is_f = false;}
- else regs[rdid] = rval;
+ if(is_f) fregs[rdid] = frval;
+ else regs[rdid] = rval;
}
pc += 4;
}
// Handle traps and interrupts.
- if(trap != 0) {
+ if(trap != 0)
return trap;
- }
_cycle = cycle;
_pc = pc;
return 0;
diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java
index 9c7c1360..ca33438a 100644
--- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java
+++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java
@@ -1,11 +1,9 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
import com.seaofnodes.simple.util.SB;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
+
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -15,52 +13,52 @@ public class MergeSortTest {
@Test public void testMergeSort0() throws IOException {
// Test merge sort on a small array of primes
String src =
-"""
- val merge_sort = { int[] a ->
- int[] !b = new int[a#];
- copy_array (a, 0, a#, b);
- split_merge(a, 0, a#, b);
- };
-
- val split_merge = { int[] b, int begin, int end, int[] a ->
- if (end - begin <= 1)
- return 0;
- int middle = (end + begin) / 2;
- split_merge(a, begin, middle, b);
- split_merge(a, middle, end, b);
- merge(b, begin, middle, end, a);
- return 0;
- };
-
- val merge = { int[] b, int begin, int middle, int end, int[] a ->
- int i = begin, j = middle;
-
- for (int k = begin; k < end; k++) {
- // && and ||
- bool cond = false;
- if (i < middle) {
- if (j >= end) cond = true;
- else if (a[i] <= a[j]) cond = true;
- }
- if (cond) b[k] = a[i++];
- else b[k] = a[j++];
- }
- };
-
- val copy_array = { int[] a, int begin, int end, int[] b ->
- for (int k = begin; k < end; k++)
- b[k] = a[k];
- };
+"""
+val merge_sort = { int[] a ->
+ int[] !b = new int[a#];
+ _copy_array (a, 0, a#, b);
+ _split_merge(a, 0, a#, b);
+};
+
+val _split_merge = { int[] b, int begin, int end, int[] a ->
+ if (end - begin <= 1)
+ return 0;
+ int middle = (end + begin) / 2;
+ _split_merge(a, begin, middle, b);
+ _split_merge(a, middle, end, b);
+ _merge(b, begin, middle, end, a);
+ return 0;
+};
+
+val _merge = { int[] b, int begin, int middle, int end, int[] a ->
+ int i = begin, j = middle;
+
+ for (int k = begin; k < end; k++) {
+ // && and ||
+ bool cond = false;
+ if (i < middle) {
+ if (j >= end) cond = true;
+ else if (a[i] <= a[j]) cond = true;
+ }
+ if (cond) b[k] = a[i++];
+ else b[k] = a[j++];
+ }
+};
+
+val _copy_array = { int[] a, int begin, int end, int[] b ->
+ for (int k = begin; k < end; k++)
+ b[k] = a[k];
+};
""";
int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, };
SB sb = new SB().p(primes.length).p("[");
for( int prime : primes )
sb.p(prime).p(", ");
String sprimes = sb.p("]").toString();
- TestC.run(src, "sort", null, sprimes, 40);
+ TestC.runC(src, "sort", sprimes, 34);
- EvalRisc5 R5 = TestRisc5.build("src/test/java/com/seaofnodes/simple/progs",src, "sort", "merge_sort", 0, 36, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "merge_sort", 0, 36, false);
// Allocate an array of primes, filled in reverse order
int ps = R5._heap;
R5._heap += (primes.length+1)*8; // 25 primes, and length
diff --git a/src/test/java/com/seaofnodes/simple/SchedulerTest.java b/src/test/java/com/seaofnodes/simple/SchedulerTest.java
deleted file mode 100644
index ca29bf84..00000000
--- a/src/test/java/com/seaofnodes/simple/SchedulerTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-package com.seaofnodes.simple;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.evaluator.Evaluator;
-import com.seaofnodes.simple.node.StopNode;
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import static org.junit.Assert.assertEquals;
-
-public class SchedulerTest {
-
- private static void assertObj(Object obj, String name, Object... fields) {
- assertEquals(Evaluator.Obj.class, obj.getClass());
- var o = (Evaluator.Obj)obj;
- assertEquals(name, o.struct()._name);
- Assert.assertArrayEquals(fields, o.fields());
- }
-
- @Test
- @Ignore
- public void testJig() {
- CodeGen code = new CodeGen("""
-struct S {
- int f;
-}
-S !v=new S;
-v.f = 2;
-int i=v.f;
-if (arg) v.f=1;
-return i;
-""");
- code.parse();//.opto();
- var eval = new Evaluator(code._stop);
- assertEquals(2L, eval.evaluate(1, 10));
- }
-
- @Test
- public void testStoreInIf() {
- CodeGen code = new CodeGen("""
-struct S {
- int f;
-};
-S !v0=new S;
-if(arg) v0.f=1;
-return v0;
-""");
- code.parse().opto();
- var eval = new Evaluator(code._stop);
- assertObj(eval.evaluate(0, 10), "S", 0L);
- assertObj(eval.evaluate(1, 10), "S", 1L);
- }
-
- @Test
- public void testStoreInIf2() {
- CodeGen code = new CodeGen("""
-struct S {
- int f;
-};
-S !v=new S;
-v.f = 2;
-int i=new S.f;
-i=v.f;
-if (arg) v.f=1;
-return i;
-""");
- code.parse().opto();
- var eval = new Evaluator(code._stop);
- assertEquals(2L, eval.evaluate(0, 10));
- assertEquals(2L, eval.evaluate(1, 10));
- }
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/Simple.java b/src/test/java/com/seaofnodes/simple/Simple.java
index be582a64..35b9b922 100644
--- a/src/test/java/com/seaofnodes/simple/Simple.java
+++ b/src/test/java/com/seaofnodes/simple/Simple.java
@@ -1,69 +1,66 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.CompUnit;
+import com.seaofnodes.simple.codegen.CodeGen.Phase;
import com.seaofnodes.simple.print.*;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.Utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.stream.Collectors;
public class Simple {
- public static final String PORTS = "com.seaofnodes.simple.node.cpus";
-
- static final int DUMP_AFTER_PARSE = 1<<0;
- static final int DUMP_AFTER_OPTO = 1<<1;
- static final int DUMP_AFTER_TYPE_CHECK = 1<<2;
- static final int DUMP_AFTER_LOOP_TREE = 1<<3;
- static final int DUMP_AFTER_SELECT = 1<<4;
- static final int DUMP_AFTER_GCM = 1<<5;
- static final int DUMP_AFTER_LOCAL_SCHED = 1<<6;
- static final int DUMP_AFTER_REG_ALLOC = 1<<7;
- static final int DUMP_AFTER_ENCODE = 1<<8;
-
- static final int DUMP_FINAL = 1<<16;
-
- static final int DUMP_DOT = 1<<29;
- static final int DUMP_PASS_NAME = 1<<30;
- static final int DUMP_AFTER_ALL = 0xff | DUMP_PASS_NAME;
- static final int DUMP_DOT_AFTER_ALL = 0xff | DUMP_DOT;
-
- static final int DUMP_DISASSEMBLE = 1<<31;
-
static void usage() {
System.out.println(
"""
-simple [options] <input-file> ...args...
+simple [options] input-file.smp ...program args...
+Options are applied in-order.
Options:
- --dump - dump final intermediate representation
+ --help - This text
+
+ --root root/ - Project root directory; this must end in a '/' and input
+ files are relative paths to this. If missing, the one input
+ file path is treated as its own root directory.
+
+ -o output - Project output directory; similar to --root, if 'output' ends in a
+ '/', then all result files use relative paths to 'output', using a input file
+ root-relative path. If --root is missing, the one result file is named 'output'
+
+ -L path - add an external Simple object search path
+ --eval - (slowly) evaluate the compiled code in emulator
+ --run - run the compiled code natively; this is the default
+ --main - export the top-level initializer as C `main`
+ --cpu <cpu-name> - use specific CPU (x86_64_v2, riscv, arm)
+ --abi <abi-name> - use specific ABI variant (SystemV)
+ --target - print native CPU and ABI
+ --version
+ -S - dump generated assembler code
+ --dump-size - print the size of generated code
+ --dump-time - print compilation and execution times
--dump-after-parse - dump intermediate representation after parse
+ --dump-after-iter - dump intermediate representation after pessimistic iter pass
--dump-after-opto - dump intermediate representation after opto pass
--dump-after-type-check - dump intermediate representation after type check pass
- --dump-after-select - dump intermediate representation after instrution selection pass
+ --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
--dump-after-reg-alloc - dump intermediate representation after register allocation pass
--dump-after-encode - dump intermediate representation after encoding pass
--dump-after-all - dump intermediate representation after all passes
+ --dump - dump final intermediate representation only
--dot - dump grapical representation of intermediate code into *.dot file(s)
- -S - dump generated assembler code
- --eval - evaluate the compiled code in emulator
- --run - run the compiled code natively; this is the default
- --dump-size - print the size of generated code
- --dump-time - print compilation and execution times
- --cpu <cpu-name> - use specific CPU (x86_64_v2, riscv, arm)
- --abi <abi-name> - use speific ABI variant (SystemV)
- --target - print native CPU and ABI
- --version
- --help
"""
);
System.exit(0);
}
- static void bad_usage() { bad("Invalid usage (use --help)"); }
+ static void bad_usage() { throw bad("Invalid usage (use --help)"); }
static RuntimeException bad(String err) {
System.err.println("ERROR: "+err);
System.exit(1);
@@ -76,91 +73,35 @@ Options:
System.out.println(TestC.CALL_CONVENTION);
}
- static void dump(CodeGen code, int dump, int pass) {
- if ((dump & pass) != 0) {
- if ((dump & DUMP_DOT) != 0) {
- String fn = switch (pass) {
- case DUMP_AFTER_PARSE -> "01-parse.dot";
- case DUMP_AFTER_OPTO -> "02-opto.dot";
- case DUMP_AFTER_TYPE_CHECK -> "03-type_check.dot";
- case DUMP_AFTER_LOOP_TREE -> "04-loop_tree.dot";
- case DUMP_AFTER_SELECT -> "05-instr_select.dot";
- case DUMP_AFTER_GCM -> "06-gcm.dot";
- case DUMP_AFTER_LOCAL_SCHED -> "07-local_sched.dot";
- case DUMP_AFTER_REG_ALLOC -> "08-reg_allos.dot";
- case DUMP_AFTER_ENCODE -> "09-local_sched.dot";
- case DUMP_FINAL -> "10-final.dot";
- default -> throw Utils.TODO();
- };
-
- try {
- Files.writeString(Path.of(fn),
- new GraphVisualizer().generateDotOutput(code._stop, null, null));
- } catch(IOException e) { throw bad("Cannot write DOT file"); }
- } else {
- if ((dump & DUMP_PASS_NAME) != 0) {
- System.err.println(switch (pass) {
- case DUMP_AFTER_PARSE -> "After Parse:";
- case DUMP_AFTER_OPTO -> "After OPTO:";
- case DUMP_AFTER_TYPE_CHECK -> "After Type Check:";
- case DUMP_AFTER_LOOP_TREE -> "After Loop Tree:";
- case DUMP_AFTER_SELECT -> "After Code Selection:";
- case DUMP_AFTER_GCM -> "After GCM:";
- case DUMP_AFTER_LOCAL_SCHED -> "After Local Scheduling:";
- case DUMP_AFTER_REG_ALLOC -> "After Register Allocation:";
- case DUMP_AFTER_ENCODE -> "After Encoding:";
- case DUMP_FINAL -> "Final:";
- default -> throw Utils.TODO();
- });
- }
-
- System.err.println(IRPrinter.prettyPrint(code._stop, 9999));
- }
- }
- }
-
static void print_compilation_times(CodeGen code) {
- double t, total = 0;
-
- total += t = code._tParse / 1e3;
- System.out.printf( "Parsing Time: %.3f sec%n", t);
- total += t = code._tOpto / 1e3;
- System.out.printf( "Optimization Time: %.3f sec%n", t);
- total += t = code._tTypeCheck / 1e3;
- System.out.printf( "Type Checking Time: %.3f sec%n", t);
- total += t = code._tLoopTree / 1e3;
- System.out.printf( "Loop Tree Time: %.3f sec%n", t);
- total += t = code._tInsSel / 1e3;
- System.out.printf( "Code Selection Time: %.3f sec%n", t);
- total += t = code._tGCM / 1e3;
- System.out.printf( "GCM Time: %.3f sec%n", t);
- total += t = code._tLocal / 1e3;
- System.out.printf( "Local Scheduling Time: %.3f sec%n", t);
- total += t = code._tRegAlloc / 1e3;
- System.out.printf( "Register Allocation Time: %.3f sec%n", t);
- total += t = code._tEncode / 1e3;
- System.out.printf( "Encoding Time: %.3f sec%n", t);
- System.out.printf( "TOTAL COMPILATION TIME: %.3f sec%n", total);
+ long[] times = code._times;
+ for( int i=0; i<code._times.length; i++ )
+ System.out.printf("%20s %.3f sec%n", Phase.values()[i], times[i]);
}
- static String getInput() throws IOException {
- try (BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))) {
- return reader.lines().collect(Collectors.joining("\n"));
- }
+ static String getInput() {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ return reader.lines().collect(Collectors.joining("\n"));
}
public static void main(String[] args) throws Exception {
+ String root = null;
String input_filename = null;
boolean do_eval = false;
boolean do_run = true;
boolean do_codegen = false;
- boolean do_print_size = false;
- boolean do_print_time = false;
+ boolean emit_main = false;
+ boolean dump_dot = false;
+ boolean print_time = false;
+ boolean print_size = false;
+ boolean print_asm = false;
int dump = 0;
int first_arg = 0;
String src = null;
String cpu = null;
String abi = null;
+ String out = null;
+ ArrayList<String> libPaths = new ArrayList<>();
// Parse command line
int i; for( i = 0; i < args.length; i++ ) {
@@ -172,24 +113,33 @@ Options:
switch (arg) {
case "-": input_filename=""; break;
- case "--dump": dump |= DUMP_FINAL; break;
- case "--dump-after-parse": dump |= DUMP_AFTER_PARSE; break;
- case "--dump-after-opto": dump |= DUMP_AFTER_OPTO; break;
- case "--dump-after-type-check": dump |= DUMP_AFTER_TYPE_CHECK; break;
- case "--dump-after-loop-tree": dump |= DUMP_AFTER_LOOP_TREE; break;
- case "--dump-after-select": dump |= DUMP_AFTER_SELECT; break;
- case "--dump-after-gcm": dump |= DUMP_AFTER_GCM; break;
- case "--dump-after-local-sched": dump |= DUMP_AFTER_LOCAL_SCHED; break;
- case "--dump-after-reg-alloc": dump |= DUMP_AFTER_REG_ALLOC; break;
- case "--dump-after-encode": dump |= DUMP_AFTER_ENCODE; break;
- case "--dump-after-all": dump |= DUMP_AFTER_ALL; break;
- case "--dot": dump |= DUMP_DOT; break;
- case "-S": dump |= DUMP_DISASSEMBLE; break;
+ case "--dump-after-parse": dump |= 1<<Phase.Parse .ordinal() ; break;
+ case "--dump-after-iter": dump |= 1<<Phase.Iter .ordinal() ; break;
+ case "--dump-after-opto": dump |= 1<<Phase.Opto .ordinal() ; break;
+ case "--dump-after-type-check": dump |= 1<<Phase.TypeCheck .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;
+ case "--dump-after-local-sched": dump |= 1<<Phase.LocalSched.ordinal() ; break;
+ case "--dump-after-reg-alloc": dump |= 1<<Phase.RegAlloc .ordinal() ; break;
+ case "--dump-after-encode": dump |= 1<<Phase.Encoding .ordinal() ; break;
+ case "--dump-after-export": dump |= 1<<Phase.Export .ordinal() ; break;
+ case "--dump": dump |= 1<<Phase.LastPhase .ordinal() ; break;
+ case "--dump-after-all": dump |= (2<<Phase.LastPhase .ordinal())-1; break;
+ case "--dot": dump_dot = true; break;
+ case "-S": print_asm = true; break;
case "--eval": do_eval = true ; do_run = false; break;
case "--run": do_run = true ; break;
case "--norun": do_run = false; break;
- case "--dump-size": do_print_size = true; break;
- case "--dump-time": do_print_time = true; break;
+ case "--main": emit_main = true; break;
+ case "--dump-size": print_size = true; break;
+ case "--dump-time": print_time = true; break;
+ case "-o": if (out != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ out = args[++i]; do_codegen=true;
+ break;
+ case "-L": if (i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
+ libPaths.add(args[++i]);
+ break;
case "--cpu": if (cpu != null || i + 1 >= args.length || args[i + 1].charAt(0) == '-') bad_usage();
cpu = args[++i];
break;
@@ -203,16 +153,68 @@ Options:
}
}
+ // Break the paths into these parts:
+ // - A module path, given from "--root" or inferred from the one input file
+ // - Some sources [modpath/relpath/file.smp]*, all relative to the module path.
+ // - A build path, given from "-o" or inferred from the one input file
+ //
+ // All the sources are compiled, with outputs in [buildpath/relpath/file.o]*
+
if (input_filename == null) throw bad("no input file' (use --help)");
if( !input_filename.isEmpty() && !input_filename.endsWith(".smp") )
throw bad("File extension must be .smp");
- String base = input_filename.substring(0,input_filename.length()-4);
+ Path inPath = Path.of(input_filename);
+
+ // Figure out the module path
+ Path modPath;
+ if( root == null ) {
+ modPath = inPath.getParent();
+ if( modPath == null )
+ modPath = Path.of(".");
+ } else {
+ throw Utils.TODO();
+ }
+
+ // Compute the module-relative paths for all sources
+ Path srcRelPath = modPath.relativize(inPath);
+ int n = srcRelPath.getNameCount();
+ Path relPath = n==1 ? null : srcRelPath.subpath(0,n-1);
+ // Compute source/class name
+ String srcNameExt = srcRelPath.getName(n-1).toString();
+ String srcName = srcNameExt.substring(0,srcNameExt.length()-4);
+
+ // Compute the build path.
+ Path outPath;
+ if( out == null ) {
+ // If no output given, the build dir is the modPath
+ outPath = modPath;
+ } else {
+ // If the output is a directory, that is our out path
+ outPath = Path.of(out);
+ if( !out.endsWith("/") ) {
+ // If the output is a file, the directory is our outPath
+ Path fullOut = outPath;
+ outPath = outPath.getParent();
+ if( outPath == null )
+ outPath = Path.of(".");
+ Path foo = outPath.relativize(fullOut);
+ if( !foo.toString().equals(srcName+".o") )
+ throw bad("Output path needs to be of the form "+srcName+".o");
+ }
+ }
+ String outBase = outPath.resolve(srcName).toString();
- if (do_run || (dump & DUMP_DISASSEMBLE) != 0 || do_print_size) {
+ if( do_run || print_asm || print_size ) {
if (cpu == null) cpu = TestC.CPU_PORT;
if (abi == null) abi = TestC.CALL_CONVENTION;
do_codegen = true;
}
+ if( cpu == null ) cpu = TestC.CPU_PORT;
+ if( abi == null ) abi = TestC.CALL_CONVENTION;
+
+ String simpleHome = System.getenv("SIMPLE_HOME");
+ if( simpleHome != null && !simpleHome.isEmpty() )
+ libPaths.add(Path.of(simpleHome,cpu+"_"+abi).toString());
// Read input file
try {
@@ -220,70 +222,32 @@ Options:
} catch( IOException e ) { throw bad("Cannot read input file: "+input_filename); }
// Compilation pipeline
- CodeGen code = new CodeGen(src);
-
- code.parse();
- dump(code, dump, DUMP_AFTER_PARSE);
-
- code.opto();
- dump(code, dump, DUMP_AFTER_OPTO);
-
- code.typeCheck();
- dump(code, dump, DUMP_AFTER_TYPE_CHECK);
-
- code.loopTree();
- dump(code, dump, DUMP_AFTER_LOOP_TREE);
+ Ary<String> externPaths = libPaths.isEmpty() ? null : new Ary<>(libPaths.toArray(new String[0]));
+ CodeGen code = new CodeGen(modPath.toString(), outPath.toString(), externPaths, srcName, src, 456, TypeInteger.BOT );
+ code.driver(Phase.LastPhase,cpu,abi, false, emit_main || do_run || do_eval, dump);
- if (do_codegen) {
- code.instSelect(cpu, abi, PORTS);
- dump(code, dump, DUMP_AFTER_SELECT);
- }
-
- code.GCM();
- dump(code, dump, DUMP_AFTER_GCM);
-
- code.localSched();
- dump(code, dump, DUMP_AFTER_LOCAL_SCHED);
-
- if (do_codegen) {
- code.regAlloc();
- dump(code, dump, DUMP_AFTER_REG_ALLOC);
-
- code.encode();
- dump(code, dump, DUMP_AFTER_ENCODE);
-
- code.exportELF(base+".o");
- }
-
- dump(code, dump, DUMP_FINAL);
-
- if (do_codegen && (dump & DUMP_DISASSEMBLE) != 0) {
+ if( do_codegen && print_asm )
System.out.println(code.asm());
- }
- if (do_print_size) {
- System.out.printf( "Code Size: %d%n", code._encoding._bits.size());
- }
+ if( print_size )
+ System.out.printf( "Code Size: %d%n", code._encoding._bits.size());
- if (do_print_time) {
+ if( print_time )
print_compilation_times(code);
- }
- if (do_eval) {
+ if( do_eval ) {
// TODO: Support for evaluation of functions with different argument numbers and types
long t = System.currentTimeMillis();
long arg = (i+1 < args.length) ? Integer.parseInt(args[i+1]) : 0;
System.out.println(Eval2.eval(code, arg, 100000));
- if (do_print_time) {
- System.out.printf( "EXECUTION TIME: %.3f sec%n",
- (System.currentTimeMillis() - t) / 1e3);
- }
+ if( print_time )
+ System.out.printf( "EXECUTION TIME: %.3f sec%n", (System.currentTimeMillis() - t) / 1e3);
}
- if (do_run) {
+ if( do_run ) {
if ( !TestC.CPU_PORT.equals( cpu ) || !TestC.CALL_CONVENTION.equals( abi ) )
throw bad("cannot run code on not native target");
- String exe = TestC.OS.startsWith("Windows") ? base+".exe" : base;
- String result = TestC.gcc(base+".o", null, null, true, exe);
+ String exe = TestC.OS.startsWith("Windows") ? outBase+".exe" : outBase;
+ String result = TestC.gcc(outBase+".o", null, null, true, exe);
System.out.print(result);
}
}
diff --git a/src/test/java/com/seaofnodes/simple/TestArm64.java b/src/test/java/com/seaofnodes/simple/TestArm64.java
index a1c02ede..14864d64 100644
--- a/src/test/java/com/seaofnodes/simple/TestArm64.java
+++ b/src/test/java/com/seaofnodes/simple/TestArm64.java
@@ -1,11 +1,9 @@
package com.seaofnodes.simple;
-
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.FunNode;
import com.seaofnodes.simple.node.cpus.arm.arm;
-import junit.framework.Assert;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import com.seaofnodes.simple.util.BAOS;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
@@ -13,36 +11,40 @@ import static org.junit.Assert.assertEquals;
// Runs 32-bit ARM code in an emulator
public class TestArm64 {
- public static EvalArm64 build( String file, int arg, int spills, boolean print ) throws IOException {
- String dir = "src/test/java/com/seaofnodes/simple/progs";
- String src = Files.readString(Path.of(dir + "/"+file+".smp"));
- return build(dir,src, file, arg, spills, print);
- }
- public static EvalArm64 build( String name, String src, int arg, int spills, boolean print ) throws IOException {
- String dir = "src/test/java/com/seaofnodes/simple/progs";
- return build(dir, src ,name, arg, spills, print);
- }
-
- // Compile and run a simple program
- public static EvalArm64 build( String dir, String src, String file, int arg, int spills, boolean print ) throws IOException {
+ public static EvalArm64 build( String main, String src, int arg, int spills, boolean print ) throws IOException {
// Compile and export Simple
- CodeGen code = new CodeGen(src).driver("arm", "SystemV",null);
- if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
+ CodeGen code = new CodeGen(src).driver("arm", "SystemV",true,false);
// Allocation quality not degraded
int delta = spills>>3;
if( delta == 0 ) delta =1;
- if( spills != -1 ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta);
+ if( spills != -1 && !CodeGen.iterSeedOverridden() ) assertEquals("Expect spills:", spills, code._regAlloc._spillScaled, delta);
// Image
byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
EvalArm64 ARM = new EvalArm64(image, 1<<16);
- // Code at offset 0
- System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length);
+ int start = 0; // Code at offset 0
+ int cpool = fill(code._encoding._bits ,image,start);
+ int sdata = fill(code._encoding._cpool,image,cpool);
+ int end = fill(code._encoding._sdata,image,sdata);
+
// Initial incoming int arg
ARM.regs[arm.X0] = arg;
// malloc memory starts at 64K and goes up
+
+ // Look up starting PC or zero if not given
+ if( main!=null )
+ for( FunNode fun : code._linker )
+ if( fun != null && !fun.isDead() && fun._compunit == code.compunit() && main.equals(fun._name) )
+ ARM._pc = code._encoding.opStart(fun);
+
return ARM;
}
+
+ // Fill bits into image; return end point rounded up.
+ private static int fill( BAOS bits, byte[] image, int off) {
+ System.arraycopy(bits.buf(), 0, image, off, bits.size());
+ return (off+bits.size() + 15) & -16;
+ }
}
diff --git a/src/test/java/com/seaofnodes/simple/TestC.java b/src/test/java/com/seaofnodes/simple/TestC.java
index f1831929..7177f765 100644
--- a/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/src/test/java/com/seaofnodes/simple/TestC.java
@@ -4,9 +4,10 @@ import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.TypeInteger;
import com.seaofnodes.simple.util.Ary;
import com.seaofnodes.simple.util.Utils;
+
+import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
@@ -17,77 +18,114 @@ public abstract class TestC {
public static final String OS = System.getProperty("os.name");
public static final String CPU = System.getProperty("os.arch");
- public static final String CALL_CONVENTION = OS.startsWith("Windows") ? "Win64" : "SystemV";
+ public static final String CALL_CONVENTION = OS.startsWith("Windows") ? "win64" : "SystemV";
public static final String CPU_PORT = switch( CPU ) {
case "amd64" -> "x86_64_v2";
default -> throw Utils.TODO("Map Yer CPU Port Here");
};
-
- public static void run( String file, int spills ) throws IOException { run(file,"",spills); }
-
- public static void run( String file, String expected, int spills ) throws IOException {
- run("src/test/java/com/seaofnodes/simple/progs",file,expected,spills);
+ public static final String CPU_ABI = CPU_PORT+"_"+CALL_CONVENTION;
+ public static final String RELEASE_SYS_DIR = "build/release/"+CPU_ABI;
+
+ public static final String C_DRIVERS_DIR = "src/test/java/com/seaofnodes/simple/progs/";
+
+ /**
+ * Compile, link and run
+ * - WITH a C driver
+ * - using the default OS/CPU calling convention.
+ * - no lib sys
+ */
+ public static void runC( String src, String base, String expected, int spills) throws IOException {
+ String cfile = C_DRIVERS_DIR+base+".c";
+ run(src, base, null, CALL_CONVENTION, "", cfile, expected, spills);
}
- // Compile and run a simple program
- public static void run( String dir, String file, TypeInteger arg, String expected, int spills, boolean standalone) throws IOException {
- // Files
- String cfile = dir+"/"+file+".c" ;
- String sfile = dir+"/"+file+".smp";
- String efile = "build/objs/"+file;
-
- // Compile and export Simple
- String src = Files.readString(Path.of(sfile));
- run(src,CALL_CONVENTION,arg, "",standalone? null: cfile,efile,"S",expected,spills);
+ /**
+ * Compile, link and run
+ * - withOUT a C driver
+ * - using the default OS/CPU calling convention.
+ * - no lib sys
+ */
+ public static void runSF( String src, String base, String expected, int spills ) throws IOException {
+ run(src, base, null, CALL_CONVENTION, null,null, expected,spills);
}
- // link with c and also inline
- public static void run(String src, String file, TypeInteger arg, String expected, int spills) throws IOException {
- String dir = "src/test/java/com/seaofnodes/simple/progs";
- String cfile = dir+"/"+file+".c";
- String efile = "build/objs/"+file;
- run(src, CALL_CONVENTION, arg, "", cfile, efile, "S", expected, spills);
+ /**
+ * Compile, link and run
+ * - withOUT a C driver
+ * - using the default OS/CPU calling convention.
+ * - WITH a test lib sys
+ */
+ public static void runSYS( String src, String base, String expected, int spills ) throws IOException {
+ Ary<String> externPaths = new Ary<>(new String[]{RELEASE_SYS_DIR});
+ run(src, base, externPaths, CALL_CONVENTION, null,null, expected,spills);
}
- public static void run(String dir, String file, String expected, int spills ) throws IOException {
- run(dir, file,null, expected, spills, false);
- }
- // Do not link with c file - no inline.
- public static void runS(String file, String expected, int spills ) throws IOException {
- run("src/test/java/com/seaofnodes/simple/progs", file, null, expected, spills, true);
+ /**
+ * Compile, link and run - general case
+ *
+ * @param src Program source to compile
+ * @param base Compiled binary final base name
+ * @param externPaths External symbol search path
+ * @param simple_conv Argument convention when calling Simple code
+ * @param c_conv Argument convention when calling C code, or null if not
+ * linking against a C driver program
+ * @param cfile Associated C program which will drive the Simple program,
+ * or null if not linking against a C driver program
+ * @param expected Expected stdout string
+ * @param spills Expected limit on spill code, used to validate the
+ * register allocation is reasonable
+ */
+ public static void run( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv, String cfile, String expected, int spills ) throws IOException {
+ run(src, base, externPaths, simple_conv, c_conv, cfile, null, expected, spills);
}
-
- // Do not link with c file - just inline with source.
- public static void runSF(String name, String src, TypeInteger arg, String expected, int spills ) throws IOException {
- String efile = "build/objs/"+name;
- run(src,CALL_CONVENTION,arg, "",null,efile,"S",expected,spills);
- }
-
- public static void runS(String file, TypeInteger arg, String expected, int spills ) throws IOException {
- run("src/test/java/com/seaofnodes/simple/progs", file, arg, expected, spills, true);
- }
-
-
- public static void run( String src, String simple_conv, TypeInteger arg, String c_conv, String cfile, String efile, String xtn, String expected, int spills ) throws IOException {
- String bin = efile+xtn;
- String obj = bin+".o";
- String exe = OS.startsWith("Windows") ? bin+".exe" : bin;
+ public static void run( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv, String cfile, String stdin, String expected, int spills ) throws IOException {
+ // Simple file base-name example:
+ // foo.smp ->
+ // build/objs/foo.o - object file
+ // build/objs/foo.exe - linked executable Windows
+ // build/objs/foo - linked executable Linux
+ String pathBase = "build/objs/"+base;
+ String obj = pathBase+".o";
+ String exe = pathBase+(OS.startsWith("Windows") ? ".exe" : "");
// Compile simple, emit ELF
- CodeGen code = new CodeGen(src, arg).driver( CPU_PORT, simple_conv, obj);
+ CodeGen code = new CodeGen(null,"build/objs",externPaths,base,src,126L,TypeInteger.BOT);
+ code.driver( CPU_PORT, simple_conv, false, cfile==null );
- String result = gcc(obj, c_conv, cfile, false, exe );
+ String result = gcc(obj, c_conv, cfile, stdin, linkObjs(externPaths), exe );
assertEquals(expected,result);
// Allocation quality not degraded
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
}
+ // Link with gcc, and execute the resulting binary, returning stdout as a
+ // String. Any errors *assert* instead of returning some error code; this
+ // utility is meant to execute inside a JUnit test which will catch the
+ // assert and flag the test as failed.
+ public static String gcc( String main, double ignore, String... objs ) throws IOException {
+ String BLDDIR = "build/objs/test0/";
+ String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : "");
+ return gcc(objs[0],null,null,false,exe);
+ }
+
+ // Link with gcc, and execute the resulting binary, returning stdout as a
+ // String. Any errors *assert* instead of returning some error code; this
+ // utility is meant to execute inside a JUnit test which will catch the
+ // assert and flag the test as failed.
public static String gcc( String obj, String c_conv, String cfile, boolean stdin, String... args ) throws IOException {
+ return gcc(obj,c_conv,cfile,stdin ? "" : null,null,args);
+ }
+
+ public static String gcc( String obj, String c_conv, String cfile, String stdin, String... args ) throws IOException {
+ return gcc(obj,c_conv,cfile,stdin,null,args);
+ }
+
+ public static String gcc( String obj, String c_conv, String cfile, String stdin, Ary<String> linkObjs, String... args ) throws IOException {
// Compile the C program. Compiling code and constants in the low
// 2Gig. Pointers are 64b BUT since always in the low 2G all the
@@ -96,12 +134,14 @@ public abstract class TestC {
var params = new Ary<>(String.class);
params.add("gcc");
if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main`
+ params.add(obj);
+ if( linkObjs != null )
+ params.addAll(linkObjs.asAry());
params.addAll(new String[] {
- obj,
- "-lm", // Picks up 'sqrt' for newtonFloat tests to compare
- "-g",
- "-o",
- args[0],
+ "-lm", // Picks up 'sqrt' for newtonFloat tests to compare
+ "-g",
+ "-o",
+ args[0],
});
// Calling convention for C calls, if any
if( cfile!=null ) {
@@ -128,12 +168,42 @@ public abstract class TestC {
// Execute results
ProcessBuilder smp = new ProcessBuilder(args);
- if( stdin ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ File stdinFile = null;
+ File stdoutFile = File.createTempFile("simple-stdout",".txt");
+ smp.redirectOutput(stdoutFile);
+ if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ else if( stdin!=null ) {
+ stdinFile = File.createTempFile("simple-stdin",".txt");
+ Files.writeString(stdinFile.toPath(),stdin);
+ smp.redirectInput(stdinFile);
+ }
Process p = smp.start();
+ if( stdin==null )
+ p.getOutputStream().close();
try { exit = (byte)p.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
- result = new String(p.getInputStream().readAllBytes());
+ result = Files.readString(stdoutFile.toPath());
+ if( stdinFile != null )
+ stdinFile.delete();
+ stdoutFile.delete();
if( exit!=0 )
- System.err.println("exec exit code: "+exit);
+ return "exec exit code: "+exit;
return result;
}
+
+ private static Ary<String> linkObjs(Ary<String> externPaths) {
+ if( externPaths == null )
+ return null;
+ Ary<String> objs = new Ary<>(String.class);
+ for( String path : externPaths ) {
+ File file = new File(path);
+ if( file.isFile() && file.getName().endsWith(".o") )
+ objs.add(file.toString());
+ else if( file.isDirectory() ) {
+ File sys = new File(file,"sys.o");
+ if( sys.isFile() )
+ objs.add(sys.toString());
+ }
+ }
+ return objs.isEmpty() ? null : objs;
+ }
}
diff --git a/src/test/java/com/seaofnodes/simple/TestRisc5.java b/src/test/java/com/seaofnodes/simple/TestRisc5.java
index a8f6817e..658909ca 100644
--- a/src/test/java/com/seaofnodes/simple/TestRisc5.java
+++ b/src/test/java/com/seaofnodes/simple/TestRisc5.java
@@ -1,56 +1,52 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
import com.seaofnodes.simple.node.FunNode;
-import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.node.cpus.riscv.riscv;
+import com.seaofnodes.simple.util.BAOS;
import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
+
import static org.junit.Assert.*;
// Runs 32-bit R5 code in an emulator
public abstract class TestRisc5 {
- public static EvalRisc5 build( String file, int arg, int spills, boolean print ) throws IOException {
- String dir = "src/test/java/com/seaofnodes/simple/progs";
- String src = Files.readString(Path.of(dir + "/"+file+".smp"));
- return build(dir,src, file, null, arg, spills, print);
- }
-
- public static EvalRisc5 build( String name, String src, int arg, int spills, boolean print ) throws IOException {
- String dir = "src/test/java/com/seaofnodes/simple/progs";
- return build(dir, src ,name, null, arg, spills, print);
- }
-
- // Compile and run a simple program
- public static EvalRisc5 build( String dir, String src, String file, String main, int arg, int spills, boolean print ) throws IOException {
+ public static EvalRisc5 build( String src, String main, int arg, int spills, boolean print ) throws IOException {
// Compile and export Simple
- CodeGen code = new CodeGen(src).driver("riscv", "SystemV",null);
- if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
+ CodeGen code = new CodeGen(src).driver("riscv", "SystemV",true,false);
// Allocation quality not degraded
int delta = spills>>3;
if( delta==0 ) delta = 1;
- if( spills != -1 )
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
// Image
byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
EvalRisc5 R5 = new EvalRisc5(image, 1<<16);
- // Code at offset 0
- System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length);
+ int start = 0; // Code at offset 0
+ int cpool = fill(code._encoding._bits ,image,start);
+ int sdata = fill(code._encoding._cpool,image,cpool);
+ int end = fill(code._encoding._sdata,image,sdata);
+
// Initial incoming int arg
R5.regs[riscv.A0] = arg;
// malloc memory starts at 64K and goes up
// Look up starting PC or zero if not given
if( main!=null )
- for( Node use : code._start._outputs )
- if( use instanceof FunNode fun && main.equals(fun._name) )
- R5._pc = code._encoding._opStart[fun._nid];
+ for( FunNode fun : code._linker )
+ if( fun != null && !fun.isDead() && fun._compunit == code.compunit() && main.equals(fun._name) )
+ R5._pc = code._encoding.opStart(fun);
return R5;
}
+ // Fill bits into image; return end point rounded up.
+ private static int fill( BAOS bits, byte[] image, int off) {
+ System.arraycopy(bits.buf(), 0, image, off, bits.size());
+ return (off+bits.size() + 15) & -16;
+ }
+
}
diff --git a/src/test/java/com/seaofnodes/simple/TypeTest.java b/src/test/java/com/seaofnodes/simple/TypeTest.java
index 0fd56607..e881a2bf 100644
--- a/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -10,18 +10,19 @@ public class TypeTest {
// Test basic properties and GLB
@Test
public void testTypeAdHoc() {
- Assert.assertEquals( Type.BOTTOM, TypeInteger.TRUE.meet(TypeNil.NIL) );
- Assert.assertEquals( Type.BOTTOM, TypeInteger.TOP .meet(TypeNil.NIL) );
+ Assert.assertEquals( TypeScalar.BOT, TypeInteger.TRUE.meet(TypeNil.NIL) );
+ Assert.assertEquals( TypeScalar.BOT, TypeInteger.TOP .meet(TypeNil.NIL) );
TypeStruct s1 = TypeStruct.make("s1",false,
- Field.make("a", TypeInteger.BOT,-1, false, false),
- Field.make("b", TypeInteger.BOT,-2, false, false) );
+ Field.make("a", TypeInteger.BOT,-1, false),
+ Field.make("b", TypeInteger.BOT,-2, false) );
TypeStruct s2 = TypeStruct.make("s2",false,
- Field.make("a", TypeInteger.BOT,-3, false, false),
- Field.make("b", TypeInteger.BOT,-4, false, false) );
+ Field.make("a", TypeInteger.BOT,-3, false),
+ Field.make("b", TypeInteger.BOT,-4, false) );
TypeStruct x1ro = (TypeStruct)s1.makeRO();
TypeMemPtr p1 = TypeMemPtr.make(s1);
- Assert.assertEquals(x1ro, ((TypeMemPtr)p1.glb(false))._obj);
+ // GLB no longer forces RO
+ //Assert.assertEquals(x1ro, ((TypeMemPtr)p1.glb(false))._obj);
Assert.assertNotEquals(s1, s1.dual());
TypeStruct s1dglb = ((TypeMemPtr)p1.dual().glb(false))._obj;
Assert.assertTrue(x1ro.isa(s1dglb));
@@ -36,12 +37,13 @@ public class TypeTest {
Assert.assertNotEquals(m3, m4);
Assert.assertEquals(TypeStruct.BOT, s1.meet(s2));
- Assert.assertEquals(TypeMem .BOT, m1.meet(m2));
- Assert.assertEquals(TypeMem.make(1,Type.BOTTOM), m1.meet(m3));
- Assert.assertEquals(TypeMem .BOT, m3.meet(m4));
+ TypeMem scalarMemBot = TypeMem.make(1,TypeScalar.BOT,false,false,false,XInt.FULL,XInt.FULL);
+ Assert.assertEquals(scalarMemBot, ((TypeMem)m1.meet(m2)).makeFrom( XInt.FULL, XInt.FULL));
+ Assert.assertEquals(TypeMem.make(1,TypeScalar.BOT), m1.meet(m3));
+ Assert.assertEquals(scalarMemBot, ((TypeMem)m3.meet(m4)).makeFrom( XInt.FULL, XInt.FULL));
Assert.assertEquals(TypeMem.make(2,Type.BOTTOM), m1.glb(false));
- Assert.assertEquals(TypeMem.make(2,Type.XNIL), m1.dual());
+ Assert.assertEquals(TypeMem.make(2,Type.XNIL,true,true,true, XInt.FULL, XInt.FULL), m1.dual());
Assert.assertEquals(m4.dual(), m4.glb(false).dual());
TypeMemPtr ptr1 = TypeMemPtr.make(s1);
@@ -58,14 +60,17 @@ public class TypeTest {
Assert.assertNotEquals(ptr1, ptr2);
Type p1glb = ptr1.glb(false);
- Assert.assertNotEquals(ptr1, p1glb );
+ // GLB no longer forces RO
+ //Assert.assertNotEquals(ptr1, p1glb );
Type p1nro = ptr1nil.makeRO();
- Assert.assertEquals(p1nro, p1glb);
+ // GLB no longer forces RO
+ //Assert.assertEquals(p1nro, p1glb);
Assert.assertEquals(ptr1, ptr1.dual().dual());
Assert.assertTrue(p1glb.makeRO().isa( ptr1.dual().glb(false)));
Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT), ptr1.meet(ptr2nil));
- Assert.assertEquals(p1glb, ptr1.meet(TypeNil.NIL).makeRO());
+ // GLB no longer forces RO
+ //Assert.assertEquals(p1glb, ptr1.meet(TypeNil.NIL).makeRO());
TypeMemPtr TOP = TypeMemPtr.TOP;
TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT);
@@ -83,7 +88,7 @@ public class TypeTest {
Type nullableptr1_dual = ptr1nil.dual();
// Cyclic check
- TypeStruct S1 = ((TypeMemPtr)TypeStruct.SFLT2.field("s1")._t)._obj;
+ TypeStruct S1 = ((TypeMemPtr)TypeStruct.SFLT1.field("s1")._t)._obj.close();
Assert.assertFalse(s1.isFinal());
Type s1ro = S1.makeRO();
Type s1ro2 = S1.makeRO();
@@ -126,7 +131,7 @@ public class TypeTest {
Type t02 = t0.join(t2);
Type t12 = t1.join(t2);
Type mt = t02.meet(t12);
- assertSame(mt,t12);
+ assertSame(t0+" isa "+t1+" joined "+t2, mt,t12);
}
}
@@ -152,8 +157,8 @@ public class TypeTest {
Type d1 = t1.dual();
Type ta = dm.meet(d1);
Type tb = dm.meet(d0);
- assertSame(ta,d1);
- assertSame(tb,d0);
+ assertSame(t0+" & "+t1+" mt="+mt, ta,d1);
+ assertSame(t0+" & "+t1+" mt="+mt, tb,d0);
}
private static void assoc( Type t0, Type t1, Type t2 ) {
@@ -161,15 +166,15 @@ public class TypeTest {
Type t12 = t1 .meet(t2 );
Type t01_2 = t01.meet(t2 );
Type t0_12 = t0 .meet(t12);
- assertSame(t01_2,t0_12);
+ assertSame(t0+" & "+t1+" & "+t2, t01_2,t0_12);
}
// Test cyclic types and meets
@Test
public void testCyclic0() {
- Type d0 = TypeStruct.SFLT2.dual();
+ Type d0 = TypeStruct.SFLT1.dual();
Type d1 = d0.dual();
- assertSame(TypeStruct.SFLT2,d1);
+ assertSame(TypeStruct.SFLT1,d1);
}
@Test
@@ -184,8 +189,8 @@ public class TypeTest {
public void testList() {
TypeStruct list = TypeStruct.open("List");
TypeMemPtr plist = TypeMemPtr.makeNullable(list);
- list = list.add(Field.make("next", plist, 2, false, false ) );
- list = list.add(Field.make("x", TypeInteger.BOT, 3, false, false));
+ list = list.add(Field.make("next", plist, 2, false) );
+ list = list.add(Field.make("x", TypeInteger.BOT, 3, false));
// Make a cyclic type
list = list.close();
// Fields are mutable
@@ -193,6 +198,17 @@ public class TypeTest {
TypeStruct flist = (TypeStruct)list.makeRO();
Assert.assertTrue(flist.isFinal());
Assert.assertNotSame( list, flist );
+ }
+ @Test
+ public void testOpen() {
+ TypeStruct open = TypeStruct.make("List",false);
+ TypeMemPtr plist = TypeMemPtr.makeNullable(open);
+ TypeStruct list = open.add(Field.make("next", plist, 2, false) );
+ list = list.add(Field.make("x", TypeInteger.BOT, 3, false));
+ // Make a cyclic type
+ list = list.close();
+ Type mt = open.meet(list);
+ Assert.assertEquals(list,mt);
}
}
diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
deleted file mode 100644
index 911acc5c..00000000
--- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
+++ /dev/null
@@ -1,382 +0,0 @@
-package com.seaofnodes.simple.evaluator;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
-import java.util.*;
-
-public class Evaluator {
-
- private static final Object MEMORY = new Object();
-
- public record Obj(TypeStruct struct, Object[] fields) {
- private void init(IdentityHashMap<Obj, Integer> objs) {
- var oid = objs.get(this);
- if (oid != null) {
- if (oid != 0) objs.put(this, 0);
- return;
- }
- objs.put(this, -1);
- for (var obj : fields) {
- if (obj instanceof Obj o) o.init(objs);
- }
- }
- private static int p(StringBuilder sb, IdentityHashMap<Obj, Integer> objs, int id, String indentation, String step, String sep, Object obj) {
- if (obj instanceof Obj o) return o.p(sb, objs, id, indentation, step, sep);
- sb.append(obj);
- return id;
- }
- private int p(StringBuilder sb, IdentityHashMap<Obj, Integer> objs, int id, String indentation, String step, String sep) {
- var cid = objs.get(this);
- assert cid != null;
- if (cid > 0) {
- sb.append("obj@").append(cid);
- return id;
- }
- if (struct._name.equals("[u8]")) {
- sb.append('"');
- for (int i=struct._fields.length-1; i<fields.length; i++) {
- var v = fields[i];
- if (v == null) v = 0;
- assert v instanceof Number;
- var n = ((Number)v).byteValue() & 0xFF;
- if (n >= 0x20 && n < 0x80) {
- if (n == 0x22 || n == 0x5C) sb.append("\\");
- sb.append((char)n);
- } else {
- sb.append("\\x").append("0123456789abcdef".charAt(n >> 4)).append("0123456789abcdef".charAt(n & 0xF));
- }
- }
- sb.append('"');
- return id;
- }
- sb.append("Obj<").append(struct._name).append(">");
- if (cid == 0) {
- cid = id++;
- objs.put(this, cid);
- sb.append("@").append(cid);
- }
- sb.append("{");
- if (struct._fields.length == 0) {
- assert !struct.isAry();
- sb.append("}");
- return id;
- }
- String nextIndent = indentation + step;
- int e = struct._fields.length - 1;
- for(int i=0; i<e; i++) {
- sb.append(nextIndent).append(struct._fields[i]._fname).append("=");
- id = p(sb, objs, id, nextIndent, step, sep, fields[i]);
- sb.append(sep);
- }
- sb.append(nextIndent).append(struct._fields[e]._fname).append("=");
- if (struct.isAry()) {
- sb.append("[");
- if (fields.length > e) {
- String innerIndent = nextIndent + step;
- sb.append(innerIndent);
- id = p(sb, objs, id, nextIndent, step, sep, fields[e]);
- for (int i = e+1; i < fields.length; i++) {
- sb.append(sep).append(innerIndent);
- id = p(sb, objs, id, nextIndent, step, sep, fields[i]);
- }
- sb.append(nextIndent);
- }
- sb.append("]");
- } else {
- id = p(sb, objs, id, nextIndent, step, sep, fields[e]);
- }
- sb.append(indentation).append("}");
- return id;
- }
- private StringBuilder p(StringBuilder sb, String indentation, String step, String sep) {
- var objs = new IdentityHashMap<Obj, Integer>();
- init(objs);
- p(sb, objs, 1, indentation, step, sep);
- return sb;
- }
- @Override
- public String toString() {
- if (struct._name.equals("[u8]")) {
- var sb = new StringBuilder();
- for (int i=struct._fields.length-1; i<fields.length; i++) {
- var v = fields[i];
- if (v == null) v = 0;
- assert v instanceof Number;
- var n = ((Number)v).byteValue() & 0xFF;
- sb.append((char)n);
- }
- return sb.toString();
- }
- return p(new StringBuilder(), "", "", ",").toString();
- }
- public String pretty() {
- return p(new StringBuilder(), "\n", " ", "").toString();
- }
- }
-
- private static int getFieldIndex(TypeStruct struct, MemOpNode memop, long off) {
- int idx = struct.find(memop._name);
- if( idx >= 0 ) return idx;
- throw new AssertionError("Field "+memop._name+" not found in struct " + struct._name);
- }
-
- public enum Status {
- TIMEOUT,
- FALLTHROUGH
- }
-
- /**
- * Find the start node from some node in the graph or null if there is no start node
- */
- private static StartNode findStart(BitSet visit, Node node) {
- if (node == null) return null;
- if( visit.get(node._nid) ) return null;
- visit.set(node._nid);
- StartNode start = node instanceof StartNode ? (StartNode) node : null;
- for( Node def : node._inputs ) {
- var res = findStart(visit,def);
- if( res != null ) start = res;
- }
- for( Node use : node._outputs ) {
- var res = findStart(visit,use);
- if( res != null ) start = res;
- }
- return start;
- }
-
- private final Object[] values;
- private final ArrayList<Object> phiCache = new ArrayList<>();
- private final StartNode start;
- private final Scheduler.Block startBlock;
-
- public Evaluator(Node graph) {
- var visited = new BitSet();
- this.start = findStart(visited, graph);
- if (this.start == null) {
- this.values = null;
- this.startBlock = null;
- return;
- }
- this.startBlock = Scheduler.schedule(this.start);
- this.values = new Object[visited.length()];
- }
-
- private long div(DivNode div) {
- long in2 = vall(div.in(2));
- return in2 == 0 ? 0 : vall(div.in(1)) / in2;
- }
- private double div(DivFNode div) {
- double in2 = vald(div.in(2));
- return in2 == 0 ? 0 : vald(div.in(1)) / in2;
- }
-
- private static long offToIdx(long off, TypeStruct t) {
- off -= t.aryBase();
- int scale = t.aryScale();
- long mask = (1L << scale)-1;
- assert (off & mask) == 0;
- return off>>scale;
- }
-
- private Object alloc(NewNode alloc) {
- TypeStruct type = alloc._ptr._obj;
- Object[] body=null;
- int num;
- if( type.isAry() ) {
- long sz = (Long)val(alloc.in(1));
- long n = offToIdx(sz, type);
- if( n < 0 )
- throw new NegativeArraySizeException(""+n);
- body = new Object[(int)n+1]; // Array body
- var elem = type._fields[1]._t;
- if (elem instanceof TypeInteger) {
- for (int i=0; i<n; i++) body[i+1] = 0L;
- } else if (elem instanceof TypeFloat) {
- for (int i = 0; i < n; i++) body[i+1] = 0D;
- } else {
- assert elem instanceof TypeMemPtr;
- }
- // Length value
- body[0] = vall(alloc.in(2+2));
- } else {
- body = new Object[num = type._fields.length];
- for( int i=0; i<num; i++ )
- body[i] = switch( alloc._ptr._obj._fields[i]._t ) {
- case TypeInteger ti -> 0L;
- case TypeFloat tf -> 0;
- default -> null;
- };
- }
- Object[] mems = new Object[type._fields.length+2];
- // mems[0] is control
- mems[1] = new Obj(type,body); // the ref
- // mems[2+...] are memory aliases
- return mems;
- }
-
- private Object load(LoadNode load) {
- var from = (Obj)val(load.ptr());
- var off = vall(load.off());
- var idx = getFieldIndex(from.struct, load, (Long)off);
- if( idx==from.struct._fields.length-1 && from.struct.isAry() ) {
- long len = from.fields.length - from.struct._fields.length + 1;
- long i = offToIdx(off, from.struct);
- if( i < 0 || i >= len )
- throw new ArrayIndexOutOfBoundsException("Array index out of bounds " + i + " < " + len);
- return from.fields[(int)i+from.struct._fields.length-1];
-
- } else
- return from.fields[idx];
- }
-
- private Object store(StoreNode store) {
- var to = (Obj)val(store.ptr());
- var off = vall(store.off());
- var val = val(store.val());
- var idx = getFieldIndex(to.struct, store, (Long)off);
- if( idx==to.struct._fields.length-1 && to.struct.isAry() ) {
- long len = to.fields.length - to.struct._fields.length + 1;
- long i = offToIdx(off, to.struct);
- if( i < 0 || i >= len )
- throw new RuntimeException("Array index out of bounds " + off + " <= " + len);
- to.fields[(int)i+to.struct._fields.length-1] = val;
- } else
- to.fields[idx] = val;
- return null;
- }
-
- private static boolean isTrue(Object obj) {
- if (obj == null) return false;
- if (obj instanceof Number n) return n.longValue() != 0;
- return true;
- }
-
- private Object val(Node node) {
- return values[node._nid];
- }
-
- private long vall(Node node) {
- var v = val(node);
- if (v instanceof Number n) return n.longValue();
- throw new AssertionError("Not a long " + v);
- }
- private double vald(Node node) {
- var v = val(node);
- if (v instanceof Number n) return n.doubleValue();
- if( v == null ) return 0;
- throw new AssertionError("Not a double " + v);
- }
-
- private Object cons(ConstantNode cons) {
- var type = cons.compute();
- if (type instanceof TypeInteger i) return i.isConstant() ? i.value() : Integer.MAX_VALUE;
- if (type instanceof TypeFloat i) return i.value();
- return null;
- }
-
- private Object exec(Node node) {
- return switch (node) {
- case ConstantNode cons -> cons(cons);
- case AddNode add -> vall(add.in(1)) + vall(add.in(2));
- case AddFNode add -> vald(add.in(1)) + vald(add.in(2));
- case BoolNode.EQF eq -> vald(eq.in(1)) == vald(eq.in(2)) ? 1L : 0L;
- case BoolNode.LEF le -> vald(le.in(1)) <= vald(le.in(2)) ? 1L : 0L;
- case BoolNode.LTF lt -> vald(lt.in(1)) < vald(lt.in(2)) ? 1L : 0L;
- case BoolNode.EQ eq -> Objects.equals(val(eq.in(1)), val(eq.in(2))) ? 1L : 0L;
- case BoolNode.LE le -> vall(le.in(1)) <= vall(le.in(2)) ? 1L : 0L;
- case BoolNode.LT lt -> vall(lt.in(1)) < vall(lt.in(2)) ? 1L : 0L;
- case DivNode div -> div(div);
- case DivFNode div -> div(div);
- case MinusNode minus -> -vall(minus.in(1));
- case MinusFNode minus -> -vald(minus.in(1));
- case MulNode mul -> vall(mul.in(1)) * vall(mul.in(2));
- case MulFNode mul -> vald(mul.in(1)) * vald(mul.in(2));
- case NotNode not -> isTrue(val(not.in(1))) ? 0L : 1L;
- case SubNode sub -> vall(sub.in(1)) - vall(sub.in(2));
- case SubFNode sub -> vald(sub.in(1)) - vald(sub.in(2));
- case ShlNode shl -> vall(shl.in(1)) << vall(shl.in(2));
- case ShrNode shr -> vall(shr.in(1)) >>> vall(shr.in(2));
- case SarNode sar -> vall(sar.in(1)) >> vall(sar.in(2));
- case AndNode and -> vall(and.in(1)) & vall(and.in(2));
- case OrNode or -> vall(or .in(1)) | vall(or .in(2));
- case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2));
- case CastNode cast -> val(cast.in(1));
- case ToFloatNode cast -> (double)vall(cast.in(1));
- case LoadNode load -> load(load);
- case StoreNode store -> store(store);
- case NewNode alloc -> alloc(alloc);
- case CProjNode cproj -> ((Object[])val(cproj.ctrl()))[cproj._idx];
- case ProjNode proj -> ((Object[])val( proj.in(0) ))[ proj._idx];
- case MemMergeNode mem -> null;
- case ReadOnlyNode ro -> val(ro.in(1));
- case CallNode call -> Utils.TODO(); // should not reach here, go the IfNode route
- default -> throw new AssertionError("Unexpected node " + node);
- };
- }
-
- /**
- * Run the graph until either a return is found or the number of loop iterations are done.
- */
- public Object evaluate(long parameter, int loops) {
- if (start == null) return Status.TIMEOUT;
- var s = new Object[start.compute()._types.length];
- s[1] = MEMORY;
- s[2] = parameter;
- values[start._nid] = s;
- int i=0;
- Scheduler.Block block = this.startBlock;
- for(;;) {
- for(; i<block.nodes().length;i++) values[block.nodes()[i]._nid] = exec(block.nodes()[i]);
- i=0;
- switch (block.exit()) {
- case null:
- return Status.FALLTHROUGH;
- case ReturnNode ret : return val(ret .expr());
- case IfNode ifn:
- block = block.next()[isTrue(val(ifn.in(1))) ? 0 : 1];
- if (block == null) return Status.FALLTHROUGH;
- break;
- case RegionNode region:
- if (loops-- <= 0) return Status.TIMEOUT;
- int exit = block.exitId();
- assert exit > 0 && region.nIns() > exit;
- block = block.next()[0];
- assert block != null;
- for (; i < block.nodes().length; i++) {
- if (!(block.nodes()[i] instanceof PhiNode phi)) break;
- var val = region instanceof FunNode fun && "main".equals(fun._name) && ((ParmNode)phi)._idx==2 && exit==1
- ? parameter
- : val(phi.in(exit));
- phiCache.add(val);
- }
- for (i=0; i<phiCache.size(); i++) {
- values[block.nodes()[i]._nid] = phiCache.get(i);
- }
- phiCache.clear();
- break;
- default:
- throw Utils.TODO("Unexpected control node " + block.exit());
- }
- }
- }
-
- public static Object evaluate(StopNode graph) {
- return evaluate(graph, 0);
- }
-
- public static Object evaluate(StopNode graph, long parameter) {
- return evaluate(graph, parameter, 1000);
- }
-
- public static Object evaluate(StopNode graph, long parameter, int loops) {
- var res = evaluateWithResult(graph, parameter, loops);
- if (res == Status.TIMEOUT) throw new RuntimeException("Timeout");
- return res;
- }
-
- public static Object evaluateWithResult(StopNode graph, long parameter, int loops) {
- return new Evaluator(graph).evaluate(parameter, loops);
- }
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java b/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
deleted file mode 100644
index c837bd6f..00000000
--- a/src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java
+++ /dev/null
@@ -1,605 +0,0 @@
-package com.seaofnodes.simple.evaluator;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.Utils;
-import java.util.*;
-
-/**
- * A late scheduler for test cases.
- * <br>
- * <br>
- * This scheduler is used for tests and is not the final scheduler.
- * <br>
- * <br>
- * The scheduler schedules not as late as possible. Since it is just for test cases this is fine.
- * A better scheduler will try to place nodes outside of loops. Due to the schedule late strategy
- * this scheduler might place initialization code in loops.
- * <br>
- * We schedule late here since memory edges have some implicit information. There can only be one
- * memory edge (for a field) at a time. However, before <code>if</code> statements this might split.
- * When scheduling early a store might be moved out of the loop but then there are two edges alive.
- * The one through the store node and the adjacent one. This is an invalid schedule and instead of
- * handling this case a late scheduler does not have this problem as memory edges are combined
- * through phi nodes.
- * <br>
- * <br>
- * A bit about this scheduler. It first discovers all alive nodes and allocates side data for them.
- * Then the control flow graph is build. Then, all phis are placed and finally all the remaining nodes
- * in an order where a node is placed when all alive uses are placed. Finally, the output is produced.
- */
-public class Scheduler {
-
- /**
- * A basic block with a schedule of the containing nodes.
- *
- * @param nodes The nodes in the final schedule of this block
- * @param exit The exit node of this block.
- * @param exitId In case the next block is a region this gives the entry id into the region.
- * @param next The next blocks.
- */
- public record Block(Node[] nodes, Node exit, int exitId, Block[] next) {}
-
- /**
- * Definition of a block in the building process.
- */
- private static class BasicBlock {
-
- /**
- * Dominator of this block
- */
- final BasicBlock dom;
- /**
- * Id of this block where <code>a.depth < b.depth</code> means that a is before b or
- * both are in different branches.
- */
- final int depth;
- /**
- * Previous blocks
- */
- final BasicBlock[] prev;
- /**
- * Entry to this block
- */
- final Node entry;
- /**
- * Schedules node in reverse order.
- */
- final ArrayList<Node> reverseSchedule = new ArrayList<>();
-
- /**
- * Initialize the block.
- * @param entry The node that started this block
- * @param depth The depth of this node. All previous nodes need to have a lower value.
- * @param prev The previous blocks.
- */
- BasicBlock(Node entry, int depth, BasicBlock... prev) {
- BasicBlock dom = null;
- int max = -1;
- for(var bb: prev) {
- max = Math.max(max, bb.depth);
- dom = dom==null?bb:dom(dom, bb);
- }
- assert depth > max;
- this.dom = dom;
- this.depth = depth;
- this.entry = entry;
- this.prev = prev;
- }
- BasicBlock(Node entry, BasicBlock prev) {
- this(entry,prev.depth+1,prev);
- }
-
- }
-
- /**
- * Ancillary data for nodes used during the scheduling process.
- */
- private static class NodeData {
-
- /**
- * The node this ancillary data is used for.
- */
- final Node node;
-
- /**
- * Number of alive users not yet scheduled.
- */
- int users = 1;
-
- /**
- * Block into which the node should be placed so far.
- * This will be updated with every placed use.
- */
- BasicBlock block;
-
- /**
- * Initialize ancillary data for a node
- * @param node The node this ancillary data is associated with.
- */
- NodeData(Node node) {
- this.node = node;
- }
-
- }
-
- /**
- * Ancillary data for nodes.
- */
- private final IdentityHashMap<Node, NodeData> data = new IdentityHashMap<>();
- /**
- * List of nodes which can be placed since all users are placed.
- */
- private final Stack<NodeData> scheduleQueue = new Stack<>();
-
- /**
- * Get the ancillary data for an alive node.
- * @param node The node for which the ancillary data is requested.
- * @return The ancillary data for the node.
- */
- NodeData d(Node node) {
- return od(node).orElseThrow(AssertionError::new);
- }
-
- /**
- * Get the ancillary data for a potentially dead node.
- * @param node The node for which the ancillary data is requested.
- * @return The ancillary data for the node an alive node or
- * <code>Optional.empty()</code> for <code>null</code> or dead nodes.
- */
- Optional<NodeData> od(Node node) {
- return Optional.ofNullable(data.get(node));
- }
-
- /**
- * Checks a node for validity
- * @param data The node to check.
- * @return true if all placed inputs are before this node.
- */
- private boolean isValid(NodeData data) {
- return data.node._inputs.stream()
- .map(i->i==null?null:d(i))
- .map(d->d==null||d.users>0?null:d.block)
- .allMatch(d->d==null||dom(d, data.block)==d);
- }
-
- /**
- * Checks that node is not XCtrl.
- * @param node The node to check.
- * @return true if the node is not XCtrl.
- */
- private static boolean isNotXCtrl(Node node) {
- return !(node instanceof ConstantNode c && c.compute() == Type.XCONTROL) && !(node instanceof XCtrlNode);
- }
-
- /**
- * Return the dominator of <code>a</code> and <code>b</code>
- * @param a Block a
- * @param b Block b
- * @return The dominator of <code>a</code> and <code>b</code>
- */
- private static BasicBlock dom(BasicBlock a, BasicBlock b) {
- while (a!=b) {
- if (a.depth >= b.depth) a=a.dom;
- if (b.depth > a.depth) b=b.dom;
- }
- return a;
- }
-
- /**
- * Return if a is before b.
- * @param a Block a
- * @param b Block b
- * @return true is a is before b.
- */
- private static boolean isBefore(BasicBlock a, BasicBlock b) {
- while (b.depth > a.depth) {
- while (b.dom.depth > a.depth) b = b.dom;
- for (int i=0; i<b.prev.length-1; i++) {
- if (isBefore(a, b.prev[i])) return true;
- }
- b = b.prev[b.prev.length-1];
- }
- return a == b;
- }
-
- /**
- * Is node placed during the control flow graph build.
- * @param data Node to check.
- * @return true if node is placed during the control flow graph build.
- */
- private static boolean isPinnedNode(NodeData data) {
- return data.node instanceof CFGNode || data.node instanceof PhiNode;
- }
-
- /**
- * Refine placement of a node.
- * @param data The node to refine.
- * @param block The block before the node should be scheduled.
- */
- private void refinePlacement(NodeData data, BasicBlock block) {
- assert !isPinnedNode(data);
- data.block = data.block == null ? block : dom(data.block, block);
- assert isValid(data);
- }
-
- /**
- * Refine placement if before is happening before data
- * @param data The node for which the placement should be refined.
- * @param before The node before which the data node should be placed.
- */
- private void optionalRefinePlacement(NodeData data, Node before) {
- od(before).ifPresent(d->{
- // before might be in a different branch. So check this case with isBefore
- // and only refine the placement if it is not in a different branch.
- if (isBefore(d.block, data.block)) refinePlacement(data, d.block);
- });
- }
-
- /**
- * Schedule all nodes not yet scheduled.
- */
- private void doSchedule() {
- while (!scheduleQueue.isEmpty()) {
- var data = scheduleQueue.pop();
- assert !isPinnedNode(data);
-
- assert data.block != null;
- assert data.users == 0;
-
- if (data.node instanceof MemOpNode l && l._isLoad) {
- assert isValid(data);
- // Handle anti-deps of load nodes.
- // At this point all anti-dep nodes are scheduled,
- // but they did not refine the placement
- // so do that now.
- var mem = data.node.in(1);
- for(var out : mem._outputs) {
- if (out instanceof PhiNode p) {
- var r = p.in(0);
- for (int i = 1; i < p.nIns(); i++) {
- if (p.in(i) == mem) optionalRefinePlacement(data, r.in(i));
- }
- } else if (!(out instanceof MemOpNode ld && ld._isLoad)) {
- optionalRefinePlacement(data, out);
- }
- }
- }
-
- assert isValid(data);
- data.block.reverseSchedule.add(data.node);
-
- for(var in:data.node._inputs) {
- if (in!=null) update(d(in), data.block);
- }
- if (data.node instanceof MemOpNode s && !s._isLoad) {
- // Store nodes have anti-deps to load nodes.
- // So decrease the uses of these loads when the store is placed.
- for (var out: ((Node)s).in(1)._outputs) {
- if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(this::decUsers);
- }
- }
- }
-
- // Now all nodes should be placed and have a block assigned
- assert this.data.values().stream().noneMatch(d->d.block==null);
- }
-
- /**
- * Decrement the not-yet-placed users of a block.
- * @param data The node for which the users should be decremented.
- */
- private void decUsers(NodeData data) {
- assert data.users > 0;
- if (--data.users == 0) {
- assert data.block != null;
- assert isValid(data);
-
- // When all users are gone this node can be scheduled.
- scheduleQueue.push(data);
- }
- }
-
- /**
- * Update the placement of a node
- * @param data The node to update
- * @param block The block before which the node should happen.
- */
- private void update(NodeData data, BasicBlock block) {
- assert block != null;
- if (data.users == 0) {
- assert isPinnedNode(data);
- return;
- }
- refinePlacement(data, block);
- decUsers(data);
- }
-
- /**
- * Checks if a CFG node is ready to be placed. This is used for regions joining branches.
- * @param node The CFG node to check
- * @return true if all parents of a CFG node are placed.
- */
- private boolean isCFGNodeReady(Node node) {
- if (node instanceof LoopNode l) {
- assert d(l.in(1)).block != null;
- } else if (node instanceof RegionNode r) {
- for(int i=1; i<r.nIns(); i++) {
- if (od(r.in(i)).map(d->d.block==null).orElse(false)) return false;
- }
- } else if (node instanceof XCtrlNode) {
- return false;
- } else {
- assert d(node.in(0)).block != null;
- }
- return true;
- }
-
- /**
- * Schedule all phi nodes.
- * @param phiQueue List of all the phi nodes to schedule.
- */
- private void schedulePhis(Stack<NodeData> phiQueue) {
- while(!phiQueue.empty()) {
- var data = phiQueue.pop();
- var phi = (PhiNode) data.node;
- var r = phi.in(0);
- data.block = d(r).block;
- for(int i=1; i<phi.nIns(); i++) {
- var in = phi.in(i);
- od(r.in(i)).ifPresent(d->update(d(in), d.block));
- }
- }
- }
-
- /**
- * Build the control flow graph.
- * @param start The start node.
- */
- private void doBuildCTF(StartNode start) {
- var queue = new Stack<NodeData>();
- var phiQueue = new Stack<NodeData>();
- queue.push(d(start));
- while (!queue.isEmpty()) {
- var data = queue.pop();
- var node = data.node;
- assert node instanceof CFGNode;
- BasicBlock block = switch (node) {
- case StartNode s -> new BasicBlock(s, 0);
- case LoopNode l -> new BasicBlock(l, d(l.in(1)).block);
- case RegionNode r -> {
- var prev = new ArrayList<BasicBlock>();
- int depth = 0;
- for(int i=1; i<r.nIns(); i++) {
- if( !od(r.in(i)).isPresent() ) continue;
- BasicBlock bb = d(r.in(i)).block;
- prev.add(bb);
- depth = Math.max(depth,bb.depth);
- }
- yield new BasicBlock(r, depth+1, prev.toArray(BasicBlock[]::new));
- }
- case IfNode i -> d(i.in(0)).block;
- case ReturnNode ret -> d(ret .in(0)).block;
- case CallNode call -> throw Utils.TODO();
- default -> new BasicBlock(node, d(node.in(0)).block);
- };
- data.block = block;
- if (node instanceof RegionNode r) {
- // Regions might have phis which need to be scheduled.
- // Put them on a list for later scheduling.
- for (var out:r._outputs) {
- if (out instanceof PhiNode p) {
- var d = this.data.get(p);
- if (d!=null) {
- d.users=0;
- phiQueue.push(d);
- }
- }
- }
- }
- if( node instanceof FunNode fun ) {
- // Cannot find CFG user from fun via a naive search, because always find
- // Return but Return is not always the one CFG user.
- CFGNode n = fun.uctrl();
- assert isCFGNodeReady(n) && d(n).block==null;
- queue.push(d(n));
- } else if( !(node instanceof ReturnNode) ) {
- for (Node n : node._outputs)
- if(n instanceof CFGNode && isCFGNodeReady(n) && d(n).block == null)
- queue.push(d(n));
- }
- var b = block;
- for(var in:data.node._inputs) od(in).ifPresent(d->update(d, b));
- }
-
- // Schedule all phi nodes.
- schedulePhis(phiQueue);
- }
-
- /**
- * Mark a node alive and create ancillary data for it.
- * @param queue List into which this node should be placed if it needs to be visited.
- * @param node Node which should be marked alive.
- * @param cfg If this node is a CFG node.
- */
- private void markAlive(Stack<NodeData> queue, Node node, boolean cfg) {
- var nd = od(node).orElse(null);
- if (nd != null) {
- // Node was already visited, just increase the users.
- if (nd.users>0) nd.users++;
- return;
- }
- assert node instanceof CFGNode == cfg;
- assert isNotXCtrl(node);
- nd = new NodeData(node);
- if (cfg) nd.users=0;
- data.put(node, nd);
- queue.push(nd);
- }
-
- /**
- * Mark all alive nodes.
- * First all CFG nodes are marked, then all other nodes.
- * @param node THe start node.
- */
- private void doMarkAlive(Node node) {
- var cfgQueue = new Stack<NodeData>();
- var dataQueue = new Stack<NodeData>();
- var mem = new Stack<NodeData>();
- markAlive(cfgQueue, node, true);
- // Mark all CFG nodes.
- while (!cfgQueue.isEmpty()) {
- var data = cfgQueue.pop();
- node = data.node;
- assert node instanceof CFGNode;
- if (!(node instanceof ReturnNode) ) {
- for (var out : node._outputs) if (out!=null && out instanceof CFGNode && isNotXCtrl(out)) markAlive(cfgQueue, out, true);
- }
- for (var in : node._inputs) if(in!=null && !(in instanceof CFGNode) && isNotXCtrl(in)) markAlive(dataQueue, in, false);
- }
- // Mark all other nodes.
- while (!dataQueue.isEmpty()) {
- var data = dataQueue.pop();
- node = data.node;
- assert !(node instanceof CFGNode);
- if (node instanceof PhiNode phi) {
- var r = phi.in(0);
- for (int i=1; i<phi.nIns(); i++) {
- if (od(r.in(i)).isPresent()) markAlive(dataQueue, phi.in(i), false);
- }
- } else {
- for (var in : node._inputs) if (in != null && !(in instanceof CFGNode)) markAlive(dataQueue, in, false);
- }
- if (node instanceof MemOpNode s && !s._isLoad) mem.push(data);
- }
- // Handle store nodes and increase load with an anti-dep to the store.
- while (!mem.isEmpty()) {
- var data = mem.pop();
- node = data.node;
- for(var out:node.in(1)._outputs) {
- if (out instanceof MemOpNode ld && ld._isLoad) od(out).ifPresent(d->d.users++);
- }
- }
- }
-
- /**
- * Helper function to append the nodes of a block to an array of nodes
- * @param arr The array the nodes of a block should be appended to.
- * @param node A potentially separator node
- * @param block The block from which the nodes should be appended.
- * @return The combined new array.
- */
- private static Node[] appendNodes(Node[] arr, Node node, BasicBlock block) {
- var idx = arr.length;
- arr = Arrays.copyOf(arr, arr.length+block.reverseSchedule.size()+(node==null?0:1));
- if (node != null) arr[idx++] = node;
- for(int i=block.reverseSchedule.size()-1;i>=0;i--) {
- arr[idx++] = block.reverseSchedule.get(i);
- }
- return arr;
- }
-
- /**
- * Find the CFG output of a node
- * @param node The node
- * @return The CFG output of the node.
- */
- private static CFGNode findSingleCFGOut(Node node) {
- if (node instanceof StartNode) {
- for(var n:node._outputs)
- if (n instanceof FunNode fun && "main".equals(fun._name) )
- return fun;
- return null;
- }
- if( node instanceof FunNode fun ) return fun.uctrl();
- assert node._outputs.stream().filter(x -> x instanceof CFGNode).limit(2).count()<=1;
- for(var n:node._outputs) if(n instanceof CFGNode cfg) return cfg;
- return null;
- }
-
- /**
- * Build the final data structure after all the scheduling happened.
- * @param start The start node
- * @return The final schedule.
- */
- private Block build(Node start) {
- var blocks = new IdentityHashMap<Node, Block>();
- var queue = new Stack<NodeData>();
- queue.push(d(start));
-
- // Visit all CFG nodes and create blocks for them.
- // This can combine blocks.
- while(!queue.isEmpty()) {
- var data = queue.pop();
- var first = data.node;
- Node prev;
- var last = first;
- Node[] arr = new Node[0];
- if (first instanceof RegionNode) {
- var nodes = new ArrayList<Node>();
- for(var out: first._outputs) {
- if (out instanceof PhiNode) {
- if(od(out).isPresent()) nodes.add(out);
- }
- }
- arr = nodes.toArray(arr);
- }
- arr = appendNodes(arr, null, data.block);
- assert !(first instanceof IfNode);
- for(;;) {
- prev = last;
- last = findSingleCFGOut(last);
- if (last == null) break;
- if (last instanceof RegionNode) {
- if (blocks.get(last) == null && last != first)
- queue.push(d(last));
- break;
- }
- if (last instanceof ReturnNode) break;
- if (last instanceof IfNode if_) {
- for(var out:if_._outputs) if (out instanceof CFGNode && blocks.get(out) == null) queue.push(d(out));
- break;
- }
- data = d(last);
- arr = appendNodes(arr, data.node, data.block);
- }
- blocks.put(first, switch(last) {
- case null -> new Block(arr, null, -1, new Block[0]);
- case IfNode i -> new Block(arr, i, -1, new Block[2]);
- case RegionNode r -> new Block(arr, r, r._inputs.find(prev), new Block[1]);
- case ReturnNode r -> new Block(arr, r , -1, new Block[0]);
- default -> throw new AssertionError("Unexpected block exit node");
- });
- }
- // Update the next pointer of all blocks.
- for (var block: blocks.values()) {
- switch(block.exit) {
- case null: break;
- case IfNode i:
- for(var out:i._outputs) if (out instanceof CProjNode p) block.next[p._idx] = blocks.get(p);
- break;
- case RegionNode r:
- block.next[0] = blocks.get(r);
- break;
- default:
- assert block.exit instanceof ReturnNode;
- }
- }
- // And return the block for the start node.
- return blocks.get(start);
- }
-
- /**
- * Create a schedule for the program reachable from start.
- * @param start The start node
- * @return The final schedule.
- */
- public static Block schedule(StartNode start) {
- var scheduler = new Scheduler();
- scheduler.doMarkAlive(start);
- scheduler.doBuildCTF(start);
- scheduler.doSchedule();
- return scheduler.build(start);
- }
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
index 77e61b42..c1ffa6c1 100644
--- a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
+++ b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
@@ -82,10 +82,10 @@ public class Fuzzer {
private static void runCheck(String script, boolean valid) {
CodeGen code1;
try {
- code1 = FuzzerUtils.parse(script, 123, true);
+ code1 = FuzzerUtils.parse(script, 123);
} catch (RuntimeException e1) {
try {
- FuzzerUtils.parse(script, 456, false);
+ FuzzerUtils.parse(script, 456);
} catch (RuntimeException e2) {
if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) {
if (!valid || e1.getClass() == Parser.ParseException.class) return;
@@ -95,7 +95,7 @@ public class Fuzzer {
}
throw e1;
}
- CodeGen code2 = FuzzerUtils.parse(script, 456, false);
+ CodeGen code2 = FuzzerUtils.parse(script, 456);
checkGraphs(code1, code2, 0);
checkGraphs(code1, code2, 1);
checkGraphs(code1, code2, 10);
diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
index e08b73fc..9175e380 100644
--- a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
+++ b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
@@ -94,13 +94,13 @@ class FuzzerUtils {
/**
* Parse script with peepholes enabled or disabled
*/
- public static CodeGen parse(String script, long workListSeed, boolean reset) {
+ public static CodeGen parse(String script, long workListSeed) {
var err = System.err;
var out = System.out;
try {
System.setErr(NULL_PRINT_STREAM);
System.setOut(NULL_PRINT_STREAM);
- var code = new CodeGen(script, TypeInteger.BOT, workListSeed, reset);
+ var code = new CodeGen(script, workListSeed);
return code.driver(CodeGen.Phase.LocalSched);
} finally {
System.setErr(err);
diff --git a/src/test/java/com/seaofnodes/simple/test0/A.smp b/src/test/java/com/seaofnodes/simple/test0/A.smp
new file mode 100644
index 00000000..b94fa147
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test0/A.smp
@@ -0,0 +1,6 @@
+// -*- mode: java; -*-
+
+val _fcn = { -> arg ? { int x -> x+1; } : { int x -> x-1; }; };
+
+// Refer to child B is OK because last field of A has been initialized
+_fcn()(B.x);
diff --git a/src/test/java/com/seaofnodes/simple/test0/A/B.smp b/src/test/java/com/seaofnodes/simple/test0/A/B.smp
new file mode 100644
index 00000000..82844f74
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test0/A/B.smp
@@ -0,0 +1 @@
+val x=5;
diff --git a/src/test/java/com/seaofnodes/simple/test5/par.smp b/src/test/java/com/seaofnodes/simple/test5/par.smp
new file mode 100644
index 00000000..f19bd57b
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test5/par.smp
@@ -0,0 +1,20 @@
+// -*- mode: java; -*-
+
+// Parent module
+val par0 = 3; // Some final field
+
+// Nested class in parent.
+// Makes a field: "val child0 = ..."
+struct child0 {
+ val kid0 = 5;
+};
+
+// Nested class in parent, comes from disk.
+// These are defined in-order
+val child1 = child1;
+
+// Nested class in parent, comes from disk
+val child2 = child2;
+
+// Nested class in parent, comes from disk
+val child3 = child3;
diff --git a/src/test/java/com/seaofnodes/simple/test5/par/child1.smp b/src/test/java/com/seaofnodes/simple/test5/par/child1.smp
new file mode 100644
index 00000000..7bca0cf7
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test5/par/child1.smp
@@ -0,0 +1,12 @@
+// -*- mode: java; -*-
+
+// Child module
+
+// Same field name as child2
+val kid1 = 7;
+
+// Can read parent directly
+val kid2 = par0;
+
+// Error to reference a later sibling
+// val kid3 = par.child2.kid1;
diff --git a/src/test/java/com/seaofnodes/simple/test5/par/child2.smp b/src/test/java/com/seaofnodes/simple/test5/par/child2.smp
new file mode 100644
index 00000000..ab5e2ac1
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test5/par/child2.smp
@@ -0,0 +1,13 @@
+// -*- mode: java; -*-
+
+// Child module
+
+// Same field name as child2
+val kid1 = 11;
+
+// Can read parent directly.
+// Can read defined-in-parent fields.
+val kid2 = child0.kid0;
+
+// OK to reference a past sibling
+val kid3 = child1.kid1;
diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp
new file mode 100644
index 00000000..1725c57d
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys.smp
@@ -0,0 +1,3 @@
+// Reduced multi-compilation-unit reproducer for order-sensitive inlining.
+var aryu8 = aryu8;
+var aryi64 = aryi64;
diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp
new file mode 100644
index 00000000..e3dd6140
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/Ary.smp
@@ -0,0 +1,6 @@
+// Array utilities, discovered indirectly from aryu8.
+val copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
+ for( int i=0; i<len; i++ )
+ dst[i+doff] = src[i+soff];
+ return dst;
+};
diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp
new file mode 100644
index 00000000..53c93666
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryi64.smp
@@ -0,0 +1,29 @@
+struct aryi64 {
+ u32 !len = 0;
+ i64[] !buf;
+
+ new aryi64 = { i64[] b -> buf = b; };
+
+ val _grow = { int len2 ->
+ if( len+len2 < buf# ) return self;
+ int newlen = buf# * 2;
+ while( len+len2 >= newlen ) newlen *= 2;
+ i64[] !buf2 = new i64[newlen];
+ for( int i=0; i<buf#; i++ )
+ buf2[i] = buf[i];
+ buf = buf2;
+ self;
+ };
+
+ val add = { i64 c ->
+ _grow(1).buf[len++] = c;
+ self;
+ };
+
+ val str = { ->
+ var str = new aryu8(new u8[1]).add('[');
+ for( int i=0; i<len; i++ )
+ str.num(buf[i]).add(',').add(' ');
+ str.undo(2).add(']');
+ };
+};
diff --git a/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp
new file mode 100644
index 00000000..112a0321
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/seed_repro/sys/aryu8.smp
@@ -0,0 +1,50 @@
+struct aryu8 {
+ u32 !len = 0;
+ u8[] !buf;
+
+ new aryu8 = { u8[] b -> buf = b; };
+
+ val _grow = { int len2 ->
+ if( len+len2 < buf# ) return self;
+ int newlen = buf# * 2;
+ while( len+len2 >= newlen ) newlen *= 2;
+ u8[] !buf2 = new u8[newlen];
+ Ary.copy(buf2,0,buf,0,buf#);
+ buf = buf2;
+ self;
+ };
+
+ val clear = { -> len = 0; self; };
+
+ val add = { u8 c ->
+ _grow(1).buf[len++] = c;
+ self;
+ };
+
+ val addAry = { u8[~] ary ->
+ _grow(ary#);
+ Ary.copy(buf,len,ary,0,ary#);
+ len += ary#;
+ self;
+ };
+
+ val undo = { int x ->
+ len = len >= x ? len-x : 0;
+ self;
+ };
+
+ val num = { i64 x ->
+ if( !x ) { add('0'); return self; }
+ if( x < 0 ) { add('-'); x = -x; }
+ i64 ten = 1, log = 0;
+ while( ten <= x ) { ten *= 10; log++; }
+ for( int i=0; i<log; i++ ) {
+ ten /= 10;
+ i64 q = x/ten;
+ u8 digit = q+'0';
+ add(digit);
+ x -= q*ten;
+ }
+ self;
+ };
+};