summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-08-10 11:11:18 -0700
committerCliff Click <cliffc@acm.org>2026-10-08 16:52:27 -0700
commitd32f97238c903765dec4e9a9f3cbfa5fd12cded6 (patch)
treea5ac731801ef7066bab6359c4e85c2bdd20082a6 /src
parentb5ebb2032aa04cf4506d533127d373d4cf5a25c6 (diff)
downloadSimple-d32f97238c903765dec4e9a9f3cbfa5fd12cded6.tar.gz
Simple-d32f97238c903765dec4e9a9f3cbfa5fd12cded6.zip
Chapter 25: Modules, Separate Compilation, and SSA Constructionlinear
Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki <jun784@gmail.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'src')
-rw-r--r--src/main/c/simple_crt.c61
-rw-r--r--src/main/java/com/seaofnodes/simple/IterPeeps.java199
-rw-r--r--src/main/java/com/seaofnodes/simple/Parser.java2117
-rw-r--r--src/main/java/com/seaofnodes/simple/Var.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java20
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/CodeGen.java901
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/CompUnit.java135
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java87
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfFile.java382
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfReader.java234
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java431
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Encoding.java363
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java177
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java30
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/IFG.java91
-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.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Module.java98
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Opto.java359
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/ParseAll.java396
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java150
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/RegMask.java1
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/Serialize.java614
-rw-r--r--src/main/java/com/seaofnodes/simple/codegen/StaticData.java195
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddFNode.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AddNode.java44
-rw-r--r--src/main/java/com/seaofnodes/simple/node/AndNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArithFNode.java30
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ArithNode.java186
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BoolNode.java119
-rw-r--r--src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java101
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CFGNode.java142
-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.java375
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CallNode.java80
-rw-r--r--src/main/java/com/seaofnodes/simple/node/CastNode.java71
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java47
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConstantNode.java54
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ConvertNode.java101
-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.java24
-rw-r--r--src/main/java/com/seaofnodes/simple/node/EscapeNode.java116
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ExternNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java19
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FRefNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FldOffNode.java48
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunNode.java354
-rw-r--r--src/main/java/com/seaofnodes/simple/node/FunPtrNode.java68
-rw-r--r--src/main/java/com/seaofnodes/simple/node/GuardMach.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/GuardNode.java68
-rw-r--r--src/main/java/com/seaofnodes/simple/node/IfNode.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoadNode.java182
-rw-r--r--src/main/java/com/seaofnodes/simple/node/LoopNode.java38
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java5
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MachNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemMergeNode.java191
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemOpNode.java109
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MemPhiNode.java62
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusFNode.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/MinusNode.java70
-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.java16
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NeverNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NewNode.java98
-rw-r--r--src/main/java/com/seaofnodes/simple/node/Node.java195
-rw-r--r--src/main/java/com/seaofnodes/simple/node/NotNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/OrNode.java6
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ParmNode.java72
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PhiNode.java190
-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.java (renamed from src/main/java/com/seaofnodes/simple/node/CastMach.java)12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java36
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RegionNode.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ReturnNode.java118
-rw-r--r--src/main/java/com/seaofnodes/simple/node/RoundF32Node.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SarNode.java7
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ScopeNode.java471
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShlNode.java9
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ShrNode.java6
-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.java58
-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.java201
-rw-r--r--src/main/java/com/seaofnodes/simple/node/StructNode.java47
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubFNode.java27
-rw-r--r--src/main/java/com/seaofnodes/simple/node/SubNode.java14
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ToFloatNode.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java1
-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.java7
-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.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java12
-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.java2
-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.java18
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java105
-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.java41
-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.java8
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java31
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java12
-rw-r--r--src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java114
-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/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.java8
-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.java48
-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.java148
-rw-r--r--src/main/java/com/seaofnodes/simple/print/ASMPrinter.java40
-rw-r--r--src/main/java/com/seaofnodes/simple/print/IRPrinter.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java43
-rw-r--r--src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java2
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Field.java90
-rw-r--r--src/main/java/com/seaofnodes/simple/type/Type.java262
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAry.java21
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeConAryB.java4
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFloat.java40
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java228
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeInteger.java68
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMem.java203
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java98
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeNil.java3
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypePtr.java17
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeRPC.java115
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeScalar.java57
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeStruct.java392
-rw-r--r--src/main/java/com/seaofnodes/simple/type/TypeTuple.java83
-rw-r--r--src/main/java/com/seaofnodes/simple/type/XInt.java268
-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.smp206
-rw-r--r--src/main/smp/sys/adt.smp3
-rw-r--r--src/main/smp/sys/adt/bitset.smp57
-rw-r--r--src/main/smp/sys/ary.smp24
-rw-r--r--src/main/smp/sys/aryi64.smp82
-rw-r--r--src/main/smp/sys/aryu8.smp87
-rw-r--r--src/main/smp/sys/char.smp8
-rw-r--r--src/main/smp/sys/io.smp64
-rw-r--r--src/main/smp/sys/libc.smp44
-rw-r--r--src/main/smp/sys/scan.smp39
-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.java240
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter11Test.java61
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter12Test.java126
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter13Test.java92
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java19
-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.java78
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter17aTest.java45
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter17bTest.java143
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter18Test.java238
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter19Test.java206
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter20Test.java302
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java55
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter21Test.java240
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter22Test.java251
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java37
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter23Test.java365
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java170
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter24Test.java560
-rw-r--r--src/test/java/com/seaofnodes/simple/Chapter25Test.java459
-rw-r--r--src/test/java/com/seaofnodes/simple/Compile.java65
-rw-r--r--src/test/java/com/seaofnodes/simple/Eval2.java137
-rw-r--r--src/test/java/com/seaofnodes/simple/FuzzerWrap.java46
-rw-r--r--src/test/java/com/seaofnodes/simple/LiteralTest.java65
-rw-r--r--src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java155
-rw-r--r--src/test/java/com/seaofnodes/simple/MergeSortTest.java80
-rw-r--r--src/test/java/com/seaofnodes/simple/MutabilityTest.java30
-rw-r--r--src/test/java/com/seaofnodes/simple/NativeExecutionTest.java69
-rw-r--r--src/test/java/com/seaofnodes/simple/SchedulerTest.java74
-rw-r--r--src/test/java/com/seaofnodes/simple/Simple.java285
-rw-r--r--src/test/java/com/seaofnodes/simple/SpillStats.java83
-rw-r--r--src/test/java/com/seaofnodes/simple/StaticDataTest.java112
-rw-r--r--src/test/java/com/seaofnodes/simple/TestArm64.java48
-rw-r--r--src/test/java/com/seaofnodes/simple/TestC.java268
-rw-r--r--src/test/java/com/seaofnodes/simple/TestRisc5.java48
-rw-r--r--src/test/java/com/seaofnodes/simple/TypeTest.java84
-rw-r--r--src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java56
-rw-r--r--src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java53
-rw-r--r--src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java123
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java380
-rw-r--r--src/test/java/com/seaofnodes/simple/evaluator/Scheduler.java605
-rw-r--r--src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java26
-rw-r--r--src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java4
-rw-r--r--src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java17
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/extern_data.c22
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/extern_data_import.c14
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/floatToInteger.c25
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/funptr.c15
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/jig.smp16
-rw-r--r--src/test/java/com/seaofnodes/simple/progs/person21.c34
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp46
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp8
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp43
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp5
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp6
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp46
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp11
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp35
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testArray1.smp (renamed from src/test/java/com/seaofnodes/simple/progs/array1.smp)0
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp13
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp (renamed from src/test/java/com/seaofnodes/simple/progs/stringHash21.smp)12
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount-2.smp (renamed from src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp)0
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount.smp (renamed from src/test/java/com/seaofnodes/simple/progs/arg_count.smp)0
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp9
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testNewtonExport.smp (renamed from src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp)2
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp (renamed from src/test/java/com/seaofnodes/simple/progs/person21.smp)2
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp30
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp (renamed from src/test/java/com/seaofnodes/simple/progs/stringHash.smp)5
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp4
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp12
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp8
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp4
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp5
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp5
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp34
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp11
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp12
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp17
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp24
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp20
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp16
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp17
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp13
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp11
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp217
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp11
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp5
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp3
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp3
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp3
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp3
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp11
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp10
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp15
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp3
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp35
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/README.ja.md50
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/README.md78
-rw-r--r--src/test/java/com/seaofnodes/simple/spill/cohorts.tsv200
-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/forward_ctor/m.smp2
-rw-r--r--src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp1
-rw-r--r--src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp4
-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
313 files changed, 15337 insertions, 8603 deletions
diff --git a/src/main/c/simple_crt.c b/src/main/c/simple_crt.c
new file mode 100644
index 00000000..8656a3cc
--- /dev/null
+++ b/src/main/c/simple_crt.c
@@ -0,0 +1,61 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+// errno may be thread-local or a macro calling a platform accessor.
+int32_t simple_errno(void) { return errno; }
+
+// Native process arguments converted to ordinary Simple array layouts.
+// The outer array is mutable; every contained byte array is published as
+// immutable and non-null to Simple code.
+typedef struct SimpleBytes {
+ uint32_t len;
+ unsigned char bytes[];
+} SimpleBytes;
+
+typedef struct SimpleArgs {
+ uint32_t len;
+ uint32_t pad;
+ SimpleBytes *args[];
+} SimpleArgs;
+
+__attribute__((CALL_CONV))
+extern int64_t simple_main(SimpleArgs *args);
+
+static SimpleArgs *simple_args(int argc, char **argv) {
+ if (argc < 0 || (uint64_t)argc > UINT32_MAX)
+ return NULL;
+
+ SimpleArgs *args = calloc(1, sizeof(*args) + (size_t)argc * sizeof(args->args[0]));
+ if (args == NULL)
+ return NULL;
+ args->len = (uint32_t)argc;
+
+ for (int i = 0; i < argc; i++) {
+ size_t len = strlen(argv[i]);
+ if (len > UINT32_MAX) {
+ free(args);
+ return NULL;
+ }
+ SimpleBytes *str = malloc(sizeof(*str) + len);
+ if (str == NULL) {
+ for (int j = 0; j < i; j++) free(args->args[j]);
+ free(args);
+ return NULL;
+ }
+ str->len = (uint32_t)len;
+ memcpy(str->bytes, argv[i], len);
+ args->args[i] = str;
+ }
+ return args;
+}
+
+int main(int argc, char **argv) {
+ SimpleArgs *args = simple_args(argc, argv);
+ if (args == NULL)
+ return 1;
+ // The language-level hidden receiver is not part of the emitted ABI for
+ // free functions; source argument zero occupies native argument zero.
+ return (int)simple_main(args);
+}
diff --git a/src/main/java/com/seaofnodes/simple/IterPeeps.java b/src/main/java/com/seaofnodes/simple/IterPeeps.java
index a3b66e09..e65ef6d9 100644
--- a/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -1,12 +1,14 @@
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;
import com.seaofnodes.simple.node.*;
import java.util.Arrays;
import java.util.BitSet;
+import java.util.IdentityHashMap;
import java.util.Random;
/**
@@ -38,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
@@ -48,11 +50,11 @@ import java.util.Random;
public class IterPeeps {
public final WorkList<Node> _work;
-
- private final WorkList<CallEndNode> _workInline;
+ final WorkList<CallEndNode> _workInline;
+ private int _cnt;
public IterPeeps( long seed ) {
- _work = new WorkList<>(seed);
+ _work = new WorkList<>(seed);
_workInline = new WorkList<>(seed);
}
@@ -62,36 +64,53 @@ public class IterPeeps {
public void addAll( Ary<Node> ary ) { _work.addAll(ary); }
/**
- * Iterate peepholes to a fixed point
+ * Iterate peepholes and inlining to a fixed point
*/
public void iterate( CodeGen code ) {
+ boolean didInline = false;
+ Ary<Node> defer = new Ary<>(Node.class);
while( true ) {
+ // Clean up everything that does not grow the code
iteratePeeps(code);
+
+ // Pick an inline candidate, no real heuristic, first come, first served
+ boolean inlined = false;
CallEndNode cend;
- while( (cend=_workInline.pop())!=null )
- if( cend.inlineSmall() ) break;
- if( cend==null ) return;
+ while( (cend=_workInline.pop()) != null) {
+ if( cend.isDead() ) continue;
+ byte inline = cend.maybeInline();
+ if( inline == -1 ) defer.add(cend);
+ if( inline > 0 ) {
+ inlined = true;
+ break;
+ }
+ }
+ // Inlined, run peeps until clean again
+ if( inlined ) { didInline = true; continue; }
+ // No more candidates, check the defer list
+ if( !didInline ) break; // No more progress, so all the "maybe inline after cleanup" do not progress
+ // Some inlining happened, retry all the "try again after cleanup" calls
+ didInline = false;
+ code.addAll(defer);
+ defer.clear();
}
}
- // Only try growing inlines once the shrinking peepholes reach a fixed point.
+ // Run all the code-reduction and type-lifting peeps as possible
private void iteratePeeps( CodeGen code ) {
- assert progressOnList(code, _work, true);
- int cnt=0;
+ assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code));
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
- if( n instanceof CallEndNode cend ) _workInline.push(cend);
- cnt++; // Useful for debugging, searching which peephole broke things
- var obs = CodeGen.CODE._obs;
+ _cnt++; // Useful for debugging, searching which peephole broke things
+ var obs = code._obs; // Collect peephole neighbors for graph display
if( obs != null ) obs.before(n);
Node x = n.peepholeOpt();
+ if( n instanceof CallEndNode cend )
+ _workInline.push(cend);
if( x != null ) {
- if( x.isDead() ) {
- if( obs != null ) obs.after(n, x, true);
- continue;
- }
+ assert !x.isDead(); // Peepholes return alive answers
// peepholeOpt can return brand-new nodes, needing an initial type set
if( x._type==null ) x.setType(x.compute());
// Changes require neighbors onto the worklist
@@ -101,22 +120,31 @@ 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 ) {
- for( Node z : n. _inputs ) _work.push(z);
+ //for( Node z : n. _inputs ) _work.push(z);
for( Node z : x._outputs ) _work.push(z);
n.subsume(x);
}
}
// If there are distant neighbors, move to worklist
n.moveDepsToWorklist();
- assert progressOnList(code, _work, true); // Very expensive assert
+ // Very expensive assert.
+ assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code));
}
- if( n.isUnused() && !(n instanceof StopNode) )
+ boolean unused = n.isUnused();
+ if( unused ) {
+ assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies
n.kill(); // Just plain dead
- if( obs != null ) obs.after(n, x, true);
+ }
+ if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true);
}
+ assert !CodeGen.expensiveAssert(0) || schedulableUses(code);
}
@@ -133,27 +161,112 @@ public class IterPeeps {
// {@link #Node.addDep} which is a side effect in an assert. The {@link
// #midAssert} is used to stop this side effect.
// Pessimistic solver assert
- public static boolean progressOnList(CodeGen code, WorkList<Node> list, boolean dir ) {
+ public static boolean progressOnList(CodeGen code, WorkList<Node> list ) {
code._midAssert = true;
Node changed = code._stop.walk( n -> {
- Node m = n;
+ // Ignore most in-progress things
+ if( n.iskeep() ) return null;
+ // Types must be forwards, even if on the worklist.
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;
+ assert nval.isa(n._type) : "Non-monotonic peep: "+n+"#"+n._nid+" old="+n._type+" new="+nval+" inputs="+inputTypes(n)+" peep="+n;
+ if( list.on(n) )
+ return null; // On worklist is ok!
+ if( n instanceof CallEndNode cend ) {
+ if( code._iter._workInline.on(cend) )
+ return null; // On inline worklist is ok!
+ assert cend.maybeInline() <= 0 : "Inline fired and not on worklist, CallEndNode#"+cend._nid;
}
- System.err.println("BREAK HERE FOR BUG");
+ // Off list: must be live, in GVN table, and no peeps.
+ assert n.nOuts() > 0 : "Unused live node: "+n;
+ assert code._gvn.get(n)==n : "Not in GVN table: "+n;
+ Node 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();
+ }
+
+ // GCM assumes every movable node can be placed no earlier than its inputs
+ // and no later than the LCA of its uses. Catch graphs where a use escapes
+ // above an input-defined branch before the later scheduling pass walks off
+ // the top of the idom tree.
+ private static final IdentityHashMap<Node,CFGNode> earlyCache = new IdentityHashMap<>();
+ private static final BitSet VISIT = new BitSet();
+ public static boolean schedulableUses(CodeGen code) {
+ earlyCache.clear();
+ Node bad = code._stop.walk( n -> {
+ if( n.iskeep() || n.isDead() || n.isConst() ||
+ n instanceof CFGNode || n instanceof PhiNode || n instanceof ProjNode )
+ return null;
+ assert VISIT.isEmpty();
+ CFGNode early = CFGNode.earlyCFG(n,earlyCache,VISIT);
+ VISIT.clear();
+ if( early == null ) return null;
+ CFGNode lca = null;
+ for( Node use : n._outputs ) {
+ CFGNode ublk = useBlock(n,use);
+ if( ublk != null )
+ lca = ublk.domLCA(lca,null);
+ }
+ if( lca == null ) return null;
+ if( !early.sameFun(lca) ) return null;
+ assert early.dominates(lca,null) : badSchedule(n,early,lca);
+ return null;
+ });
+ return bad == null;
+ }
+
+ private static CFGNode useBlock(Node n, Node use) {
+ if( use == null )
+ return null;
+ if( use instanceof PhiNode phi ) {
+ CFGNode found = null;
+ for( int i=1; i<phi.nIns(); i++ )
+ if( phi.in(i)==n ) {
+ if( i >= phi.region().nIns() )
+ return null;
+ found = phi.region().cfg(i).domLCA(found,null);
+ }
+ return found;
+ }
+ return CFGNode.safeCFG(use);
+ }
+
+ private static String badSchedule(Node n, CFGNode early, CFGNode lca) {
+ StringBuilder sb = new StringBuilder("Unschedulable data node ")
+ .append(n.getClass().getSimpleName()).append('#').append(n._nid)
+ .append(" early=").append(cfg(early))
+ .append(" use-lca=").append(cfg(lca))
+ .append(" node=").append(n).append("\ninputs:");
+ for( int i=0; i<n.nIns(); i++ ) {
+ Node in = n.in(i);
+ sb.append("\n in").append(i).append(": ").append(node(in))
+ .append(" cfg=").append(cfg(CFGNode.safeCFG(in)));
+ }
+ sb.append("\noutputs:");
+ for( Node use : n._outputs )
+ if( use != null )
+ sb.append("\n use ").append(node(use))
+ .append(" block=").append(cfg(useBlock(n,use)));
+ return sb.toString();
+ }
+
+ private static String node(Node n) {
+ return n==null ? "null" : n.getClass().getSimpleName()+"#"+n._nid+" "+n;
+ }
+
+ private static String cfg(CFGNode cfg) {
+ return cfg==null ? "null" : cfg.getClass().getSimpleName()+"#"+cfg._nid+" "+cfg;
+ }
+
/**
* Classic WorkList, with a fast add/remove, dup removal, random pull.
* The Node's nid is used to check membership in the worklist.
@@ -201,12 +314,16 @@ 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; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -220,11 +337,13 @@ public class IterPeeps {
return x;
}
- public void clear() {
- _len = 0;
- _on.clear();
- _R.setSeed(_seed);
- _totalWork = 0;
- }
+ public boolean isEmpty() { return _len==0; }
+ //Node[] asAry() { return Arrays.copyOf(_es,_len); }
+ //public void clear() {
+ // _len = 0;
+ // _on.clear();
+ // _R.setSeed(_seed);
+ // _totalWork = 0;
+ //}
}
}
diff --git a/src/main/java/com/seaofnodes/simple/Parser.java b/src/main/java/com/seaofnodes/simple/Parser.java
index 9cf565ac..cd80b3ff 100644
--- a/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/src/main/java/com/seaofnodes/simple/Parser.java
@@ -1,11 +1,13 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.node.ScopeNode.Kind;
+
+import static com.seaofnodes.simple.util.Utils.TODO;
+
import java.util.*;
/**
@@ -17,16 +19,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
@@ -78,34 +96,20 @@ 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
+ Ary<FRefNode> _frefs; // Forward refs created during this parse
+ TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block.
+ // Constructor exit types survive scope teardown until all fields are known.
+ // A null entry denotes a constructor with no returning path.
+ private ArrayList<IdentityHashMap<Var,Type>> _ctorExits;
// 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);
@@ -123,172 +127,408 @@ 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); }
-
- public void parse() {
- // Graph callbacks during scope setup need a source position.
- _lexer = new Lexer(sys.SYS);
-
+ // 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(); }
+
+ // 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(null,bulk);
+ merge.alias(alias,precise);
+ return peep(merge);
+ }
+
+
+ public Ary<FRefNode> parse( CompUnit ref ) {
+ assert _scope == null && _breakScope == null && _continueScope == null && _returnScope == null;
+ assert _frefs == null;
+ _lexer = new Lexer(ref._src);
+ _frefs = new Ary<>(FRefNode.class);
+ // 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(con(TypeMem.BOT));
+ // Track active scopes for Graph display
+ _xScopes.push(_scope);
+ ctrl(_code.XCTRL);
+ mem(con(TypeMem.BOT));
- // Parse the sys import
- 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";
+ // All still-unresolved forward refs
+ // should immediately resolve to a file-based name which is a Simple
+ // class type.
+ Ary<FRefNode> frefs = new Ary<>(FRefNode.class);
+ for( FRefNode fref : _frefs ) {
+ if( fref.nIns() != 1 && fref._con == FRefNode.FREF_TYPE ) continue;
+ 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;
+ _frefs = null;
+
+ return frefs;
}
+
+
/**
* Parses a function body, assuming the header is parsed.
*/
- private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String... ids) {
+ private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, String[] ids) {
return parseFunctionBody(sig, loc, null, ids);
}
- private ReturnNode parseFunctionBody( TypeFunPtr sig, Lexer loc, java.util.BitSet finals, 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, java.util.BitSet finals, 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,finals,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) {
+ return _parseFunctionBody(funName, sig, loc, null, ids);
+ }
+
+ private ReturnNode _parseFunctionBody( String funName, TypeFunPtr sig, Lexer loc, java.util.BitSet finals, 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 BulkMemPhiNode(ScopeNode.MEM0,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
- // Unknown callers supply whole memory; field aliases are discovered lazily.
- _scope.mem(new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,con(TypeMem.BOT)).peephole());
+ _scope.ctrl(fun); // Scope control from function
+ // Private mem alias tracking per function
+ 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, finals==null ? t instanceof TypeNil : finals.get(i), 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") || (finals!=null && finals.get(i)), 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(), 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() ) {
+ 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 = v._extern!=0
+ ? Field.make(v._name,v.type(),v._extern,false,true)
+ : old == null
+ ? Field.make(v._name,v.type(),_code.alias(_ref._cname),v._final)
+ : old.makeFrom(v.type());
+ 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);
+ }
+
+ if( !upgrade ) {
+ IdentityHashMap<Var,Type> exit = null;
+ if( _returnScope!=null && !_returnScope.ctrl()._type.isHigh() ) {
+ exit = new IdentityHashMap<>();
+ int first = _scope._kinds.at(_scope.depth()-2)._lexSize+2;
+ for( int i=first; i<lex; i++ )
+ exit.put(_scope.var(i),_returnScope.in(i)._type);
+ }
+ _ctorExits.add(exit);
+ } else if( !isClz ) {
+ checkInitFields(tself,base);
+ }
+
+ // 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 || v._extern!=0 ) continue; // C storage has no Simple initializer
+ // NewNode's TOP field is the zero-code poison for a required
+ // field. Leave it alone unless the declaration body supplied a
+ // value; otherwise a user constructor must initialize it.
+ Node val = _returnScope.in(vidx);
+ if( upgrade && v._uninit &&
+ !initialized(val._type) ) continue;
+ // 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);
+ }
+
+ // Validate each user constructor, or an implicit empty one, before the
+ // declaration scope goes away. Later fields use their declaration defaults.
+ private static boolean initialized(Type t) {
+ return t!=Type.BOTTOM && !t.isHigh() && (!(t instanceof TypeNil tn) || tn.notNull());
+ }
+
+ private void checkInitFields(TypeStruct ts, int base) {
+ if( _returnScope==null || _returnScope.ctrl()._type.isHigh() ) return;
+ boolean implicit = _ctorExits.isEmpty();
+ if( implicit ) _ctorExits.add(new IdentityHashMap<>());
+ for( IdentityHashMap<Var,Type> exit : _ctorExits ) {
+ if( exit==null ) continue;
+ for( Field fld : ts._fields ) {
+ int idx = fieldVarIdx(base,fld._fname);
+ if( idx == -1 ) continue;
+ Var v = _scope.var(idx);
+ if( !v._uninit || v._fref ) continue;
+ Type t = exit.getOrDefault(v,_returnScope.in(idx)._type);
+ if( !initialized(t) )
+ throw error("'"+ts._name+"' is not fully initialized, field '"+fld._fname+
+ (implicit ? "' needs to be set in a constructor" :
+ "' is only partially set in the constructor"),v._loc);
+ }
+ }
+ }
+
+ 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() {
+ Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
+ 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.makeFrom(fld._t.makeStorage());
+ }
+ return TypeStruct.make(self._name,self._open,fs);
+ }
+
+
/**
* Parses a block
*
@@ -301,9 +541,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();
@@ -326,7 +568,8 @@ public class Parser {
else if (matchx("break") ) return parseBreak();
else if (matchx("continue")) return parseContinue();
else if (matchx("struct") ) return parseStruct();
- else if (matchx(";") ) return null; // Empty statement
+ else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration();
+ 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 +579,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 +654,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).
@@ -405,8 +673,8 @@ public class Parser {
// IfNode takes current control and predicate
Node ifNode = new IfNode(ctrl(), pred.keep()).peephole();
// Setup projection nodes
- Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole();
// for( ;;next ) body
int nextPos = -1, nextEnd = -1;
@@ -459,6 +727,8 @@ public class Parser {
// it sets the second input to the corresponding input from the back
// edge. If the phi is redundant, it is replaced by its sole input.
var exit = _breakScope;
+ head.balanceLoopFRefs(_scope);
+ exit.balanceLoopFRefs(_scope);
head.endLoop(_scope, exit);
head.unkeep().kill();
@@ -476,12 +746,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 +769,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
@@ -507,8 +777,8 @@ public class Parser {
// conditions here, not the union.
_breakScope.removeGuards(_breakScope.ctrl());
_breakScope = require(jumpTo(_breakScope ),";");
- _breakScope.addGuards(_breakScope.ctrl(), null, false);
- return ZERO;
+ _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false);
+ return _code.ZERO;
}
// Look for an unbalanced `)`, skipping balanced
@@ -518,7 +788,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 );
@@ -552,8 +822,8 @@ public class Parser {
pred.keep();
Node ifNode = new IfNode(ctrl(), pred).peephole();
// Setup projection nodes
- Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
+ CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ CFGNode ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
// In if true branch, the ifT proj node becomes the ctrl
// But first clone the scope and set it as current
ScopeNode fScope = _scope.dup(); // Duplicate current scope
@@ -569,7 +839,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);
@@ -592,12 +862,14 @@ public class Parser {
Node rhs = switch( fside ) {
case "else" -> (doRHS=match(fside)) ? parseStatement() : con(lhs._type.makeZero());
case ":" -> (doRHS=match(fside)) ? parseAsgn() : con(lhs._type.makeZero());
- case "&&" -> rhs = pred;
- case "||" -> rhs = parseLogical();
- default -> throw Utils.TODO();
+ case "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored
+ case "||" -> parseLogical(); // pred || expr
+ default -> throw TODO();
};
rhs.keep();
_scope.removeGuards(ifF);
+ // The RHS of `||` is parsed on the false side, so retain its updated
+ // control, memory and locals just as an explicit else arm does.
if( doRHS || fside.equals("||") )
fScope = _scope;
pred.unkeep();
@@ -617,12 +889,13 @@ 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;
+ ret.keep();
r.peephole();
- return ret;
+ return ret.unkeep();
}
/**
@@ -636,20 +909,69 @@ public class Parser {
*/
private Node parseReturn() {
var expr = require(parseAsgn(), ";");
- // Force the lazy whole-memory Phi before closing this exit.
- _fun.addReturn(ctrl(), _scope.mem(), expr);
- ctrl(XCTRL); // Kill control
- return expr;
+ 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
+ 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);
+ }
+ } else if( isExplicitConstructorName(k._name) ) {
+ // Field bindings live in the enclosing declaration, not among the
+ // constructor's arguments/locals. Force lazy Phis on every exit.
+ int first = _scope._kinds.at(lexN-1)._lexSize+2;
+ for( int i=first; i<k._lexSize; i++ )
+ _scope.update(_scope.var(i),null);
+ }
+ // 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", inferredReturnType(expr._type), 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();
+ }
/** Parse: [name '='] expr
*/
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('=','=') )
@@ -658,15 +980,12 @@ 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);
- }
+ if( def._extern!=0 ) // Parse the C binding as a memory lvalue
+ { pos(old); return parseExpression(); }
// TOP fields are for late-initialized fields; these have never
// been written to, and this must be the final write. Other writes
@@ -674,49 +993,91 @@ public class Parser {
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.canInit(def) )
+ !(_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(), 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();
}
- // Apply declared access permissions; widen floats and narrow integers.
+ // Widen ints to floats; narrow wide int types.
// Early error if types do not match variable.
- private Node liftExpr( Node expr, Type t, boolean isLoad ) {
- if( expr._type instanceof TypeMemPtr tmp && TYPES.get(tmp._obj._name).isFRef() )
- throw error("Must define forward ref "+tmp._obj._name);
- // Read-only access is deep; a fixed binding alone does not remove access.
- if( t instanceof TypeMemPtr tmp && tmp.isFinal() ) {
- expr = peep(new ReadOnlyNode(expr));
- }
+ private Node convertExpr( Node expr, Type t ) {
if( !expr._type.accessISA(t) )
- throw error("Type " + expr._type.str() + " is not of declared type " + t.str());
+ throw error("Type "+expr._type.str()+" is not of declared type "+t.str());
+ if( t instanceof TypeMemPtr tmp && tmp.isFinal() )
+ expr = peep(new ReadOnlyNode(expr));
+ // 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.
- if( expr._type instanceof TypeFloat && t instanceof TypeInteger )
- expr = peep(new ToIntegerNode(expr));
- 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) || !et.accessISA(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,"[]")));
+ // Widen ints to floats, narrow wide types
+ return peep(new ConvertNode(t,expr));
+ }
+
+ // Inferred `var` declarations describe writable slots, not the exact
+ // value used to initialize them. Explicitly typed locals retain the
+ // user's nullability and numeric-width guarantees; `val` retains the
+ // initializer's precise type.
+ private static Type mutableInferredType(Type t) {
+ return switch( t ) {
+ case TypeInteger ti -> TypeInteger.BOT;
+ case TypeFloat tf -> TypeFloat.F64;
+ case TypeMemPtr tmp -> tmp.makeVar();
+ case TypeFunPtr tfp -> tfp;
+ case TypeConAry<?> a -> a;
+ case TypePtr ptr -> TypePtr.PTR;
+ case TypeScalar ts -> TypeScalar.BOT;
+ default -> t;
+ };
+ }
+
+ // Seed an inferred return merge with the declared numeric family instead
+ // of a transient constant/range. Pointer nullability remains part of the
+ // inferred function result and is combined by the later Phi.
+ private static Type inferredReturnType(Type t) {
+ return switch( t ) {
+ case TypeInteger ti -> TypeInteger.BOT;
+ case TypeFloat tf -> TypeFloat.F64;
+ default -> t;
+ };
}
private Node widenInt( Node expr, Type t ) {
@@ -745,23 +1106,6 @@ public class Parser {
return require(n,";");
}
- // Widen a reassignable binding without discarding the initializer's
- // permissions. Lattice glb includes readonly memory and is too broad here.
- private Type inferredType(Type t) {
- if( t instanceof TypeMemPtr tmp ) {
- if( tmp._obj.isAry() ) {
- Type elem = tmp._obj._fields[1]._t;
- if( elem instanceof TypeConAry )
- elem = ((TypeMemPtr)TYPES.get(tmp._obj._name))._obj._fields[1]._t;
- return typeAry(elem,tmp._obj._fields[1]._final).withAccess(tmp._ro).makeNullable();
- }
- Type base = TYPES.get(tmp._obj._name);
- TypeMemPtr ptr = (TypeMemPtr)base;
- return ptr.withAccess(tmp._ro).makeNullable();
- }
- return t.glb(false);
- }
-
// The binding modifier sits immediately before the name. It never
// changes permissions on the referenced object.
private boolean bindingFinal(Type t) {
@@ -780,27 +1124,45 @@ public class Parser {
Lexer loc = loc();
String name = requireId();
// Optional initializing expression follows
- boolean fld_final = false; // Fixed field independent of pointee access
- boolean initialized = match("=");
+ boolean fld_final = false; // Field is final, but not deeply final
+ boolean uninit = false; // Required non-null constructor field
Node expr;
- if( initialized ) {
- expr = isExternDecl()
- ? externDecl(name,t)
- : parseAsgn();
+ if( match("=") ) {
+ if( isExternDecl() ) {
+ if( !(t instanceof TypeFunPtr) ) {
+ Node ptr = externData(name,t);
+ // The lexical binding is a fixed address; its contents are mutable.
+ if( !_scope.define(name,t,true,ptr,loc) )
+ throw error("Redefining name '"+name+"'",loc);
+ _scope.lookup(name)._extern = _code.externAlias(name);
+ return _code.ZERO;
+ }
+ 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();
+ }
// var/val, then type comes from expression
if( inferType ) {
if( expr._type==Type.NIL )
throw error("a not-null/non-zero expression");
t = expr._type;
- if( !xfinal ) t = inferredType(t); // Widen if not final
+ if( !xfinal ) t = mutableInferredType(t);
}
+
// 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
+ if( t instanceof TypeFunPtr decl && expr._type instanceof TypeFunPtr tfp && tfp.isConstant() ) {
+ t = decl.makeFrom(tfp.fidx());
+ 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.
@@ -808,32 +1170,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 -> xfinal ? con(Type.TOP) : ZERO;
- case TypeFloat tf -> con(xfinal ? Type.TOP : TypeFloat.FZERO);
+ // 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 -> xfinal ? con(Type.BOTTOM) : _code.ZERO;
+ case TypeFloat tf -> con(xfinal ? Type.BOTTOM : 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() && xfinal )
- 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( (xfinal && !(t instanceof TypeNil)) || (t instanceof TypeNil tn && !tn.nullable()) ) {
+ uninit = true;
+ if( xfinal ) fld_final = true;
+ }
}
// Lift expression, based on type
- Node lift = liftExpr(expr, t, true);
+ // 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);
- // 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));
// Define a new name
if( !_scope.define(name,t,xfinal || fld_final,lift, loc) )
throw error("Redefining name '" + name + "'", loc);
- _scope.lookup(name)._init = initialized;
+ _scope.lookup(name)._uninit = uninit;
return lift;
}
@@ -845,59 +1208,211 @@ 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 && v._init && _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);
+ TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
+ ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true));
+ TYPES.put(clzName,ts);
+
+ // 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,";");
+ }
+
+ // Build the declaration-side allocation helper. Allocation sites only
+ // call this function; all layout, initialization and publication choices
+ // are made here, where the complete struct and constructor are known.
+ private TypeFunPtr makeAllocator(TypeStruct ts, ReturnNode initRet, Field ctorFld) {
+ TypeFunPtr ctor = ctorFld==null ? null : (TypeFunPtr)ctorFld._t;
+ int nargs = ctor==null ? 0 : ctor.nargs()-2; // Hide self and selfMem
+ Type[] atypes = new Type[nargs];
+ for( int i=0; i<nargs; i++ ) atypes[i] = ctor.arg(i+2);
+ TypeMemPtr ptr = TypeMemPtr.make(ts);
+ TypeFunPtr sig = TypeFunPtr.make1((byte)2,true,atypes,ptr,_code.fidx(_ref._cname));
+ String name = (ts._name+".<new>").intern();
+
+ int oldUID = _code.UID();
+ FunNode fun = (FunNode)new FunNode(loc(),sig,name,_ref,null,_ref._start).peephole();
+ _code.link(fun);
+ Node rpc = new ParmNode("$rpc",0,TypeRPC.BOT,fun,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
+ Node pub = new ParmNode(ScopeNode.MEM0,1,TypeMem.BOT,fun,
+ new ProjNode(_ref._start,1,ScopeNode.MEM0).peephole()).peephole();
+ Node[] parms = new Node[nargs];
+ for( int i=0; i<nargs; i++ )
+ parms[i] = new ParmNode("arg"+i,i+2,atypes[i],fun,ConstantNode.seed(atypes[i]).peephole()).peephole();
+
+ NewNode nnn = new NewNode(ts,fun,off(ts," len")).init();
+ Node self = new ProjNode(nnn,0,ts.str()).init();
+ Node priv = new ProjNode(nnn,1,"#selfMem").init();
+ Node[] initArgs = new Node[]{fun,pub,self,priv,
+ new FunPtrNode(initRet.fun().sig(),_code._start,initRet).peephole()};
+ CallEndNode cend = rawCall(initArgs,initRet.fun());
+ Node ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
+ pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ priv = new ProjNode(cend,2,"#selfMem").peephole();
+
+ if( ctor != null ) {
+ Node[] cargs = new Node[nargs+5];
+ cargs[0]=ctl; cargs[1]=pub; cargs[2]=self; cargs[3]=priv;
+ for( int i=0; i<nargs; i++ ) cargs[i+4]=parms[i];
+ FunNode cfun = _code.link(ctor);
+ cargs[cargs.length-1] = new FunPtrNode(ctor,_code._start,cfun.ret()).peephole();
+ cend = rawCall(cargs,cfun);
+ ctl = new CProjNode(cend,0,ScopeNode.CTRL).peephole();
+ pub = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
+ priv = new ProjNode(cend,2,"#selfMem").peephole();
+ }
+
+ priv.keep();
+ for( Field field : ts._fields ) {
+ Type storage = field._t.makeStorage();
+ Field escaped = field._final ? field : field.makeFrom(storage).makeFrom(true);
+ pub.keep();
+ Node esc = new EscapeNode(escaped,self,priv,pub).peephole();
+ MemMergeNode merge = new MemMergeNode(null,pub);
+ merge.alias(field._alias,esc);
+ Node next = merge.peephole();
+ pub.unkeep();
+ pub = next;
+ }
+ priv.unkill();
+ ReturnNode ret = (ReturnNode)new ReturnNode(ctl,pub,self,rpc,fun).peephole();
+ fun.setRet(ret);
+ fun._approxUIDs = _code.UID() - oldUID;
+ _ref.addFun(_code,fun);
+ return sig;
+ }
+
+ private CallEndNode rawCall(Node[] args, FunNode target) {
+ // Establish this known declaration-side edge immediately, but do not
+ // peephole or inline it while the surrounding factory is being built.
+ // IterPeeps therefore starts with the complete initializer caller set.
+ CallNode call = new CallNode(loc(),args).init();
+ CallEndNode cend = new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).init();
+ call.link(target);
+ return cend;
+ }
+
+ // 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 ) {
+ var oldExits = _ctorExits;
+ _ctorExits = new ArrayList<>();
+ // 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);
+ _ctorExits = oldExits;
+ // 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);
}
@@ -907,38 +1422,135 @@ 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();
- boolean writable = match("!");
- boolean readonly = !writable && match("~");
// Only type with a leading `{` is a function pointer...
if( peek('{') ) return typeFunPtr();
// Otherwise you get a type name
+ int old1 = pos();
+ boolean writable = match("!");
+ boolean readonly = !writable && match("~");
String tname = _lexer.matchId();
if( tname==null ) return posT(old1);
// 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
// A qualifier before the name applies to the struct reference, not
// to an enclosing array. Arrays have their own [] / [~] qualifier.
if( t1 instanceof TypeMemPtr tmp ) {
@@ -950,14 +1562,15 @@ public class Parser {
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).makeRO();
+ t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO();
} else if( match("[]") ) {
- t2 = typeAry(t2,false);
+ t2 = TypeMemPtr.make(typeAry(t2,false));
} else
break;
}
@@ -969,27 +1582,64 @@ public class Parser {
int old2 = pos();
if( !match("!") ) 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,((TypeMemPtr)t1).withAccess(false));
+ 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 ) {
- 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) ));
- // Reuse aliases, but preserve this occurrence's element permissions.
- TypeStruct ary = ta._obj;
- return TypeMemPtr.make(TypeStruct.makeAry(tname,TypeInteger.U32,ary._fields[0]._alias,t,ary._fields[1]._alias,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 ) {
+ // Non-null reference arrays are valid types, even though an ordinary
+ // zero-filled `new T[len]` cannot construct one. Allocation retains
+ // that check in alloc(); trusted runtimes (notably argv construction)
+ // may fill the private array completely before publishing it.
+ 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);
+ }
+ // Already have the aliases, just efinal is wrong
+ return TypeStruct.makeAry(tname,TypeInteger.U32,ta._fields[0]._alias,t,ta._fields[1]._alias,efinal );
+ }
+
+ 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 }`.
@@ -998,16 +1648,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
@@ -1083,7 +1736,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;
@@ -1100,46 +1753,64 @@ public class Parser {
*/
private Node parseComparison() {
Node lhs = parseShift();
- int dir = 0;
+ int dir = parseCompDir();
+ // No compare
+ if( dir==0 ) return lhs;
+ lhs.keep();
- 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 {
- throw error("Mixing relational directions in a chained relational test");
- }
+ // Compare
+ Node rhs = parseShift().keep();
+ Node cmp = makeCompBool(dir,lhs.unkeep(),rhs).keep(); // Convert to a bool
+
+ // Stacked compares?
+ int dir0 = parseCompDir();
+ if( dir0 == 0 ) {
+ rhs.unkill();
+ return cmp.unkeep();
}
- return lhs;
+
+ // rhs is keeped() and becomes lhs
+ // cmp is NOT keeped() and is the last test
+ Node ifNode = new IfNode(ctrl(), cmp.unkeep()).peephole();
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ 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");
+ // True side parses next arm of test
+ ctrl(ifT.unkeep());
+ 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().keep();
+ 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.unkeep());
+ 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 ) {
@@ -1147,10 +1818,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
*
@@ -1171,7 +1841,7 @@ public class Parser {
ParseException err;
if( (err=lhs.err()) != null )
throw err;
- lhs = peep(lhs.widen());
+ lhs = peep(lhs);
}
return lhs;
}
@@ -1192,7 +1862,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;
}
@@ -1213,7 +1883,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;
}
@@ -1230,16 +1900,16 @@ 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._extern!=0 )
+ return parsePostfixName(_scope.in(n),name,delta);
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;
}
@@ -1247,7 +1917,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();
@@ -1259,71 +1929,118 @@ 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}
*/
private Node parsePrimary() {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
- if( _lexer.peek('"') ) return newString(parseString());
+ if( _lexer.peek('"') ) return parsePostfix(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('.') )
- // A namespace lookup is not a reference declaration.
- return parsePostfix(con(TYPES.get(((TypeMemPtr)t)._obj._name)));
- //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,true,loc());
+ _frefs.push((FRefNode)_scope.in(var));
}
// 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._extern!=0 )
+ return parsePostfixName(rvalue,id,0);
+ 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,0);
+
+ // 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,0 );
+ //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(_scope.update(_scope.var(selfx),null)));
+ }
// Assign-update direct into Scope
if( var._final )
throw error("Cannot reassign final '"+var._name+"'");
@@ -1332,33 +2049,6 @@ public class Parser {
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.
@@ -1381,17 +2071,17 @@ public class Parser {
case '-' -> new SubNode(lhs,rhs);
case '*' -> new MulNode(lhs,rhs);
case '/' -> new DivNode(lhs,rhs);
- case '&' -> new AndNode(loc(),lhs,rhs);
- case '|' -> new OrNode(loc(),lhs,rhs);
+ case '|' -> new OrNode(null,lhs,rhs);
+ case '&' -> new AndNode(null,lhs,rhs);
case '^' -> new XorNode(loc(),lhs,rhs);
case Lexer.SHL -> new ShlNode(loc(),lhs,rhs);
case Lexer.SAR -> new SarNode(loc(),lhs,rhs);
case Lexer.SHR -> new ShrNode(loc(),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,true);
+ return convertExpr(peep(op),t);
}
@@ -1400,131 +2090,119 @@ 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();
+ // A field declaration can leave a short forward type name in TYPES.
+ // Allocation happens in lexical context, so resolve that short name
+ // to the completed nested type before selecting its class helper.
+ String typeName = ts._name.indexOf('.') == -1 ? fullTypeName(ts._name) : ts._name;
+ Type resolved = TYPES.get(typeName);
+ if( resolved instanceof TypeStruct rts ) ts = rts;
+ TypeStruct clz = (TypeStruct)TYPES.get(addClzPrefix(typeName));
+ if( clz==null )
+ throw error("Unknown struct type '" + ts._name + "'");
+ Field afld = clz.field("<new>");
+ Node alloc;
+ if( afld != null ) {
+ TypeFunPtr tfp = (TypeFunPtr)afld._t;
+ FunNode fun = _code.link(tfp);
+ alloc = fun==null
+ ? new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc())
+ : new FunPtrNode(tfp,_code._start,fun.ret()).peephole();
+ } else {
+ if( !ts._fref )
+ throw error("Constructor arguments for '" + ts._name + "' but no constructor is defined");
+ Type[] sig = new Type[ctorArgs._len];
+ for( int i=0; i<ctorArgs._len; i++ )
+ sig[i] = ctorArgs.at(i)._type;
+ alloc = new FRefNode((addClzPrefix(typeName)+".<new>").intern(),loc(),
+ TypeFunPtr.make(false,sig,TypeMemPtr.make(ts)));
+ }
+ Ary<Node> args = new Ary<>(Node.class);
+ args.add(ctrl()); args.add(mem());
+ for( Node arg : ctorArgs ) args.add(arg.unkeep());
+ args.add(alloc);
+ return functionCall(args);
+ }
+
+ 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;
}
+ private Node allocArray(TypeStruct ts, Node len) {
+ Field lenFld = ts.field("#" );
+ Field bodyFld = ts.field("[]");
- /**
- * 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 + "'");
- Node prior = mem().keep();
- MemMergeNode input = new MemMergeNode((Node)null);
- for( Field f : fs ) if( !f._one ) input.alias(f._alias,prior);
- // Allocation zeroes its fields; nonzero initializers become explicit Stores.
- Node[] ns = new Node[3];
- ns[0] = ctrl();
- ns[1] = input.peephole();
- ns[2] = size;
- Node nnn = new NewNode(TypeMemPtr.make(obj),ns).peephole().keep();
- Node ptr = new ProjNode(nnn,0,obj._name).peephole().keep();
- Node out = new ProjNode(nnn,1,"$mem").peephole().keep();
- MemMergeNode after = new MemMergeNode(prior);
- for( Field f : fs ) if( !f._one ) after.alias(f._alias,out);
- mem(after.peephole());
- out.unkeep();
- nnn.unkeep();
- prior.unkill();
- for( int i=0; i<fs.length; i++ ) {
- Node val = init.get(idx+i);
- if( !fs[i]._one && val._type != val._type.makeZero() ) {
- Field f = fs[i];
- Node st = new StoreNode(loc(),f._fname,f._alias,f._t,mem(),ptr,off(obj,f._fname),val,true).peephole();
- mem(new MemMergeNode(mem(),f._alias,st).peephole());
- }
- }
- return ptr.unkeep();
- }
+ 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));
- 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);
+ // 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.makeRO());
}
- // Memory is one hidden SSA variable, including across branches and loops.
- private Node mem() { return _scope.mem(); }
- private void mem(Node n) { _scope.mem(n); }
-
- private void store(String name, int alias, Type glb, Node ptr, Node off, Node val, boolean init, Node ctrl) {
- if( !init && ptr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
- throw error("Cannot modify final field '"+name+"'");
- Node prior = mem().keep();
- Node st = new StoreNode(loc(),name,alias,glb,prior,ptr,off,val,init);
- st.setDef(0,ctrl);
- st = st.peephole();
- mem(new MemMergeNode(prior,alias,st).peephole());
- prior.unkill();
- }
-
/**
* Parse postfix expression; this can be a field expression, an array
* lookup or a postfix operator like '#'
@@ -1537,15 +2215,33 @@ 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 if( expr != self )
+ 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);
+ return parsePostfixName(expr,name,0);
+ }
+
+ private Node defaultSelf() {
+ int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize;
+ return _scope.in(_scope.update(_scope.var(selfx),null));
}
/**
@@ -1558,87 +2254,111 @@ public class Parser {
* expr ('[' expr ']')* [ [op]= expr ] // Array reference
* </pre>
*/
- private Node parsePostfixName(Node expr, String name) {
-
- 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);
- if( !ptr._obj.isAry() )
- ptr = ptr.makeFrom((TypeStruct)ptr._obj.join(ptr2._obj)); // Resolve the shape, retaining access.
- 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();
+ // pre is +/-1 for a prefix update, otherwise parse a postfix operation.
+ private Node parsePostfixName(Node expr, String name, int pre) {
+ // Keep expr across possible updates
expr.keep();
+
+ // 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 && !startsClzPrefix(ts._name) ) {
+ Type clz0 = TYPES.get(addClzPrefix(ts._name));
+ if( clz0 instanceof TypeStruct clz && (fld = latestStruct(clz).field(name)) != null ) {
+ expr.unkeep();
+ ts = latestStruct(clz);
+ expr = con(TypeMemPtr.make((byte)2,ts,true)).keep();
+ }
+ }
+ if( fld==null && ts!=null )
+ fld = fieldOrOpen(ts,name,false);
+ if( fld==null && ts!=null && !latestStruct(ts)._open )
+ throw error("Accessing unknown field '"+name+"' from '*"+ts._name+"'");
+ // 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('=','=') ) {
+ if( pre==0 && matchOpx('=','=') ) {
+ // Field assignment
Node val = parseAsgn().keep();
- Node lift = liftExpr( val, tf, false );
+ // 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();
+ if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
+ 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();
- store(name,f._alias,tf,expr.unkeep(),off.unkeep(),lift,false,base.isAry() ? ctrl() : null);
return val.unkeep(); // "obj.a = expr" returns the expression while updating memory
}
+ // 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, mem(), 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();
+ char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre;
if( ch!=0 ) {
- Node op = opAssign(ch,load,tf);
- store(name,f._alias,tf,expr.unkeep(),off.unkeep(),op,false,base.isAry() ? ctrl() : null);
- 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),")"));
- }
+ 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 ).keep();
+ mem.keep();
+ if( expr._type instanceof TypeMemPtr view && view._ro && ctrl()._type!=Type.XCONTROL )
+ throw error("Cannot modify final field '"+name+"'");
+ Node st = new StoreNode(loc(), name, alias, decl, ctrl(), mem, expr.unkeep(), off.unkeep(), op, false).peephole().keep();
+ storeMem(st,alias,mem);
+ st.unkeep();
+ mem.unkeep();
- // Drop expr, keeping load alive
- load.keep();
- expr.unkill();
- load.unkeep();
+ op.unkeep(); // A narrow Store may have folded away the expression's conversion.
+ if( postfix(ch) ) load.unkeep();
+ return pre==0 && postfix(ch) ? load : op;
}
-
- return parsePostfix(load);
+ off.unkill();
+ // Might be a method call, so pass expr as 'self'
+ return parsePostfixMethod(load,expr.unkeep());
}
@@ -1675,15 +2395,9 @@ public class Parser {
Ary<Type> ts = new Ary<>(Type.class);
Ary<String> ids = new Ary<>(String.class);
java.util.BitSet finals = new java.util.BitSet();
-
- // 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();
@@ -1699,9 +2413,44 @@ 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,finals,ids.asAry());
- return new FunPtrNode(ret._fun.sig(),ret).peephole();
+ 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);
+ java.util.BitSet finals = new java.util.BitSet();
+ 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;
+ finals.set(ids.size(),bindingFinal(t));
+ 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,finals,ids.asAry());
+ _scope.restore(preParse);
+ require("}");
+ return new FunPtrNode(tfp,_code._start,ret).peephole();
}
/**
@@ -1713,15 +2462,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;
@@ -1730,11 +2477,15 @@ public class Parser {
}
// Control & memory after parsing args
args.set(0,ctrl().keep());
- args.set(1,_scope.mem().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 )
@@ -1746,14 +2497,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());
- // A call may update any alias, so its memory result is the new whole state.
- _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
+ // Memory from CallEnd
+ 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"`
@@ -1765,10 +2518,27 @@ 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());
+ }
+
+ // A lexical C binding has a singleton namespace with one external field.
+ // The namespace occupies no storage; its field offset supplies the address.
+ private Node externData(String name, Type t) {
+ if( !(t instanceof TypeInteger) && !(t instanceof TypeFloat) )
+ throw error("C data binding requires an integer or float type");
+ String cname = ("C:"+name).intern();
+ TypeStruct ts = TypeStruct.make(cname,false,Field.make(name,t,_code.externAlias(name),false,true));
+ Type old = TYPES.putIfAbsent(cname,ts);
+ if( old!=null && old!=ts )
+ throw error("Conflicting types for C data '"+name+"'");
+ return con(TypeMemPtr.make((byte)2,ts,true));
}
/**
@@ -1776,13 +2546,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());
@@ -1832,6 +2609,14 @@ public class Parser {
return require(con(TypeInteger.constant(c)),"'");
}
+ 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
@@ -1904,6 +2689,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
*/
@@ -2050,7 +2840,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();
@@ -2064,17 +2854,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);
}
@@ -2104,7 +2903,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();
}
//
@@ -2139,9 +2938,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 e13b85c5..af371d21 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;
/**
@@ -13,9 +14,12 @@ public class Var {
// promotions.
public int _idx; // index in containing scope
private Type _type; // Declared type
- public boolean _init; // Explicit declaration initializer
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 int _extern; // C storage alias; zero for ordinary variables
public Parser.Lexer _loc; // Source location
public Var(int idx, String name, Type type, boolean xfinal, Parser.Lexer loc ) {
@@ -29,14 +33,8 @@ public class Var {
_fref = fref;
_loc = loc;
}
- // Raw declared type, without resolving a forward reference while printing.
- public Type _type() { return _type; }
- 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
@@ -51,6 +49,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 2319714b..0b314978 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -15,11 +15,16 @@ 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() ) {
// A synthetic never-exit value has no runtime definition.
if( n._type==Type.TOP ) continue;
if( n instanceof PhiNode phi && !(phi._type instanceof TypeMem) ) {
+ if( phi instanceof MachNode mach && mach.outregmap()==null )
+ continue;
// All real 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);
@@ -28,8 +33,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++ )
@@ -51,7 +56,7 @@ abstract public class BuildLRG {
}
// 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
@@ -71,8 +76,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();
@@ -80,12 +86,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 f3d3dee3..6fc66e4f 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -1,85 +1,293 @@
package com.seaofnodes.simple.codegen;
import com.seaofnodes.graph.GraphObserver;
-
import com.seaofnodes.simple.IterPeeps;
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 GraphObserver<Node> _obs;
- 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;
+ // Null for normal compilation; owned by this compilation, not a global viewer.
+ public GraphObserver<Node> _obs;
+
+ /** True when tests deliberately randomize Iter worklist order. */
+ public static boolean iterSeedOverridden() { return System.getProperty("simple.iter.seed") != null; }
+
+ // Expensive assert sampling:
+ // -Dsimple.assert.expensive=0 disables these checks
+ // -Dsimple.assert.expensive=1 checks every step
+ // -Dsimple.assert.expensive=N checks once every 1<<N steps
+ private static final int EXPENSIVE_ASSERT_LOG = Integer.getInteger("simple.assert.expensive",8);
+ public static boolean expensiveAssert() { return EXPENSIVE_ASSERT_LOG > 0; }
+ public static boolean expensiveAssert( int trip ) {
+ if( EXPENSIVE_ASSERT_LOG <= 0 ) return false;
+ if( EXPENSIVE_ASSERT_LOG == 1 ) return true;
+ int shift = Math.min(EXPENSIVE_ASSERT_LOG,30);
+ return (trip & ((1<<shift)-1)) == 0;
+ }
+
+ // ---------------------------
+ // 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;
+ assert false : "no compilation unit";
+ return null;
+ }
+
+ // 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) {
+ if( fidx < GlobalBits.RESERVED ) return false;
+ // 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=GlobalBits.RESERVED; 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, true ); }
+
+ // 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, true, arg);
+ }
+ // Test setup; no module nor file; can alter seed & argument; can re-run same CodeGen
+ public CodeGen( String src, long workListSeed, boolean resetTypes ) {
+ this(null,null,null,null, src, workListSeed, resetTypes, 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, boolean resetTypes, 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);
+ if( resetTypes ) 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<<30)) != 0 )
+ System.err.println("After "+_phase+":");
+ System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal()
+ ? asm(new SB()).toString()
+ : IRPrinter.prettyPrint(this));
+ return p2;
+ }
+
+
+ // Record times by phase
+ public final long[] _times = new long[Phase.LastPhase.ordinal()];
// ---------------------------
/**
@@ -92,9 +300,45 @@ 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); }
+
+ // C storage has one global alias per symbol, including across object files.
+ public int externAlias(String name) { return _aliases.next("C:"+name,GlobalBits.RESERVED); }
+
+ // External data addresses supplied by an in-memory linker (image offsets).
+ public final HashMap<String,Integer> _externDataAddresses = new HashMap<>();
+
+ // 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); }
// Inlining can insert CFG edges and invalidate cached depth ordering.
@@ -109,34 +353,22 @@ 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 diagnostic lookup; preserve stale entries.
- 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 _link(int fidx) { return _linker.atX(fidx); }
- // Return the FunNode from a fidx, cleaning up dead entries.
+ public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); }
+ // Return the FunNode from a fidx
public FunNode link( int fidx ) {
FunNode fun =_linker.atX(fidx);
if( fun!=null && fun.isDead() ) {
@@ -154,21 +386,49 @@ 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);
- if( _obs != null ) _obs.phase("Parse");
+ Parser.TYPES.clear();
+ Parser.TYPES.putAll(Parser.INIT_TYPES);
+ if( _src != null ) {
+ // No source file, just the source itself.
+ ParseAll.parseSource(this,_srcName==null ? "Test" : _srcName,_src);
+ } else {
+ // Path from module root to source file
+ ParseAll.parsePath(this,_srcName);
+ }
+
+ _times[Phase.Parse.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs != null ) _obs.phase(_phase.name());
return this;
}
@@ -184,25 +444,89 @@ 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: memory escape
+ // summaries must restart at FULL so newly discovered private escapes
+ // can only be removed, never added. Seed the complete graph once.
+ boolean hasImports = _externPaths != null;
+ _start.walk(n -> {
+ if( !(n instanceof ConstantNode) )
+ n._type = hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type);
+ add(n);
+ return null;
+ });
+ _iter.iterate(this);
+ // Expensive assert.
+ assert !expensiveAssert() || Opto.fixedPointCheck(this);
+
+ _times[Phase.Iter.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs != null ) _obs.phase(_phase.name());
+ return this;
+ }
+
+ private static Type iterEscapeReset(Type t) {
+ if( t instanceof TypeMem mem )
+ return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterEscapeReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+
+ private static Type iterImportReset(Type t) {
+ if( t.isHigh() )
+ return t.dual();
+ if( t instanceof TypeMem mem )
+ return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL);
+ if( t instanceof TypeMemPtr )
+ return t.makeStorage();
+ if( t instanceof TypeStruct )
+ return t.makeStorage();
+ if( t instanceof TypeTuple tt ) {
+ TypeTuple rez = tt;
+ for( int i=0; i<tt._types.length; i++ ) {
+ Type tx = iterImportReset(tt._types[i]);
+ if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
+ }
+ return rez;
+ }
+ return t;
+ }
+
+
// 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);
- if( _obs != null ) _obs.phase("Opto");
+ _times[Phase.Opto.ordinal()] = System.currentTimeMillis() - t0;
+ if( _obs != null ) _obs.phase(_phase.name());
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.
@@ -210,38 +534,128 @@ public class CodeGen {
_phase = Phase.TypeCheck;
long t0 = System.currentTimeMillis();
- Parser.ParseException err = _stop.walk(n -> {
- Parser.ParseException msg = n.err();
- if( msg != null && _obs != null ) _obs.error(n, msg.getMessage());
- return msg;
- });
- _tTypeCheck = (int)(System.currentTimeMillis() - t0);
- if( err != null )
+ 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;
+ if( _obs != null ) _obs.error(min, min.err().getMessage());
+ throw min.err();
+ }
+ if( unresolved != null ) {
+ var err = Parser.error("Unknown struct type '"+unresolved+"'",null);
+ if( _obs != null ) _obs.error(null, err.getMessage());
throw err;
+ }
if( _obs != null ) _obs.phase("TypeCheck");
return this;
}
+ private static boolean noBottomInputs(Node n) {
+ for( Node def : n._inputs )
+ if( def!=null && !(def instanceof FldOffNode) && def._type==Type.BOTTOM )
+ return false;
+ return true;
+ }
+
+ // 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
@@ -254,10 +668,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;
@@ -269,12 +682,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
@@ -294,12 +712,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;
}
@@ -307,6 +733,8 @@ public class CodeGen {
// make a machine node for "this".
private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
if( n==null ) return null;
+ if( n instanceof StoreNode st && StaticData.preinitialized(this,st) )
+ return _instSelect(st.mem(),map);
Node x = map.get(n);
if( x !=null ) return x; // Been there, done that
@@ -320,18 +748,33 @@ public class CodeGen {
}
// Produce a machine node from n; map it to flag as done so stops cycles.
+ if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
+ // External fields replace base+offset with symbol+0. Keep the ideal
+ // declaring type on the address for machine load/store width selection.
+ TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
+ n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
+ n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
+ }
map.put(n, x=_mach.instSelect(n) );
- // Stop retains the callable body. Its lifetime edge is not a machine operand.
- if( n instanceof FunPtrNode ) x._inputs.pop();
+ // 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;
}
+
// Walk all machine Nodes, and set their output edges
private void _instOuts( Node n, BitSet visit ) {
if( visit.get(n._nid) ) return;
@@ -339,14 +782,100 @@ 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() )
+ continue;
+
+ // Imported functions are not emitted by this object. Unlink any
+ // concrete calls so they become normal external relocations.
+ if( !owns(fun) ) {
+ while( fun.nIns() > 1 ) {
+ if( fun.in(1) instanceof CallNode call )
+ call.unlink(fun,1);
+ else
+ fun.removeDeadPath(1);
+ }
+ StartCUNode start = fun._compunit._start;
+ StopCUNode stop = fun._compunit._stop;
+ // Unhook the Stop->Return edge also
+ ReturnNode ret = fun.ret();
+ int idx = stop._inputs.find(ret);
+ if( idx != -1 ) {
+ stop._inputs.del(idx);
+ ret.delUse(stop);
+ }
+ //
+ if( stop.nIns()==0 ) {
+ idx = _stop._inputs.find(stop);
+ if( idx != -1 )
+ _stop.delDef(idx);
+ if( start.nIns() > 0 && start.in(0) != null )
+ start.setDef(0,null);
+ if( start.nIns() > 1 && start.in(1) != null )
+ start.setDef(1,null);
+ }
+ continue;
+ }
+
+ // Insert the post-Opto code-generation hook. Unlike an Opto-time
+ // Start input, this no longer carries unknown-caller semantics.
+ ReturnNode ret = fun.ret();
+ StartNode start = fun._compunit._start;
+ assert start!=null;
+ if( fun.nIns() <= 1 || fun.in(1) != start ) {
+ fun.insertDef(1,start);
+ for( Node use : fun._outputs )
+ if( use instanceof ParmNode parm )
+ parm.insertDef(1,ConstantNode.raw(parm._type));
+ if( fun._compunit._stop._inputs.find(ret) == -1 )
+ fun._compunit._stop.addDef(ret);
+ }
+ // Unlink from Call
+ for( int i=2; i<fun.nIns(); i++ )
+ ((CallNode)fun.in(i)).unlink(fun,i--);
+ if( fun.rpc()==null ) { // Ensure valid RPC
+ // First make sure a valid RPC; single-call into multi-fun will have each
+ // function having a single call site, so the RPC becomes a constant and
+ // folds - but since multiple targets, the Call never inlines. Recreate a
+ // valid RPC so codegen (and Eval2) understands the calling convention.
+ ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun );
+ rpc.addDef(ret.rpc());
+ ret.setDef( 3, rpc.init() );
+ }
+ }
+
+ _stop.walk(n -> {
+ if( n instanceof CallEndNode cend )
+ for( int i=1; i<cend.nIns(); i++ )
+ if( cend.in(i) instanceof ReturnNode )
+ cend.delDef(i--);
+ if( n instanceof CallNode call )
+ call.unlink_all();
+ return null;
+ });
+
+ _times[Phase.Unlink.ordinal()] = System.currentTimeMillis() - t0;
+ }
// ---------------------------
// Control Flow Graph in Reverse Post Order.
- public int _tGCM;
public Ary<CFGNode> _cfg = new Ary<>(CFGNode.class);
// Global schedule (code motion) nodes
@@ -356,27 +885,25 @@ public class CodeGen {
long t0 = System.currentTimeMillis();
GlobalCodeMotion.buildCFG(this);
- _tGCM = (int)(System.currentTimeMillis() - t0);
+ _times[Phase.Schedule.ordinal()] = System.currentTimeMillis() - t0;
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;
@@ -384,15 +911,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;
@@ -401,50 +927,169 @@ 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 )
- : IRPrinter.prettyPrint(this);
+ if( _stop == null )
+ return "No StopNode";
+ return IRPrinter.prettyPrint(this);
}
// 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() {
@@ -463,28 +1108,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..d1326cee
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
@@ -0,0 +1,135 @@
+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
+
+ // 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/ElfExpr.java b/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
deleted file mode 100644
index 9d9b8a1c..00000000
--- a/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-public class ElfExpr {
- public static void main(String[] args) {
- try (FileOutputStream fos = new FileOutputStream("hello.o")) {
- ByteBuffer buffer = ByteBuffer.allocate(512);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
-
- // === ELF HEADER (64 bytes) ===
- buffer.put(new byte[]{
- 0x7F, 'E', 'L', 'F', // ELF Magic
- 2, 1, 1, 0, // 64-bit, little-endian, version
- 0, 0, 0, 0, 0, 0, 0, // ABI padding
- 2, 0, // Type: Executable (ET_EXEC)
- 0x3E, 0x00, // Machine: x86-64
- 1, 0, 0, 0, // ELF version
- 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Entry point
- 0, 0, 0, 0, 0, 0, 0, 0, // Program header offset
- 64, 0, 0, 0, 0, 0, 0, 0, // Section header offset
- 0, 0, 0, 0, // Flags
- 64, 0, // ELF header size
- 0, 0, // Program header entry size
- 0, 0, // Number of program header entries
- 40, 0, // Section header entry size
- 2, 0, // Number of section headers
- 1, 0 // Section header string table index
- });
-
- // === .TEXT SECTION (Executable Code) ===
- byte[] textSection = {
- // sys_write (write(1, message, 13))
- (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x01, 0x00, 0x00, 0x00, // mov rax, 1
- (byte) 0x48, (byte) 0xC7, (byte) 0xC7, 0x01, 0x00, 0x00, 0x00, // mov rdi, 1
- (byte) 0x48, (byte) 0xBE, (byte)0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov rsi, message
- (byte) 0x48, (byte) 0xC7, (byte) 0xC2, 0x0D, 0x00, 0x00, 0x00, // mov rdx, 13
- (byte) 0x0F, (byte) 0x05, // syscall
-
- // sys_exit (exit(0))
- (byte) 0x48, (byte) 0xC7, (byte) 0xC0, 0x3C, 0x00, 0x00, 0x00, // mov rax, 60
- (byte) 0x48, (byte) 0x31, (byte) 0xFF, // xor rdi, rdi
- (byte) 0x0F, (byte) 0x05 // syscall
- };
-
- // Append section data
- buffer.position(256);
- buffer.put(textSection);
-
- // === STRING MESSAGE (.data Section) ===
- byte[] message = "Hello, World!\n".getBytes();
- buffer.position(400);
- buffer.put(message);
-
- // === SECTION HEADERS ===
- buffer.position(64);
-
- // Null Section (required as first entry)
- buffer.put(new byte[40]);
-
- // .text Section
- buffer.putInt(1); // Offset in section string table (".text")
- buffer.putInt(1); // Type: SHT_PROGBITS
- buffer.putLong(6); // Flags: Executable | Allocatable
- buffer.putLong(0); // Virtual address
- buffer.putLong(256); // Offset to section data
- buffer.putLong(textSection.length); // Section size
- buffer.putInt(0); // Link
- buffer.putInt(0); // Info
- buffer.putLong(4); // Address alignment
- buffer.putLong(0); // No extra entries
-
- // === SECTION HEADER STRING TABLE ===
- buffer.position(500);
- buffer.put(".text".getBytes());
- buffer.put((byte) 0x00); // Null terminator
-
- fos.write(buffer.array());
- System.out.println("ELF object file 'hello.o' created.");
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-}
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 b6267600..00000000
--- a/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
+++ /dev/null
@@ -1,382 +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];
-
- if( fun._name==null ) continue; // Anonymous addresses are relocated within .text.
- 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..1b6281b8
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
@@ -0,0 +1,431 @@
+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 ) {
+ this(".rela.text",info);
+ }
+ ReloSection(String name,int info) {
+ super(name, SHT_RELA, SHF_INFO_LINK, 24/*entsize*/);
+ _link = 2;
+ _info = info;
+ }
+
+ void writeReloOffPlus(int offset, int symidx, byte relo) {
+ writeRelo(offset,symidx,relo,-4);
+ }
+ void writeRelo(int offset,int symidx,byte relo,long addend) {
+ assert symidx > 0;
+ write8(offset);
+ write8(((long)symidx << 32L) | relo);
+ write8(addend);
+ }
+ }
+
+
+ // ------------------------------------------------------------------------
+ // 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, boolean emitEntrySymbol) {
+ 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
+ // The C runtime owns `main` and adapts argc/argv into
+ // a normal Simple array. Keep the Simple function on
+ // its native ABI under a private entry symbol.
+ String name = emitEntrySymbol && "main".equals(fun._name)
+ ? "simple_main"
+ : fun._name;
+ symbol(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; // encodeFunctions exported this as simple_main
+ 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("simple_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*/+9/*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, emitEntrySymbol);
+ // 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*/);
+ }
+
+ // Native ELF output is AMD64. Data addresses use PC32, not PLT calls.
+ for( Node n : enc._externData.keySet() ) {
+ String extern = enc._externData.get(n);
+ int symidx = symbols.symbol(extern, 0, SYM_BIND_GLOBAL, SYM_TYPE_OBJECT);
+ int offset = enc.opStart(n) + enc.opLen(n) - 4;
+ relocations.writeReloOffPlus(offset, symidx, (byte)2/*PC32*/);
+ }
+
+ // Stable function identities are also addressable from data, including
+ // anonymous functions which have no source-level exported name.
+ HashMap<String,Integer> dataSymbols=new HashMap<>();
+ for( FunNode fun : _code._linker )
+ if( fun!=null && !fun.isDead() && _code.owns(fun) && enc.opStart(fun)>=0 ) {
+ String name=_code._fidxs.symbol(fun.sig().fidx());
+ dataSymbols.put(name,symbols.symbol(name,text._index,SYM_BIND_GLOBAL,SYM_TYPE_FUNC,enc.opStart(fun),0));
+ }
+ for( StaticData.ObjectData obj : enc._data.objects.values() ) {
+ int section=obj.external ? 0 : obj.readOnly ? rodata._index : rwdata._index;
+ dataSymbols.put(obj.symbol,symbols.symbol(obj.symbol,section,SYM_BIND_GLOBAL,SYM_TYPE_OBJECT,
+ obj.external ? 0 : obj.offset,obj.external ? 0 : obj.size));
+ }
+ for( Encoding.Relo relo : enc._bigCons.values() ) {
+ StaticData.ObjectData obj=enc._data.object(relo._t);
+ relocations.writeReloOffPlus(relo._opStart+relo._off,dataSymbols.get(obj.symbol),relo._elf);
+ }
+ ReloSection roRelos=new ReloSection(".rela.rodata",rodata._index);
+ ReloSection rwRelos=new ReloSection(".rela.data",rwdata._index);
+ for( StaticData.Relocation relo : enc._data.relocations ) {
+ String name=enc._data.targetSymbol(relo.value());
+ Integer sym=dataSymbols.get(name);
+ if( sym==null ) {
+ sym=symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_NOTYPE);
+ dataSymbols.put(name,sym);
+ }
+ // AMD64 absolute 32/64-bit addresses; linker rejects overflow.
+ byte kind=(byte)(relo.logSize()==2 ? 10 : 1);
+ (relo.object().readOnly ? roRelos : rwRelos).writeRelo(relo.object().offset+relo.offset(),sym,kind,0);
+ }
+
+ // 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 b5a8e148..8e9b99db 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 implements com.seaofnodes.isa.CodeSink {
// 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,12 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// - 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<>();
+ public final StaticData _data;
+ public final HashMap<String,Integer> _externalAddresses = new HashMap<>();
// Big Constant relocation info.
public static class Relo {
@@ -56,17 +52,28 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
public final int _structSize; // Capture layout during encoding, not printing
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;
_align = t.alignment();
_structSize = t instanceof TypeStruct ts ? ts.size() : 0;
+ _readOnly = readOnly(t);
+ }
+ public boolean readOnly() { return _readOnly; }
+ private final boolean _readOnly;
+ private static boolean readOnly(Type t) {
+ // Class initializers still write their fields, even when every
+ // final value is constant. Their storage must remain writable.
+ return !(t instanceof TypeStruct ts) ||
+ (!Parser.startsClzPrefix(ts._name) && ts.isConstant());
}
}
- public final HashMap<Node,Relo> _bigCons = new HashMap<>();
- Encoding( CodeGen code ) { _code = code; }
+ Encoding( CodeGen code ) {
+ _code = code;
+ _data = new StaticData(this);
+ }
// Shortcut to the defining register
public short reg(Node n) {
@@ -107,14 +114,6 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
}
// 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);
@@ -127,48 +126,91 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
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;
}
+ public final HashMap<Node,String> _externData = new HashMap<>();
+ public void externalData(Node n, String name) { _externData.put(n,name); }
+
// 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.
@@ -178,8 +220,13 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// encodings and compact the code, changing all the offsets.
compactShortForm();
+ // Place pool objects and record relocations within their contents.
+ _data.write();
+
// Patch RIP-relative and local encodings now.
patchLocalRelocations();
+
+ return this;
}
// --------------------------------------------------
@@ -187,29 +234,48 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// 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
}
@@ -283,7 +349,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
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
@@ -328,24 +394,26 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// --------------------------------------------------
// 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)));
}
}
}
@@ -355,10 +423,10 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// 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.
@@ -372,24 +440,27 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
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);
}
}
}
@@ -400,21 +471,21 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// 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];
@@ -422,7 +493,7 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
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;
}
@@ -437,103 +508,59 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
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 ) {
- 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);
- int align = relo._t.alignment();
- Ary<Relo> relos = raligns[align]==null ? (raligns[align]=new Ary<>(Relo.class)) : raligns[align];
- relos.add(relo);
- }
-
-
- // Types can be used more than once; collapse the dups
- HashMap<Type,Integer> targets = new HashMap<>();
-
- // By alignment
- for( int align = 4; align >= 0; align-- ) {
- Ary<Relo> relos = raligns[align];
- if( relos == null ) continue;
- for( Relo relo : relos ) {
- // Map from relo to constant start and patch
- Integer target = targets.get(relo._t);
- if( target==null ) {
- 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)
- default -> addN(align,relo._t,bits);
- }
- }
- // Record target address and opcode 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);
- }
- }
- }
-
- // 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);
- }
-
- // Structs use internal field layout
- private void cpool( BAOS bits, TypeStruct ts ) {
- // Field order by offset
- int[] layout = ts.layout();
- int off=0; // offset in the struct
- for( int fn=0; fn<ts._fields.length; fn++ ) {
- Field f = ts._fields[layout[fn]];
- int foff = ts. offset(layout[fn]);
- // Pad up to field
- while( off < foff ) { bits.write(0); off++; };
- // Constant array fields are special
- if( f._fname=="[]" ) { // Must be a constant array
- ((TypeConAry)f._t).write(bits);
- off += ((TypeConAry)f._t).len();
- } else {
- int log = f._t.log_size();
- addN(log,f._t,bits);
- off += 1<<log;
- }
- }
+ // 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);
}
-
// A series of libc/external calls that Simple can link against in a JIT.
// Since no runtime in the JVM process, using magic numbers for the CPU
// emulators to pick up on.
public static int SENTINEL_CALLOC = -4;
public static int SENTINEL_WRITE = -8;
+ int externalAddress(String name) {
+ Integer address=_externalAddresses.get(name);
+ if( address!=null ) return address;
+ return switch(name) {
+ case "calloc" -> SENTINEL_CALLOC;
+ case "write" -> SENTINEL_WRITE;
+ default -> throw new IllegalArgumentException("Unresolved external function '"+name+"'");
+ };
+ }
+
void patchGlobalRelocations() {
+ for( Node src : _externData.keySet() ) {
+ String name = _externData.get(src);
+ Integer target = _code._externDataAddresses.get(name);
+ if( target==null ) throw new IllegalArgumentException("Unresolved C data symbol '"+name+"'");
+ int start = opStart(src);
+ ((RIPRelSize)src).patch(this,start,opLen(src),target-start);
+ }
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);
+ int target = externalAddress(dst);
+ ((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..bc9c9d39
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
@@ -0,0 +1,177 @@
+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);
+ }
+
+ // Stable native symbol identity, independent of this compilation's dense IDs.
+ String owner(int local) { return _local2Clz.at(local); }
+ String symbol(int local) {
+ return _local2Clz.at(local)+"$F$"+_local2Order.at(local);
+ }
+
+ // 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 80d5d9bb..8eebc15b 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -27,16 +27,15 @@ public abstract class GlobalCodeMotion {
breakUpGlobalConstants(code._start);
code._visit.clear();
- schedLate (code);
+ schedLate(code);
}
// Post-Order of CFG
private static void _rpo_cfg(CFGNode def, Node use, BitSet visit, Ary<CFGNode> rpo) {
if( !(use instanceof CFGNode cfg) || visit.get(cfg._nid) )
return; // Been there, done that
- if( def instanceof ReturnNode && use instanceof CallEndNode )
- return;
- assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now
+ assert !( def instanceof ReturnNode && use instanceof CallEndNode ); // All calls unwired now
+ assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now
visit.set(cfg._nid);
for( Node useuse : cfg._outputs )
_rpo_cfg(cfg,useuse,visit,rpo);
@@ -50,7 +49,7 @@ public abstract class GlobalCodeMotion {
var cons = new ArrayList<Node>();
for( Node con : start._outputs )
if( con!=null && !(con instanceof CFGNode) &&
- (con.isConst() || (con instanceof MachNode mach && mach.outregmap()!=null)) ) {
+ (con.isConst() || con instanceof MachNode mach && mach.outregmap()!=null) ) {
globals.put(con,true);
cons.add(con.keep()); // Preserve original inputs while rewiring users.
}
@@ -61,6 +60,7 @@ public abstract class GlobalCodeMotion {
if( use==null || globals.containsKey(use) ) continue;
FunNode fun = useFun(use);
if( fun==null ) continue; // Global metadata has no function owner.
+ assert CodeGen.CODE.owns(fun);
var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>());
Node copy = cloneGlobal(con,fun,globals,local);
for( int i=0; i<use.nIns(); i++ )
@@ -207,7 +207,6 @@ public abstract class GlobalCodeMotion {
if( n instanceof MemOpNode load && load._isLoad )
lca = find_anti_dep(lca,load,early,late,anti);
-
// Walk up from the LCA to the early, looking for best place. This is
// the lowest execution frequency, approximated by least loop depth and
// deepest control flow.
@@ -224,7 +223,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 ) return late[use._nid];
+ if( use instanceof ParmNode parm )
+ return late[use._nid];
if( !(use instanceof PhiNode phi) )
return late[use._nid];
CFGNode found=null;
@@ -245,7 +245,7 @@ public abstract class GlobalCodeMotion {
best instanceof IfNode;
}
- // An allocation may consume a load's memory through a partial aggregate.
+ // A writer may consume a load's memory through an aggregate.
private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) {
if( visit.get(def._nid) ) return;
visit.set(def._nid);
@@ -259,7 +259,7 @@ public abstract class GlobalCodeMotion {
private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti) {
// We could skip final-field loads here.
// Walk LCA->early, flagging Load's block location choices
- for( CFGNode cfg=lca; early!=null && cfg!=early.idom(); cfg = cfg.idom() )
+ for( CFGNode cfg=lca; cfg!=early.idom(); cfg = cfg.idom() )
anti[cfg._nid] = load._nid;
// Walk load->mem uses, looking for Stores causing an anti-dep
for( Node mem : load.antiDeps() ) {
@@ -268,10 +268,14 @@ public abstract class GlobalCodeMotion {
assert late[st._nid]!=null;
lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
break;
- case CallNode st:
+ case EscapeNode st:
assert late[st._nid]!=null;
lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
break;
+ case CallNode call:
+ assert late[call._nid]!=null;
+ lca = anti_dep(load,late[call._nid],call.cfg0(),lca,call,anti);
+ break;
case PhiNode phi:
// Repeat anti-dep for matching Phi inputs.
// No anti-dep edges but may raise the LCA.
@@ -279,11 +283,7 @@ public abstract class GlobalCodeMotion {
if( phi.in(i)==load.mem() )
lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti);
break;
- case NewNode st:
- assert late[st._nid]!=null;
- lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
- break;
- default: throw Utils.TODO();
+ default: throw Utils.TODO("Should not reach here");
}
}
return lca;
diff --git a/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index bcd1957d..bcdb14bf 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/IFG.java
@@ -69,9 +69,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 );
@@ -208,7 +208,13 @@ abstract public class IFG {
assert tlrg.leader();
// Always, tlrg cannot use kills
if( tlrg._mask.overlap(killMask) ) {
- if( !tlrg.sub(killMask) ) alloc.fail(tlrg);
+ Node live = TMP.get(tlrg);
+ // Disallow clone-ables from living across killed registers.
+ // Fail them and let splitting re-clone closer to the target use.
+ if( live instanceof MachNode mach && mach.isClone() )
+ alloc.fail(tlrg);
+ else if( !tlrg.sub(killMask) )
+ alloc.fail(tlrg);
}
}
}
@@ -316,7 +322,7 @@ abstract public class IFG {
// Pull all lrgs from IFG, in trivial order if possible
while( sptr < color_stack.length ) {
// Swap best color up front
- pickColor(color_stack,sptr,swork);
+ pickColor(color_stack,sptr,swork,alloc);
// Pick a trivial lrg, and (temporarily) remove from the IFG.
LRG lrg = color_stack[sptr++];
@@ -372,10 +378,10 @@ abstract public class IFG {
}
// Pick LRG from color stack
- private static void pickColor(LRG[] color_stack, int sptr, int swork) {
+ private static void pickColor(LRG[] color_stack, int sptr, int swork, RegAlloc alloc) {
// Out of trivial colorable, pick an at-risk to pull
if( sptr==swork )
- swap(color_stack,sptr,pickRisky(color_stack,sptr));
+ swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc));
// When coloring, we'd like to give more choices; so when coloring we'd
// like to see the single-def first (since no choices anyway), then
// non-split related (so more live ranges get colored), then
@@ -392,6 +398,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() )
@@ -399,18 +406,22 @@ 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();
}
// Pick a live range that hasn't already spilled, or has a single-def-
// single-use that are not adjacent.
- private static int pickRisky( LRG[] color_stack, int sptr ) {
+ private static int pickRisky( LRG[] color_stack, int sptr, RegAlloc alloc ) {
int best=sptr;
- int bestScore = pickRiskyScore(color_stack[best]);
+ int bestScore = pickRiskyScore(color_stack[best],alloc);
for( int i=sptr+1; i<color_stack.length; i++ ) {
if( bestScore == 1000000 ) return best; // Already max score
- int iScore = pickRiskyScore(color_stack[i]);
+ int iScore = pickRiskyScore(color_stack[i],alloc);
if( iScore > bestScore )
{ best = i; bestScore = iScore; }
}
@@ -425,30 +436,60 @@ abstract public class IFG {
//
// Picking a live range that is very close to coloring might allow it to
// color despite being risky.
- private static int pickRiskyScore( LRG lrg ) {
+ private static int pickRiskyScore( LRG lrg, RegAlloc alloc ) {
+ // Callee-save live ranges cover the whole function and are cheap to
+ // split. Rank scheduled area recovered against the loop-scaled cost
+ // of the def- and use-side moves.
+ if( lrg._machDef instanceof CalleeSaveNode )
+ return areaScore(functionArea(lrg._fun,alloc),
+ splitCost((Node)lrg._machDef,(Node)lrg._machUse));
+ if( lrg._splitDef != null && lrg._splitDef. in(1) instanceof CalleeSaveNode &&
+ lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
+ return areaScore(functionArea(lrg._fun,alloc),
+ splitCost(lrg._splitDef,lrg._splitUse));
+
// Pick single-def clonables that are not right next to their single-use.
- // Failing to color these will clone them closer to their uses.
+ // Rank by the area recovered by cloning closer to the use.
if( !lrg._multiDef && lrg._machDef!=null && lrg._machUse!=null && lrg._machDef.isClone() ) {
- Node def = (Node)lrg._machDef;
- Node use = (Node)lrg._machUse;
+ Node def = ((Node)lrg._machDef);
+ Node use = ((Node)lrg._machUse);
CFGNode cfg = def.cfg0();
- if( lrg._multiUse || cfg != use.cfg0() || // Many uses or different blocks OR
- // Same block, but not close
- cfg._outputs.find(def)+1 < cfg._outputs.find(use) )
+ int area = cfg != use.cfg0()
+ ? functionArea(lrg._fun,alloc)
+ : cfg._outputs.find(use)-cfg._outputs.find(def)-1;
+ // A single-use function address can move next to its use without
+ // adding an instruction. Prefer that to saving a callee-save
+ // register just to hold the address. Stay in the same block and
+ // require a gap so the clone makes progress.
+ if( def instanceof FunPtrNode && !lrg._multiUse &&
+ cfg == use.cfg0() && area > 0 )
return 1000000;
+ if( area > 0 )
+ return areaScore(area,loopCost(use));
}
- // Always pick callee-save registers as being very large area recovered
- // and very cheap to spill.
- if( lrg._machDef instanceof CalleeSaveNode )
- return 1000000-2-lrg._mask.firstReg();
- if( lrg._splitDef != null && lrg._splitDef.in(1) instanceof CalleeSaveNode &&
- lrg._splitUse != null && lrg._splitUse.out(0) instanceof ReturnNode )
- return 1000000-1;
+ // Default for ranges without a specific area/cost estimate.
+ return 1000 + (lrg._multiUse ? -100 : 0);
+ }
+
+ private static int functionArea(FunNode fun, RegAlloc alloc) {
+ int area=0;
+ for( CFGNode bb : alloc._code._cfg )
+ if( bb.fun()==fun )
+ area += bb.nOuts();
+ return area;
+ }
+
+ private static int areaScore(int area, int cost) {
+ return 1000 + (area<<10)/cost;
+ }
+
+ private static int splitCost(Node def, Node use) {
+ return loopCost(def) + (use==null ? 0 : loopCost(use));
+ }
- // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
- // and "area" for benefit
- return 1000;
+ private static int loopCost(Node n) {
+ return 1 << (n.cfg0().loopDepth()*3);
}
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..252b5bf0 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,16 +10,24 @@ 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);
+ int target = enc._data.address(enc._data.object(relo._t),cpool,sdata);
+ ((RIPRelSize)relo._op).patch(enc, relo._opStart, enc.opLen(relo._op), target - relo._opStart);
+ }
+ enc._data.link(cpool,sdata);
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 a95faefc..3eee8fc3 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -7,28 +7,42 @@ import com.seaofnodes.simple.type.TypeInteger;
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 );
@@ -94,13 +108,18 @@ 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; }
// Only the declared C result width describes the physical return register.
// Simple functions already return canonical full-width integer values.
protected static TypeInteger cReturn(ProjNode prj) {
if( prj._idx==2 && prj.in(0) instanceof CallEndNode cend &&
- cend.call().fptr() instanceof ExternNode ext &&
- ((TypeFunPtr)ext._con).ret() instanceof TypeInteger ret && ret.log_size()<3 )
+ cend.call().fptr()._type instanceof TypeFunPtr tfp && tfp.isConstant() &&
+ CodeGen.CODE.externFunc(tfp.fidx())!=null &&
+ tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 )
return ret;
return null;
}
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 cd52aadd..132705aa 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,38 +26,22 @@ 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);
+ // 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);
+
+ if( !code._iter._work.isEmpty() )
+ sccp(code, oldTypes);
+
// After SCCP add everything changed to the worklist and see if
// peepholes can make more progress.
moveChangesToWorklist(code, oldTypes);
@@ -73,15 +59,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 +84,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,44 +92,55 @@ 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
- // - True for function addresses used as values
- // - Otherwise false 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;
- // No escape analysis yet: a surviving address may be called from outside.
- if( fun.hasPtr() ) 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();
+ // An unresolved forward call can still carry the infinite, inferred
+ // function set into Opto. There is no finite call graph to link yet;
+ // type checking will report the unresolved reference afterwards. This
+ // can yet be valid if the call is eventually dead.
+ // `if( false ) frefCall(args);`
+ if( XInt.isHigh(fidxs) ) return;
+ 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._link(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) ) {
@@ -149,6 +151,56 @@ 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 ) {
+ assert 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
@@ -160,108 +212,161 @@ 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
- if( n instanceof CallNode call && oval.isHigh() && !nval.isHigh() && call.fptr()._type instanceof TypeFunPtr )
+ // Link a Call which becomes alive. Like above, adds new Call
+ // Graph edges to the graph.
+ if( n instanceof CallNode call && nval == Type.CONTROL && 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 )
+ linkStart(code,(TypeTuple)n._type);
+
// 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);
+ // Quadratic (expensive) small-step assert.
+ assert !CodeGen.expensiveAssert(count) || 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);
+ // A closed recursive numeric component can have no remaining
+ // concrete evidence after SCCP, e.g. dead all-integer loops.
+ // With no float evidence, settle to integer.
+ if( mode == 0 )
+ mode = 1;
+ if( mode != 0 ) {
+ // If both evidence, FP wins
+ modeNode.setMode((byte)((mode&2)==2 ? 2 : 1));
+ code.add(n);
+ code.addAll(n.outs());
+ }
+ }
+ 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 ) return 0;
+ if( 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;
+ assert !(n instanceof ConvertNode); // No test case
+ switch( n ) {
+ case ModeNode modeNode when modeNode.mode() != 0 -> { return modeNode.mode(); }
+ case ModeNode modeNode -> {}
+ case PhiNode phi -> {}
+ case ReturnNode ret -> {}
+ case ProjNode proj when proj.in(0) instanceof CallEndNode cend -> {
+ for( int i=1; i<cend.nIns(); i++ )
+ evidence |= numericEvidence(cend.in(i),visited);
+ }
+ 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) );
+ Node con = off.asOffset();
+ assert con != null; // Can be null for trying to reference missing field
+ off.subsume( con );
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();
- }
- 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..90309328
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
@@ -0,0 +1,396 @@
+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 ) {
+ boolean alloc = fref._name.endsWith(".<new>");
+ String clzName = alloc
+ ? fref._name.substring(0,fref._name.length()-".<new>".length()).intern()
+ : fref._name;
+ TypeStruct clz = (TypeStruct)Parser.TYPES.get(clzName);
+ if( clz == null )
+ return null;
+ Node con;
+ if( alloc ) {
+ Field ctor = clz.field("<new>");
+ if( ctor == null || !(ctor._t instanceof TypeFunPtr tfp) ) {
+ String name = clzName.startsWith(Parser.CLZ)
+ ? clzName.substring(Parser.CLZ.length()) : clzName;
+ int dot = name.lastIndexOf('.');
+ throw Parser.error("Unknown struct type '"+(dot==-1 ? name : name.substring(dot+1))+"'",fref._loc);
+ }
+ FunNode fun = code.link(tfp);
+ if( fun == null ) return null;
+ con = new FunPtrNode(tfp,code._start,fun.ret()).peephole();
+ } else {
+ // Class pointer constant:
+ TypeMemPtr clzptr = TypeMemPtr.make((byte)2,clz,true);
+ con = code.con(clzptr);
+ }
+ // Replace and optimize
+ fref.addDef(con);
+ 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 7e826661..1ce68080 100644
--- a/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -57,6 +57,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<>();
@@ -68,6 +70,7 @@ public class RegAlloc {
_failed.put(lrg,"");
}
boolean success() { return _failed.isEmpty(); }
+ public boolean done() { return _done; }
// -----------------------
@@ -76,16 +79,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();
@@ -142,18 +145,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)+"]";
}
// -----------------------
@@ -179,8 +183,7 @@ public class RegAlloc {
for( Node n : bb._outputs )
if( n instanceof NewNode nnn ) nnn.cacheRegs(_code);
}
-
- // Optional diagnostic for values accidentally shared across functions.
+ // Expensive assert: useful when chasing cross-function allocator edges.
//assert verifyFunctionLocalEdges();
// Top driver: repeated rounds of coloring and splitting.
@@ -192,23 +195,66 @@ public class RegAlloc {
round++;
}
postColor(); // Remove no-op spills
+ _done = true;
}
boolean verifyFunctionLocalEdges() {
- for( CFGNode bb : _code._cfg ) {
- if( bb instanceof StartNode ) continue;
- FunNode fun = bb.fun();
- for( Node use : bb._outputs )
- if( use instanceof MachNode mach && !(use instanceof ParmNode) )
- for( int i=1; i<use.nIns(); i++ ) {
- Node def = use.in(i);
- if( def==null || mach.regmap(i)==null || def.cfg0()==null || def.cfg0() instanceof StartNode ) continue;
- assert def.cfg0().fun()==fun : "Cross-function register edge: "+def.uniqueName()+" -> "+use.uniqueName()+" input "+i;
- }
- }
+ for( CFGNode bb : _code._cfg )
+ for( Node use : bb._outputs ) {
+ FunNode useFun = funOf(use);
+ for( int i=1; i<use.nIns(); i++ ) {
+ Node def = use.in(i);
+ if( def==null || def.isConst() || allowedCrossFunctionEdge(use,i,def) )
+ continue;
+ FunNode defFun = funOf(def);
+ if( defFun != null && useFun != null && defFun != useFun )
+ throw new AssertionError(crossFunctionMessage(def,defFun,use,useFun,i));
+ }
+ }
return true;
}
+ private static String crossFunctionMessage(Node def, FunNode defFun, Node use, FunNode useFun, int idx) {
+ StringBuilder sb = new StringBuilder("Cross-function value edge: ");
+ sb.append(def.uniqueName()).append("(").append(defFun.label()).append(") -> ")
+ .append(use.uniqueName()).append("(").append(useFun.label()).append(") input ").append(idx)
+ .append(" def=").append(def).append(" use=").append(use).append("\n all uses:");
+ for( Node out : def.outs() ) {
+ FunNode outFun = funOf(out);
+ sb.append("\n ").append(out.uniqueName()).append("(")
+ .append(outFun==null ? "?" : outFun.label()).append(")");
+ }
+ return sb.toString();
+ }
+
+ private static boolean allowedCrossFunctionEdge(Node use, int idx, Node def) {
+ // Function metadata/control edges are not value live ranges.
+ if( use instanceof FunNode || use instanceof StopNode || use instanceof StopCUNode )
+ return true;
+ // Return-linked function pointers carry function identity/lifetime, not
+ // an allocator value flowing from the callee body into this function.
+ if( use instanceof FunPtrNode && def instanceof ReturnNode )
+ return true;
+ // Parm defaults and call-linked argument edges are the entry protocol;
+ // BuildLRG also skips Parm input 1.
+ if( use instanceof ParmNode )
+ return true;
+ // Linked CallEnd -> Return edges should be gone before RegAlloc, but
+ // diagnose allocator value edges rather than call graph metadata here.
+ if( use instanceof CallEndNode && def instanceof ReturnNode )
+ return true;
+ return false;
+ }
+
+ private static FunNode funOf(Node n) {
+ if( n instanceof ParmNode parm ) return parm.fun();
+ if( n instanceof ReturnNode ret ) return ret.fun();
+ if( n instanceof FunNode fun ) return fun;
+ if( n instanceof CFGNode cfg ) return cfg.fun();
+ CFGNode cfg = n.cfg0();
+ return cfg==null ? null : cfg.fun();
+ }
+
private boolean graphColor(byte round) {
_failed.clear();
_lrgs.clear();
@@ -252,7 +298,7 @@ public class RegAlloc {
// Register mask when empty; split around defs and uses with limited
// register masks.
- if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) {
+ if( lrg._mask.isEmpty() ) {
if( lrg._1regDefCnt <= 1 &&
lrg._1regUseCnt <= 1 &&
(lrg._1regDefCnt + lrg._1regUseCnt) > 0 &&
@@ -265,6 +311,10 @@ public class RegAlloc {
if( splitEmptyMaskByUse(round,lrg) )
return true;
}
+ if( lrg._mask.isEmpty() && lrg._multiDef && lrg._1regUseCnt > 1 && lrg._1regDefCnt <= 1 ) {
+ if( splitEmptyMaskSingleUses(round,lrg) )
+ return true;
+ }
// Generic split-by-loop depth.
return splitByLoop(round,lrg);
@@ -277,18 +327,20 @@ 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() )
- // An earlier copy is reusable only if no intervening clobber.
- // Being in the same block alone is insufficient. Example:
+ // Force must-split, even if a prior split same block because register
+ // conflicts. Example:
// alloc
// V1/rax - forced by alloc
// alloc
// V2/rax - kills prior RAX
// st4 [V1],len - No good, must split around
- insertAfterAndReplace(makeSplit("def/empty1",round,lrg),(Node)lrg._machDef,false);
+ insertAfterAndReplace( makeSplit("def/empty1",round,lrg), (Node)lrg._machDef, false/*true*/);
// Split just before use
- if( lrg._1regUseCnt==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;
}
@@ -331,6 +383,27 @@ public class RegAlloc {
return true;
}
+ // Empty-mask live range with several direct single-register uses, often
+ // through a Phi with multiple defs. Split those uses directly instead of
+ // doing a loop-depth split, which might keep isolating the wrong edge.
+ boolean splitEmptyMaskSingleUses( byte round, LRG lrg ) {
+ findAllLRG(lrg);
+ if( _ns._len <= 5 )
+ return false;
+ boolean progress = false;
+ for( Node n : _ns ) {
+ if( !(n instanceof MachNode mach) ) continue;
+ for( int i=1; i<n.nIns(); i++ ) {
+ RegMask use = mach.regmap(i);
+ if( use != null && use.size1() && lrg(n.in(i))==lrg ) {
+ insertBefore(n,i,"use/emptyN",round,lrg);
+ progress = true;
+ }
+ }
+ }
+ return progress;
+ }
+
// Narrowing preserves all earlier users of a class; new classes are disjoint.
// Each use contains its first overlapping class, so one pass suffices.
private static void putIntoRegClass( Ary<RegMask> rclass, RegMask rmask ) {
@@ -439,9 +512,11 @@ public class RegAlloc {
int min = (int)ld;
int max = (int)(ld>>32);
-
- // If the minLoopDepth is less than the maxLoopDepth: for-all defs and
- // uses, if at minLoopDepth or lower, split after def and before use.
+ boolean emptyMulti = lrg._mask.isEmpty() && lrg._multiDef && lrg._1regDefCnt <= 1 &&
+ (lrg._1regUseCnt > 1 || _ns._len > 5);
+ // Multi-def empty-mask conflicts need the hot/deep side isolated from
+ // fixed register uses. Ordinary pressure splits keep the old shallow
+ // boundary behavior.
for( Node n : _ns ) {
if( n instanceof SplitNode && min!=max ) continue; // Ignoring splits; since spilling need to split in a deeper loop
if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
@@ -452,7 +527,7 @@ public class RegAlloc {
if( lrg(n)==lrg && // This is a LRG def
// At loop boundary, or splitting in inner loop
- (min==max || n.cfg0().loopDepth() <= min) ) {
+ (min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) {
// Cloneable constants will be cloned at uses, not after def
if( !(n instanceof MachNode mach && mach.isClone()) &&
// Single user is already a split adjacent
@@ -467,7 +542,7 @@ public class RegAlloc {
// No split in front of a split
if( !(n.in(i) instanceof SplitNode) &&
// splitting in inner loop or at loop border
- (min==max || phi.region().cfg(i).loopDepth() <= min) &&
+ (min==max || (emptyMulti ? phi.region().cfg(i).loopDepth() >= max : phi.region().cfg(i).loopDepth() <= min)) &&
// and not around the backedge of a loop (bad place to force a split, hard to remove)
!(phi.region() instanceof LoopNode && i==2 && (phi.in(i) instanceof PhiNode pp && pp.region()==phi.region())) )
// Split before phi-use in prior block
@@ -479,7 +554,8 @@ public class RegAlloc {
// This is a LRG use
// splitting in inner loop or at loop border
if( lrgSame( n.in( i ), lrg ) &&
- (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) )
+ (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) ||
+ (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) )
// Split before in this block
insertBefore( n, i, "use/loop/use", round,lrg, false );
}
@@ -528,8 +604,8 @@ public class RegAlloc {
void findAllLRG( LRG lrg ) {
_ns.clear();
int wd = 0;
- if( lrg._machDef!=null ) _ns.push((Node)lrg._machDef);
- if( lrg._machUse!=null ) _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;
@@ -590,12 +666,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;
}
@@ -699,6 +775,6 @@ public class RegAlloc {
if( clobbers(n,defreg) )
return false; // Clobbered
}
- throw Utils.TODO();
+ throw Utils.TODO("Should not reach here");
}
}
diff --git a/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
index 303bba9e..9cb9ec16 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..27253c5e
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
@@ -0,0 +1,614 @@
+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 the C0DE header. The format is not versioned yet.
+ 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("Unsupported serialized IR format; rebuild the object file");
+
+ // 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( "Unsupported serialized IR format; rebuild the object file" );
+
+ // 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 = i;
+ if( n instanceof MemMergeNode && i >= GlobalBits.RESERVED ) {
+ if( def == null ) continue;
+ local = aliases.map(fileAliases,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() ) {
+ // 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/codegen/StaticData.java b/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
new file mode 100644
index 00000000..f9f16008
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
@@ -0,0 +1,195 @@
+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.BAOS;
+import java.util.*;
+
+// Pool objects have identity independently of their initial field values.
+// References in their contents use the same symbols in ELF and emulator images.
+public final class StaticData {
+ public static final class ObjectData {
+ public final Type type;
+ public final String symbol;
+ public final boolean readOnly, external;
+ public final int alignment, size;
+ public int offset;
+ ObjectData(Type t, String name, boolean ro, boolean ext) {
+ type=t; symbol=name; readOnly=ro; external=ext;
+ alignment=t.alignment();
+ int align=1<<alignment;
+ size=t instanceof TypeStruct ts ? (Math.max(1,ts.size())+align-1)&-align : align;
+ }
+ }
+ public record Relocation(ObjectData object, int offset, int logSize, Type value) {}
+
+ final Encoding enc;
+ final LinkedHashMap<Object,ObjectData> objects = new LinkedHashMap<>();
+ final ArrayList<Relocation> relocations = new ArrayList<>();
+ final String prefix;
+
+ public ArrayList<ObjectData> entries() {
+ ArrayList<ObjectData> entries=new ArrayList<>(objects.values());
+ entries.sort(Comparator.comparingInt((ObjectData obj) -> obj.alignment).reversed());
+ return entries;
+ }
+
+ StaticData(Encoding enc) {
+ this.enc=enc;
+ prefix=enc._code.entryClinitName()+"$DATA$";
+ }
+
+ static boolean isClass(Type t) {
+ return t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name);
+ }
+
+ static CompUnit owner(CodeGen code,TypeStruct ts) {
+ return owner(code,ts._name.substring(Parser.CLZ.length()));
+ }
+ static CompUnit owner(CodeGen code,String name) {
+ if(name==null) return null;
+ CompUnit owner=null;
+ for( CompUnit cu : code._compunits.values() )
+ if( cu._cname!=null && (name.equals(cu._cname) || name.startsWith(cu._cname+".")) &&
+ (owner==null || cu._cname.length()>owner._cname.length()) ) owner=cu;
+ return owner;
+ }
+
+ // A final field whose declaration already denotes this exact constant is
+ // available as that value to every ideal Load. Materialize it in the object
+ // image instead of emitting the redundant initializer Store. This lowering
+ // leaves serialized ideal IR intact, so imports retain their memory edges.
+ static boolean preinitialized(CodeGen code,StoreNode st) {
+ if( !(st.ptr()._type instanceof TypeMemPtr ptr) || !ptr._one || !isClass(ptr._obj) ) return false;
+ CompUnit owner=owner(code,ptr._obj);
+ if( owner==null || owner._src==null ) return false;
+ if( !(Parser.TYPES.get(ptr._obj._name) instanceof TypeStruct ts) ) return false;
+ Field fld=ts.field(st._name);
+ if( fld==null || !fld._final || !fld._t.isConstant() || !st.val()._type.isConstant() ) return false;
+ Type value=st.val()._type, declared=fld._t;
+ if( declared instanceof TypeFunPtr fp && value instanceof TypeFunPtr vp ) return fp.fidx()==vp.fidx();
+ if( declared instanceof TypeMemPtr ptr0 && value instanceof TypeMemPtr ptr1 )
+ return pointerConstant(ptr0) && ptr0._obj==ptr1._obj;
+ return declared==value;
+ }
+
+ ObjectData object(Type t) {
+ Object key=isClass(t) ? ((TypeStruct)t)._name
+ : t instanceof TypeFunPtr fp ? functionSymbol(fp) : t;
+ ObjectData old=objects.get(key);
+ if( old!=null ) return old;
+ boolean clz=isClass(t);
+ if( clz && Parser.TYPES.get(((TypeStruct)t)._name) instanceof TypeStruct canonical ) t=canonical;
+ CompUnit owner=clz ? owner(enc._code,(TypeStruct)t)
+ : t instanceof TypeFunPtr fp ? owner(enc._code,enc._code._fidxs.owner(fp.fidx())) : null;
+ String symbol=clz ? ((TypeStruct)t)._name.substring(Parser.CLZ.length())+".$class"
+ : t instanceof TypeFunPtr fp ? functionSymbol(fp) : prefix+objects.size();
+ ObjectData obj=new ObjectData(t,symbol,!clz && (!(t instanceof TypeStruct ts) || ts.isConstant()),
+ owner!=null && owner._src==null);
+ objects.put(key,obj);
+ // Register before following fields: class objects may refer to each other.
+ if( !obj.external && t instanceof TypeStruct ts )
+ for( Field f : ts._fields )
+ if( !f._extern && f._t.isConstant() ) target(f._t);
+ return obj;
+ }
+
+ private static boolean pointerConstant(TypeMemPtr ptr) {
+ return ptr.notNull() && (isClass(ptr._obj) || (ptr._obj.isConstant() && (!ptr._obj.isAry() || ptr._obj.isConAry())));
+ }
+
+ private void target(Type t) {
+ if( t instanceof TypeMemPtr ptr && pointerConstant(ptr) ) object(ptr._obj);
+ if( t instanceof TypeFunPtr fp && fp.notNull() &&
+ enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx()) )
+ // A dead function still needs a unique non-null identity for comparisons.
+ object(fp);
+ }
+
+ void write() {
+ ArrayList<Encoding.Relo> refs=new ArrayList<>(enc._bigCons.values());
+ refs.sort(Comparator.comparingInt(r -> r._op._nid));
+ for( Encoding.Relo ref : refs ) object(ref._t);
+ // Definitions belong to their compilation unit, even if every local use folded.
+ ArrayList<CompUnit> units=new ArrayList<>(enc._code._compunits.values());
+ units.sort(Comparator.comparing(cu -> cu._cname==null ? "" : cu._cname));
+ for( CompUnit cu : units ) if( cu._src!=null && cu._clz!=null ) object(cu._clz);
+ for( ObjectData obj : entries() ) {
+ if( obj.external ) continue;
+ BAOS bits=obj.readOnly ? enc._cpool : enc._sdata;
+ Encoding.padN(1<<obj.alignment,bits);
+ obj.offset=bits.size();
+ if( obj.type instanceof TypeStruct ts ) writeStruct(obj,ts,bits);
+ else Encoding.addN(obj.alignment,obj.type,bits);
+ while( bits.size()<obj.offset+obj.size ) bits.write(0);
+ }
+ for( Encoding.Relo ref : refs ) {
+ ObjectData obj=object(ref._t);
+ ref._target=obj.offset;
+ ref._opStart=enc.opStart(ref._op);
+ }
+ }
+
+ private void writeStruct(ObjectData obj, TypeStruct ts, BAOS bits) {
+ int[] layout=ts.layout();
+ for( int idx : layout ) {
+ if( idx==ts._fields.length ) continue;
+ Field f=ts._fields[idx];
+ if( f._extern ) continue;
+ int off=ts.offset(idx);
+ while( bits.size()<obj.offset+off ) bits.write(0);
+ if( f._fname.equals("[]") ) ((TypeConAry)f._t).write(bits);
+ else {
+ Type value=f._t.isConstant() ? f._t : f._t.makeZero();
+ int log=f._t.log_size();
+ Encoding.addN(log,value,bits);
+ if( (value instanceof TypeFunPtr fp && fp.notNull()) ||
+ (value instanceof TypeMemPtr ptr && pointerConstant(ptr)) )
+ relocations.add(new Relocation(obj,off,log,value));
+ }
+ }
+ while( bits.size()<obj.offset+ts.size() ) bits.write(0);
+ }
+
+ String functionSymbol(TypeFunPtr fp) {
+ String ext=enc._code._externFunc.get(fp.fidx());
+ return ext!=null ? ext : enc._code._fidxs.symbol(fp.fidx());
+ }
+
+ String targetSymbol(Type value) {
+ if( value instanceof TypeMemPtr ptr ) return object(ptr._obj).symbol;
+ TypeFunPtr fp=(TypeFunPtr)value;
+ return enc._code.link(fp)==null && !enc._code._externFunc.containsKey(fp.fidx())
+ ? object(fp).symbol : functionSymbol(fp);
+ }
+
+ void link(int cpool, int sdata) {
+ for( Relocation rel : relocations ) {
+ long address;
+ if( rel.value instanceof TypeMemPtr ptr ) address=address(object(ptr._obj),cpool,sdata);
+ else {
+ TypeFunPtr fp=(TypeFunPtr)rel.value;
+ String ext=enc._code._externFunc.get(fp.fidx());
+ FunNode fun=enc._code.link(fp);
+ if( ext!=null ) address=enc.externalAddress(ext);
+ else if( fun==null ) address=address(object(fp),cpool,sdata);
+ else {
+ if( !enc._code.owns(fun) ) throw new IllegalArgumentException("Unlinked function "+functionSymbol(fp));
+ address=enc.opStart(fun);
+ }
+ }
+ // Emulator native-call sentinels are signed 32-bit addresses.
+ if( rel.logSize==2 && (address<Integer.MIN_VALUE || address>0xffff_ffffL) )
+ throw new IllegalArgumentException("Function address outside 32 bits: "+address);
+ BAOS bits=rel.object.readOnly ? enc._cpool : enc._sdata;
+ int off=rel.object.offset+rel.offset;
+ for( int i=0; i<(1<<rel.logSize); i++ ) bits.buf()[off+i]=(byte)(address>>>(8*i));
+ }
+ }
+
+ int address(ObjectData obj,int cpool,int sdata) {
+ if( obj.external ) throw new IllegalArgumentException("Unlinked class object "+obj.symbol);
+ return obj.offset+(obj.readOnly ? cpool : sdata);
+ }
+}
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 2bbe1053..a29ba73b 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 )
@@ -29,12 +37,12 @@ public class AddNode extends ArithNode {
// Add of 0. We do not check for (0+x) because this will already
// canonicalize to (x+0)
- if( t2 == TypeInteger.ZERO && lhs._type instanceof TypeInteger )
+ if( t2 == TypeInteger.ZERO && lhs._type.isa(compute()) )
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 9957472b..f02007bc 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 long doOp( long x, long y ) { return x & y; }
@Override TypeInteger doOp(TypeInteger x, TypeInteger y) {
@@ -20,6 +20,7 @@ public class AndNode extends ArithNode {
public Node idealize() {
Node lhs = in(1);
Node rhs = in(2);
+
Type t1 = lhs._type;
Type t2 = rhs._type;
if( !(t1 instanceof TypeInteger && t2 instanceof TypeInteger t2i) )
@@ -28,7 +29,7 @@ public class AndNode extends ArithNode {
// And of -1. We do not check for (-1&x) because this will already
// canonicalize to (x&-1). We do not check for zero, because
// the compute() call will return a zero already.
- if( t2i.isConstant() && t2i.value()==-1 )
+ if( t2i.isConstant() && t2i.value()==-1 && forceInt(lhs) )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
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 dafba626..00000000
--- a/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-public abstract class ArithFNode extends Node {
- public ArithFNode(Node lhs, Node rhs) { super(null, lhs, rhs); }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.open().n(in(1)).p(glabel()).n(in(2)).close();
- }
-
- 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 Type.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 54290330..bc47ebfc 100644
--- a/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -3,15 +3,25 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
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.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);
+ }
abstract String op();
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -20,20 +30,106 @@ 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;
+ }
+
+ // Int-only ops, such as &, | and shifts, can collapse to one input. If
+ // that input, or the live arms of a Phi input, are still mode-polymorphic
+ // ops, settle them before returning the value so the replacement remains
+ // at least as narrow as the int-only op.
+ static boolean forceInt(Node n) {
+ if( n instanceof ModeNode mode && mode.mode()==0 ) {
+ mode.setMode((byte)1);
+ n.setType(n.compute());
+ }
+ return true;
+ }
+
+ // 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==null )
+ return null;
+ 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
+ // Generic arithmetic op math: high returns high; low returns low; 2
// constants fold; only 2 non-constants call specialized math.
@Override
public final Type compute() {
Type t1 = in(1)._type, t2 = in(2)._type;
- if( t1.isHigh() || t2.isHigh() )
- return Type.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;
+ if( t1==null || t2==null ) return Type.TOP;
+ if( t1.isHigh() || t2.isHigh() ) return Type.TOP;
+ if( t1==Type.BOTTOM || t2==Type.BOTTOM ) return Type.BOTTOM;
+ byte mode = _mode==0 ? mode(t1,t2) : _mode;
+ if( mode==0 )
+ return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
+ 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 ) {
@@ -46,36 +142,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;
}
@@ -85,14 +195,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 0d82c5b9..73e786e3 100644
--- a/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -3,21 +3,27 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
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.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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
return p.open().n(in(1)).p(op()).n(in(2)).close();
}
@@ -26,15 +32,25 @@ abstract public class BoolNode extends Node {
public TypeInteger compute() {
Type t1 = in(1)._type;
Type t2 = in(2)._type;
+ if( t1==null || t2==null )
+ return BOOL;
// Exactly equals?
if( t1.isHigh() || t2.isHigh() )
return (TypeInteger)BOOL.dual();
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() )
@@ -44,22 +60,41 @@ 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( t1==null || t2==null )
+ return null;
+ 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) )
@@ -75,76 +110,76 @@ 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 "<" ; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1._max < i2._min ) return TRUE;
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 "<="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1._max <= i2._min ) return TRUE;
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<" ; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE;
diff --git a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index 7d4f1fb8..82c4af10 100644
--- a/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -1,9 +1,15 @@
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};
@@ -15,23 +21,53 @@ public class BulkMemPhiNode extends PhiNode {
private static final BitSet EMPTY = new BitSet();
// Excluded Aliases; empty means NO exclusions.
- // Each split clones the exclusions; published sets are never mutated.
- public final BitSet _aliases;
+ // 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,TypeMem.BOT,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); }
- public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; }
+ @Override public Tag serialTag() { return Tag.BulkMemPhi; }
- @Override public String label() { return "BulkPhi"+_aliases; }
+ @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() {
@@ -51,7 +87,7 @@ public class BulkMemPhiNode extends PhiNode {
// "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,CodeGen.CODE.add(mmm.in(1)));
+ setDef(i,mmm.in(1));
return this;
}
@@ -63,8 +99,8 @@ public class BulkMemPhiNode extends PhiNode {
return slice(alias);
}
- for( int i=0; i<_outputs.size(); i++ ) {
- int alias = outputAlias(_outputs.get(i));
+ for( int i=0; i<nOuts(); i++ ) {
+ int alias = outputAlias(out(i));
if( alias!=0 )
return slice(alias);
}
@@ -89,19 +125,22 @@ public class BulkMemPhiNode extends PhiNode {
// True if the MemMerge has no precise slice still covered by this Phi.
private boolean canPeek(MemMergeNode mmm) {
- return mmm.in(1)!=null && missingAlias(mmm)==0;
+ 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 ProjNode proj -> 0;
- case ParmNode parm -> 0;
- case ConstantNode con -> 0;
- case MemMergeNode mmm -> missingAlias(mmm);
+ 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 new AssertionError("Unexpected memory input "+n);
+ default -> throw Utils.TODO("Should not reach here");
};
}
@@ -111,16 +150,18 @@ public class BulkMemPhiNode extends PhiNode {
// Normal graph-neighbor enqueueing flows from defs to uses, so retain
// an explicit forward dependency to revisit this Phi when the user
// sharpens.
- addDepForwards(use);
+ use = addDepForwards(use);
return switch(use) {
case ScopeNode scope -> 0;
- case MemMergeNode mmm -> missingAlias(mmm);
+ case ParmNode parm -> 0;
+ case MemMergeNode mmm -> missingAlias(mmm,true);
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 || use instanceof ParmNode
- : "Unexpected bulk-memory user "+use;
+ assert use instanceof ReturnNode || use instanceof CallNode
+ : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
yield 0;
}
};
@@ -142,14 +183,14 @@ public class BulkMemPhiNode extends PhiNode {
return 0;
}
- // Every explicit slot requests that alias, including a partial allocation
- // input whose slot still points to this bulk Phi.
- private int missingAlias(MemMergeNode mmm) {
+ // 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) &&
!_aliases.get(alias) )
- return alias;
+ { assert (!user || mmm.in(alias)!=this); return alias; }
return 0;
}
@@ -160,6 +201,7 @@ public class BulkMemPhiNode extends PhiNode {
// parallel precise Phi already exists, reuse it instead of building a
// duplicate for the same Region/alias pair.
Node mem = precisePhi(alias);
+ assert ((TypeMem)mem._type)._alias==alias;
BitSet aliases = ((BitSet)_aliases.clone());
aliases.set(alias);
@@ -174,8 +216,7 @@ public class BulkMemPhiNode extends PhiNode {
// Do not peephole bphi here, as BulkMemPhi can recursively start a
// second bulk rewrite before this one has finished. Let the worklist
// discover any further splits
- bphi.setType(TypeMem.BOT);
- Node bulk = bphi;
+ Node bulk = bphi.init();
CodeGen.CODE.add(bulk);
CodeGen.CODE.add(mem);
return aggregate(bulk,alias,mem);
@@ -185,11 +226,19 @@ public class BulkMemPhiNode extends PhiNode {
// 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(bulk);
+ MemMergeNode mmm = new MemMergeNode(null,bulk);
for( int old = _aliases.nextSetBit(0); old >= 0; old = _aliases.nextSetBit(old+1) )
{ assert 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.
+ TypeMem old = (TypeMem)_type;
+ TypeMem mem = (TypeMem) mmm._type;
+ mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz,
+ mem._final,old._escFs,old._escAs);
return mmm;
}
@@ -201,14 +250,14 @@ public class BulkMemPhiNode extends PhiNode {
mphi = new MemPhiNode("$"+alias,alias);
mphi.addDef(region());
// Due to cycles, must set before calling peephole
- mphi.setType(TypeMem.make(alias,Type.BOTTOM));
+ mphi.setType(TypeMem.BOT.makeFrom(alias));
for( int i=1; i<nIns(); i++ )
mphi.addDef(CodeGen.CODE.add(preciseInput(in(i),alias)));
return CodeGen.CODE.add(mphi);
}
MemPhiNode _findPhi(int alias) {
- for( Node use : region()._outputs )
+ for( Node use : region().outs() )
if( use instanceof MemPhiNode mphi && mphi._alias==alias )
return mphi;
return null;
diff --git a/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 6805c1ef..fe3382a6 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>
@@ -103,11 +101,78 @@ public abstract class CFGNode extends Node {
return lhs;
}
+ // Safe read of the control input of a data node.
+ public static CFGNode safeCFG(Node n) {
+ if( n==null ) return null;
+ if( !(n.in(0) instanceof CFGNode cfg) ) return null;
+ return cfg._safeCFG();
+ }
+ private CFGNode _safeCFG() {
+ return switch(this) {
+ case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
+ case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
+ case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
+ case ReturnNode ret when ret.fun()._folding -> ret.cfg0()._safeCFG();
+ default -> this;
+ };
+ }
+
+ // True when this node dominates `sub` in the current idom tree.
+ public boolean dominates(CFGNode sub, Node dep) {
+ for( CFGNode cfg=sub; cfg!=null; cfg=cfg.idom() )
+ if( cfg == this )
+ return true;
+ else if( dep != null ) dep.addDep(cfg);
+ return false;
+ }
+
+ public static CFGNode deepest(CFGNode a, CFGNode b) {
+ if( a == null ) return b;
+ if( b == null ) return a;
+ return b.idepth() > a.idepth() ? b : a;
+ }
+
+ public static CFGNode earlyCFG(Node n) {
+ return earlyCFG(n,new IdentityHashMap<>(),new BitSet());
+ }
+
+ public static CFGNode earlyCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
+ if( n == null ) return null;
+ CFGNode cached = cache.get(n);
+ if( cached != null ) return cached;
+ if( active.get(n._nid) )
+ return safeCFG(n);
+ active.set(n._nid);
+ CFGNode early = null;
+ for( Node x : n._inputs )
+ early = deepest(early,defCFG(x,cache,active));
+ active.clear(n._nid);
+ cache.put(n,early);
+ return early;
+ }
+
+ private static CFGNode defCFG(Node n, IdentityHashMap<Node,CFGNode> cache, BitSet active) {
+ if( n == null || n.isConst() ) return null; // no impact on early cfg
+ return switch( n ) {
+ case PhiNode phi -> phi.region();
+ case CFGNode cfg -> cfg;
+ case ProjNode projNode -> safeCFG( n );
+ default -> earlyCFG( n, cache, active );
+ };
+ }
+
+ public boolean sameFun(CFGNode that) {
+ FunNode fun = fun();
+ return fun != null && fun == that.fun();
+ }
+
// 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;
}
@@ -118,13 +183,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;
}
}
@@ -133,23 +198,29 @@ 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 contains the function bodies at depth 0; real loops add depth.
- 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 = start.cfg(1)._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++;
@@ -163,8 +234,12 @@ public abstract class CFGNode extends Node {
}
// 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 );
+ 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 );
+ }
// Post-order work: find innermost loop
LoopTree inner = null, ltree;
@@ -207,7 +282,7 @@ public abstract class CFGNode extends Node {
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 ||
@@ -215,8 +290,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 c62de840..12fde6c0 100644
--- a/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -2,13 +2,16 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-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;
-public class CProjNode extends CFGNode {
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class CProjNode extends CFGNode implements Proj {
// Which slice of the incoming multipart value
public int _idx;
@@ -22,6 +25,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; }
@@ -41,13 +52,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
@@ -66,4 +77,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 115f70c6..35180fea 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -2,9 +2,13 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-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.HashMap;
+import java.util.IdentityHashMap;
/**
* CallEnd
@@ -12,17 +16,48 @@ import com.seaofnodes.simple.type.*;
public class CallEndNode extends CFGNode implements MultiNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- private boolean _folding;
- public boolean folding() { return _folding; }
+ boolean _folding;
+ // +2 must clone, so await all cleanup but OK
+ // +1 trivial now,
+ // 0: take a look,
+ // -1: somethings wrong, but might improve
+ // -2: never and stop asking,
+ private byte _inline;
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); }
+ public boolean folding() { return _folding; }
@Override public CFGNode idom(Node dep) {
// Folding the idom is the one inlining Return
@@ -41,182 +76,234 @@ 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
- 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);
- }
- }
+ 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());
+ }
+
+ // 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 during Opto, assume call won't be called and thus won't return anything.
+ boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal();
+ Type tmem = opto ? TypeMem.TOP : TypeMem.BOT;
+ Type ret = opto ? Type.TOP : tfp.ret();
+ 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);
+ // A reachable call whose presently linked exits are all unreachable
+ // keeps a conservative continuation. In particular, do not let a
+ // transient no-return result tear down the caller during SCCP.
+ if( state.ctl()==Type.XCONTROL &&
+ CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() )
+ return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP);
+ // At least as good as the TFP
+ return state.makeFrom(2,state.ret().join(tfp.ret()));
}
@Override
public Node idealize() {
+ return null;
+ }
- // Trivial inlining: call site calls a single function; single function
- // is only called by this call site.
- 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
- // Function is being called, and its not-null
- fptr._type instanceof TypeFunPtr tfp && tfp.notNull() &&
- // Arguments are correct
- call.err()==null ) {
- 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.
- // Dead control may end the walk before an enclosing function.
- CFGNode idom = call;
- while( idom!=null ) {
- 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 if( idom!=null ) {
- addDep(idom);
- }
- } else {
- addDep(fun);
- }
- } else { // Function ptr has multiple users (so maybe multiple call sites)
- addDep(fptr);
- }
- }
+ public byte maybeInline() {
+ byte inline = inlineCandidate();
+ if( CodeGen.CODE._midAssert )
+ return inline;
+ _inline = inline;
+ if( _inline > 0 )
+ doInline();
- return null;
+ return _inline;
}
- // Clone only tiny, straight-line boilerplate, after ordinary peeps settle.
- // Larger bodies still use the existing single-caller, non-cloning inline.
- public boolean inlineSmall() {
- if( isDead() || _folding || nIns()!=2 || !(in(0) instanceof CallNode call) ) return false;
- Node fptr = call.fptr();
- if( !(fptr instanceof ConstantNode) ||
- !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
- { addDep(fptr); return false; }
- if( call.err()!=null ) return false;
+ // Check if candidate right now, sets no flags
+ private byte inlineCandidate() {
+ if( _inline < -2 ) return -2; // Never
+ if( _folding ) return -2; // No, and will collapse anyways
+ if( !(in(0) instanceof CallNode call) ) return -2; // malformed because mid-death
+
+ if( nIns()!=2 ) return -1; // Not exactly 1 link, but can change
+
+ // Have exactly 1 linked function
ReturnNode ret = (ReturnNode)in(1);
FunNode fun = ret.fun();
- if( fun._folding ) return false;
- int len = smallBody(fun);
- if( len==0 ) return false;
- // All callers consume the three normal return projections.
- for( Node use : _outputs )
- if( !((use instanceof ProjNode p && p._idx>=0 && p._idx<3) ||
- (use instanceof CProjNode cp && cp._idx==0)) ) return false;
-
- // Substitute control and arguments directly; no new function identity,
- // parameter Phis, or return address is needed for this straight line.
- Node[] copy = new Node[len];
- for( int i=0; i<len; i++ ) {
- Node n = TINYBODY[i];
- copy[i] = n==fun ? call.ctrl() :
- n instanceof ParmNode parm ? (parm._idx==0 ? null : call.arg(parm._idx)) :
- n==ret ? null : n.copyEmpty();
- }
- for( int i=0; i<len; i++ ) {
- Node n = TINYBODY[i];
- if( n instanceof StoreNode || n instanceof MemMergeNode ) {
- for( Node in : n._inputs ) copy[i].addDef(mapped(in,copy));
- CodeGen.CODE.add(copy[i]);
- }
- }
- Node[] result = new Node[3];
- for( int i=0; i<3; i++ ) result[i] = mapped(ret.in(i),copy).keep();
- keep();
- call.unlink_all();
- CodeGen.CODE.add(fun);
- CodeGen.CODE.addAll(fun._outputs);
- // subsume kills each projection; retain CallEnd until all are replaced.
- while( nOuts()>1 ) {
- Node use = out(0)==null ? out(1) : out(0);
- int idx = use instanceof ProjNode p ? p._idx : ((CProjNode)use)._idx;
- CodeGen.CODE.addAll(use._outputs);
- use.subsume(result[idx]);
- }
- unkill();
- for( Node n : result ) n.unkill();
- CodeGen.CODE.invalidateIDepthCaches();
- return true;
- }
- private static Node mapped(Node n, Node[] copy) {
- for( int i=0; i<copy.length; i++ )
- if( TINYBODY[i]==n ) return copy[i];
- return n; // Constants outside the body are shared.
+ // Heuristic forced inlining off via name
+ if( fun._name != null &&
+ fun._name.endsWith("_noInline") )
+ return -2; // Never, stop asking
+
+ // 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; } // No, but can lose the recursive edge
+
+ // Things that might improve, so not now a candidate
+ Node fptr = call.fptr();
+ // Constant Function is being called, and its not-null
+ if( !(fptr._type instanceof TypeFunPtr tfp && tfp.notNull() && tfp.isConstant()) )
+ { addDep(fptr); return -1; }
+
+ // Arguments are correct
+ if( call.nargs()!=tfp.nargs() || !goodArgs(call,tfp) )
+ { addDep(fptr); return -1; }
+
+ // Encouraged inlining because small size and constructor.
+ int maxSize = fun.inlineLimit();
+ // A large parse can leave only a tiny initializer. Recognize that
+ // without repeatedly walking the whole body while it shrinks.
+ if( fun._approxUIDs >= maxSize ) {
+ if( !smallBody(fun) ) return -1;
+ } else if( fun.body().cardinality() >= maxSize )
+ return -1; // Try later (if cleanup makes the real body small enough)
+
+ // Can be cloned but not trivial
+ if( fun.nIns() > 2 ) { addDep(fun); return 2; }
+
+ // A candidate right now
+ return 1;
}
- // Bounded recognition of initializer boilerplate. Walk uses from the entry
- // and stop at Return, never following callers or constant inputs. This also
- // avoids scanning sparse MemMerge input arrays while a large body shrinks.
- // Watch this small prefix, including an unsupported node that may fold.
+ // Bounded, conservative recognition of straight-line initializer boilerplate.
+ // Walk uses from the entry and stop at Return, never following callers or
+ // constant inputs. This also avoids scanning sparse MemMerge input arrays.
+ // Watch only this small prefix, including an unsupported node that may fold.
private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
- private int smallBody(FunNode fun) {
+ private boolean smallBody(FunNode fun) {
ReturnNode ret = fun.ret();
for( Node in : ret._inputs )
if( in!=null ) addDep(in);
- if( ret.ctrl()!=fun ) return 0;
+ if( ret.ctrl()!=fun ) return false;
TINYBODY[0] = fun;
int len=1;
for( int i=0; i<len; i++ ) {
Node n = TINYBODY[i];
addDep(n);
if( n==ret ) continue;
- if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return false;
if( n!=fun && !(n instanceof ParmNode) &&
!(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
- return 0;
- if( n.nOuts()>TINYBODY.length ) return 0;
+ return false; // Something unexpected
+ if( n.nOuts()>TINYBODY.length ) return false; // Too big
for( Node use : n._outputs ) {
if( use==null ) continue;
int j=0;
while( j<len && TINYBODY[j]!=use ) j++;
- if( j<len ) continue;
- if( len==TINYBODY.length ) { addDep(use); return 0; }
- TINYBODY[len++] = use;
+ if( j<len ) continue; // Already in TINYBODY
+ if( len==TINYBODY.length ) { addDep(use); return false; } // Too big
+ TINYBODY[len++] = use; // Add to worklist
}
}
- return len;
+ return true;
+ }
+
+ public void doInline() {
+ assert _inline > 0; // Only called when possible to inline
+
+ ReturnNode ret = (ReturnNode)in(1);
+ FunNode fun = ret.fun();
+ CallNode call = call();
+ assert fun.sig().isa(call.fptr()._type);
+
+ // Clone first
+ if( _inline==2 ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // 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;
+ }
+
+ // Trivial inlining: call site calls a single function; single function
+ // is only called by this call site.
+ doTrivialInlining(fun);
+ }
+
+ 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) || !arg._type.accessISA(formal) )
+ { addDep(arg); return false; }
+ }
+ return true;
+ }
+
+
+ // Do trivial inlining. Inlining does not need to clone code, merely
+ // triggers folding the Call/Fun and Return/CallEnd away.
+ private boolean doTrivialInlining( FunNode fun ) {
+ assert !CodeGen.CODE._midAssert; // Triggered inlining
+ // 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();
+ // More peeps
+ CodeGen.CODE.add(fun);
+ CodeGen.CODE.add(this);
+ CodeGen.CODE.addAll(_outputs);
+ CodeGen.CODE.addAll(_inputs);
+ return true; // Always return true
}
+
@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 e63b3f62..4372e2d5 100644
--- a/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -2,12 +2,17 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-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.TypeRPC;
+import com.seaofnodes.simple.type.XInt;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Call
@@ -19,8 +24,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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
String fname = name();
@@ -97,14 +103,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);
}
@@ -125,35 +128,58 @@ public class CallNode extends CFGNode {
// Link so this calls fun
public Node link( FunNode fun ) {
assert !linked(fun);
- fun.addDef(this);
+ CodeGen.CODE.add(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));
+ if( use instanceof ParmNode parm ) {
+ Node def = parm._idx==0 ? ConstantNode.seed(cend()._rpc).peephole() : arg(parm._idx);
+ CodeGen.CODE.add(parm).addDef(def);
+ // Linking can happen during post-SCCP inlining. Keep an RPC
+ // Parm's cached finite set synchronized with its newly added
+ // input instead of waiting for the next worklist visit.
+ if( parm._idx==0 && parm._type instanceof TypeRPC )
+ parm._type = parm._type.meet(def._type);
+ }
// 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();
+ if( cend != null ) {
+ ret.keep();
+ cend.delDef(cend._inputs.find(ret));
+ CodeGen.CODE.add(cend);
+ ret.unkeep();
+ }
+ assert !linked(fun);
+ }
+
+
// 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();
+ return Parser.error("Expected a function, but found " + fptr()._type, _loc);
if( !tfp.notNull() )
return Parser.error( "Might be null calling "+tfp, _loc);
if( nargs() != tfp.nargs() )
@@ -162,11 +188,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)) || !arg(i+2)._type.accessISA(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 4e93f3e6..00000000
--- a/src/main/java/com/seaofnodes/simple/node/CastNode.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-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;
-
-// 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 boolean isConst() { return true; }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p(label()).n(in(1));
- }
-
- @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)._type.accessISA(_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/ConFldOffNode.java b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
index afdd29be..f94fa484 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -2,11 +2,14 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
+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.HashMap;
/**
* A field offset. This becomes a constant *after* other optimizations which
@@ -15,27 +18,57 @@ import com.seaofnodes.simple.util.SB;
*/
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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
return p.p(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;
+ // " len" selects the terminal size slot, not a Field.
+ if( fldx < _ts._fields.length && _ts._fields[fldx]._extern )
+ return new ExternOffsetNode(_fname).peephole();
+ 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 7c330175..47a7bbf6 100644
--- a/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -5,7 +5,10 @@ import com.seaofnodes.print.ExprPrinter;
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.HashMap;
+import java.util.IdentityHashMap;
/**
* A Constant node represents a constant value.
@@ -17,29 +20,60 @@ import com.seaofnodes.simple.util.SB;
* 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( type instanceof TypeFunPtr tfp && tfp.isConstant() && tfp.notNull() ) {
- FunNode fun = CodeGen.CODE.link(tfp);
- if( fun!=null && !fun.isDead() ) return new FunPtrNode(tfp,fun.ret());
+ 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._link(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 protected String repeatName() {
return _con==null || _con.toString().length()<=32 ? null : uniqueName();
}
@Override public String label() { return "Con"; }
+ @Override public Node copy() { return raw(this); }
+
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -54,6 +88,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..b6c3b0c9
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -0,0 +1,101 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+
+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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label()).open().n(val()).close();
+ }
+
+ // 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) && src.accessISA(_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) && src.accessISA(_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());
+
+ // Convert to i64 first, then apply the destination's integer width.
+ if( src instanceof TypeFloat && _dst instanceof TypeInteger )
+ return new ConvertNode(_dst,new ToIntegerNode(val()).peephole());
+
+ // 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 cf630ad4..62140873 100644
--- a/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -8,6 +8,7 @@ import com.seaofnodes.simple.type.Type;
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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("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 7b5ae16c..f173f0ef 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;
}
@@ -18,11 +22,19 @@ public class DivNode extends ArithNode {
@Override
public Node idealize() {
// Div of 1.
- if( in(2)._type == TypeInteger.TRUE && in(1)._type instanceof TypeInteger )
+ if( in(2)._type == TypeInteger.TRUE && in(1)._type.isa(compute()) )
return in(1);
+
+ // Div of constant
+ if( _mode==2 ) {
+ if( in(2)._type == TypeFloat.constant(1.) && in(1)._type.isa(compute()) )
+ 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..3e95fffb
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
@@ -0,0 +1,116 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.*;
+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
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("Esc#").p(fld()._alias).p(" {");
+ if( self()==null ) p.p("---");
+ else p.n(self()).p(",");
+ p.n(priv()).p("}, ");
+ p.n(pub ());
+ return p;
+ }
+
+ @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 the declared field type
+ 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() )
+ if( pub()._type.isa(_type) ) return pub();
+ else addDep(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 ccbcfc66..978298fc 100644
--- a/src/main/java/com/seaofnodes/simple/node/ExternNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -1,21 +1,38 @@
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 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/ExternOffsetNode.java b/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
new file mode 100644
index 00000000..409ce5d1
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+// A field address supplied by the C linker, not a displacement from the base.
+// The class base remains available for ideal type/alias analysis. Instruction
+// selection replaces it with this symbol's address and a zero displacement.
+public class ExternOffsetNode extends ExternNode {
+ public ExternOffsetNode(String name) { super(TypeInteger.BOT,name); }
+ @Override public Tag serialTag() { return Tag.ExternOffset; }
+ @Override public Node copy() { return new ExternOffsetNode(_extern); }
+ @Override public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node,Integer> nodes) {
+ baos.packed2(strs.get(_extern));
+ }
+}
diff --git a/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
index e7333c23..644f1434 100644
--- a/src/main/java/com/seaofnodes/simple/node/FRefNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -10,17 +10,32 @@ import com.seaofnodes.simple.type.Type;
* 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 ) { this(name,loc,FREF_TYPE); }
+ public FRefNode( String name, Parser.Lexer loc, Type type ) {
+ super(type);
+ _name = name;
+ _loc = loc;
+ }
- @Override public String label() { return "FRef"+_n; }
+ @Override public String label() { return "FRef"+_name; }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p("FRef_").p(_n);
+ return p.p("FRef_").p(_name);
}
@Override public Node idealize() {
@@ -28,8 +43,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..ef894929
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -0,0 +1,48 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+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.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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(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 70e1c200..b74f9e6b 100644
--- a/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FunNode.java
@@ -1,21 +1,21 @@
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.Arrays;
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;
public boolean folding() { return _folding; }
private TypeFunPtr _sig; // Initial signature
@@ -23,20 +23,43 @@ public class FunNode extends RegionNode {
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( 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.
@@ -48,42 +71,63 @@ 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.
public void setRet(ReturnNode ret) { _ret=ret; }
public ReturnNode ret() { assert _ret!=null; return _ret; }
- // Direct calls are known; any other use of an address may have unknown callers.
- public boolean hasPtr() {
- for( Node use : ret()._outputs )
- if( use instanceof FunPtrNode )
- for( Node user : use._outputs )
- if( !(user instanceof CallNode call) || call.fptr()!=use ||
- call._inputs.find(use)!=call.nIns()-1 ) return true;
- return false;
- }
-
// Signature can improve over time
public TypeFunPtr sig() { return _sig; }
public void setSig( TypeFunPtr sig ) {
- assert sig.isa(_sig);
+ assert sig.isa(_sig) || resolvesForwardSelf(sig,_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);
+ unlock();
_sig = sig;
}
}
+ // A class initializer starts parsing with an open singleton receiver and
+ // closes that same receiver after discovering its fields. Function
+ // arguments are contravariant, so ordinary isa does not describe this
+ // parser-time forward-reference resolution even though every other part
+ // of the signature sharpens normally.
+ private static boolean resolvesForwardSelf(TypeFunPtr sig, TypeFunPtr old) {
+ if( !(sig.arg(0) instanceof TypeMemPtr now) ||
+ !(old.arg(0) instanceof TypeMemPtr prior) ||
+ now._obj._name != prior._obj._name ||
+ !prior._obj._open || now._obj._open )
+ return false;
+ return sig.makeFrom(old.arg(0),0).isa(old);
+ }
+
+ @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
@@ -92,10 +136,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);
}
@@ -105,28 +149,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
@@ -145,22 +198,60 @@ 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() : 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>"); }
+
+ // Allocation factories and instance initializers carry extra memory boilerplate.
+ public int inlineLimit() {
+ return isInstance() || (_name!=null && _name.endsWith(".<new>")) ? 200 : 100;
+ }
- // Add a new function exit point.
- public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
+ // 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;
+ }
- // Build the function body
- public BitSet body() {
+ @Override public boolean inProgress() {
+ return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
+ }
+ // Build the function body set
+ public BitSet body() {
// Reverse up (stop to start) CFG only, collect bitmap.
BitSet cfgs = new BitSet();
cfgs.set(_nid);
@@ -170,7 +261,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;
}
@@ -184,23 +281,147 @@ 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;
}
- // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able)
+ // 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() < inlineLimit();
+ // 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;
+ BitSet body2 = new BitSet();
+ for( Node n : map.values() )
+ body2.set(n._nid);
+
+ // Remove non-body callers. Body-local call edges were cloned along
+ // with their CallEnds and must stay linked so their cloned types remain
+ // monotonic during the post-Opto Iter.
+ for( int i=1; i<fun2.nIns(); i++ )
+ if( !(fun2.in(i) instanceof CallNode call) || !body2.get(call._nid) )
+ fun2.removeDeadPath(i--);
+
+ // Linking adds graph edges and worklist entries; identity-map order
+ // would make inlining depend on the host JVM's object hashes.
+ Node[] olds = map.keySet().toArray(new Node[0]);
+ Arrays.sort(olds, (x,y) -> Integer.compare(x._nid,y._nid));
+ for( Node old : olds )
+ if( old instanceof CallNode oldCall )
+ relinkClonedCall(oldCall,(CallNode)map.get(oldCall),map);
+
+ // 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;
+ }
+
+ private static void relinkClonedCall( CallNode oldCall, CallNode call, IdentityHashMap<Node,Node> map ) {
+ CallEndNode oldCend = oldCall.cend();
+ if( oldCend==null || oldCend._folding )
+ return;
+ for( int i=1; i<oldCend.nIns(); i++ ) {
+ ReturnNode oldRet = (ReturnNode)oldCend.in(i);
+ FunNode oldFun = oldRet.fun();
+ FunNode fun = (FunNode)map.get(oldFun);
+ if( fun==null ) fun = oldFun;
+ if( !call.linked(fun) )
+ call.link(fun);
+ }
+ CallEndNode cend = call.cend();
+ if( cend.nIns() > 1 )
+ cend._type = cend.compute();
+ }
+
+ // 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);
+ for( Node e : n._inputs ) {
+ Node x = e;
+ if( e != null ) {
+ if( n instanceof CallEndNode cend && e != cend.call() &&
+ (!(e instanceof ReturnNode ret) || map.get(ret.fun()) == null) )
+ continue;
+ x = map.get(e);
+ if( x == null ) {
+ if( n instanceof CallEndNode cend && e != cend.call() )
+ continue;
+ x = e;
+ }
+ }
+ m.addDef(x);
+ }
+ for( Node x : n._outputs )
+ bodyEdge(visit,body,map,x);
+ }
+
+
+ // no 2 FunNodes are ever GVN'able
@Override public boolean eq( Node n ) {
- return _sig == ((FunNode)n)._sig;
+ return this==n;
}
@Override public int hash() {
@@ -254,4 +475,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
index 43242c32..82053fd8 100644
--- a/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -1,12 +1,68 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.type.TypeFunPtr;
+import com.seaofnodes.print.ExprPrinter;
-// A function address keeps its body alive, even without a linked Call.
-public class FunPtrNode extends ConstantNode {
- public FunPtrNode(TypeFunPtr tfp, ReturnNode ret) {
- super(tfp);
- addDef(ret);
+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;
+
+// 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 Node copy() {
+ FunPtrNode fptr = new FunPtrNode((TypeFunPtr)_con,null,null);
+ fptr._type = _type;
+ return fptr;
+ }
+
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ FunNode fun = CodeGen.CODE._link(((TypeFunPtr)_con).fidx());
+ return fun!=null && fun._name!=null
+ ? p.p("{ ").p(fun._name).p("}")
+ : p.p(_con);
+ }
+
+ @Override public boolean isConst() { return true; }
+ @Override public Type compute() {
+ ReturnNode ret = ret();
+ if( ret == null ) return _con;
+ if( !(ret._type instanceof TypeTuple tt) ) return ret._type.oob();
+ TypeFunPtr tfp = ret.fun().sig();
+ // Both the sig and the return value must be true, so JOIN both
+ Type tret = tfp._ret.join(tt.ret());
+ return tfp.makeFrom(tret);
+ }
+ @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..0ce504d9
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/GuardNode.java
@@ -0,0 +1,68 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.print.ExprPrinter;
+
+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.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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ return p.p(label()).n(in(1));
+ }
+
+ @Override public Type compute() {
+ // Guard is a pinned, path-local fact. If its control path is not
+ // executable, the fact is not executable either, regardless of how the
+ // value input sharpens later.
+ Type c = in(0)._type;
+ if( c != Type.CONTROL && c != Type.BOTTOM )
+ return Type.TOP;
+ 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(0)._type != Type.CONTROL && in(0)._type != Type.BOTTOM )
+ return null;
+ 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 c96993e5..c83c81d0 100644
--- a/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -13,9 +13,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
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -46,7 +44,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;
@@ -59,15 +57,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 b3204cff..a557f97c 100644
--- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -2,9 +2,11 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.codegen.CodeGen;
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;
@@ -22,9 +24,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; }
@Override boolean canDrop(MemOpNode other, Node dep) {
return super.canDrop(other,dep) && !clobbered(dep);
@@ -40,7 +47,7 @@ public class LoadNode extends MemOpNode {
dep.addDepForwards(use);
if( use instanceof StoreNode || use instanceof PhiNode ||
use instanceof MemMergeNode ||
- use instanceof NewNode || use instanceof CallNode ) return true;
+ use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ) return true;
}
return false;
}
@@ -52,58 +59,102 @@ 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 = mem()._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( mem._t == Type.TOP )
+ return TypeScalar.TOP;
+ if( ptr0 == Type.NIL )
+ return Type.BOTTOM; // Load from nil is error
+ if( ptr0 == Type.BOTTOM )
+ return Type.BOTTOM; // Error in is error out
+ if( !(ptr0 instanceof TypeMemPtr tmp) )
+ return scalar(mem._t);
+ if( tmp.nullable() )
+ return Type.BOTTOM; // Load from nil-able is error
+
// Load field from object
- Field f = tmp._obj.field(_name);
+ 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;
+ boolean readonly = t instanceof TypeMemPtr view && view._ro;
// 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(MemMergeNode.contents(mem(),_alias,this));
- if( ptr()._type.isFinal() || _declaredType.isFinal() )
- t = t.makeRO(); // Deep final applied
- // Pinch between declared type
- t = t.join(_declaredType);
- return t;
+ if( mem._alias==_alias )
+ t = t.join(mem._t);
+
+ // 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() || readonly )
+ 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() {
- if( mem() instanceof CastNode cast ) {
- setDef(1,cast.in(1));
+ Node mem = mem();
+ Node ptr = ptr();
+
+ // Null checks are handled by types. Array elements retain control for
+ // range checks, but reading an array's length has no index to check.
+ if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp &&
+ (!tmp._obj.isAry() || _name.equals("#")) ) {
+ setDef(0,null);
return this;
}
- if( mem() instanceof MemMergeNode merge ) {
- setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
+
+ // 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;
+ assert !tmp._obj._open && !tmp._obj._fref;
+ unlock(); // Alias participates in GVN semantics
+ _alias = fld._alias;
return this;
}
- Node ptr = ptr();
- Node mem = mem();
+ // Must sharpen alias first
+ if( _alias == 1 )
+ return null;
- Node win = find(mem,ptr,null,null);
- if( win!=null ) return folded(win);
+ if( mem instanceof MemMergeNode merge ) {
+ setDef(1,merge.alias(_alias));
+ // In support of uplifting control to a prior dominating load, if
+ // we move our memory, recheck other loads to see if they can hoist.
+ for( Node use : in(1).outs() )
+ if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() )
+ CodeGen.CODE.add(ld);
+
+ return this;
+ }
// Uplift control to a prior dominating load.
for( Node memuse : mem._outputs )
// Find a prior load, has same mem,ptr,off but higher ctrl
if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() &&
- cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier
+ cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() && // Higher control means load is legal earlier
+ addDep(ld)._type.isa(_type) ) // and not rolling backwards
return ld;
+ Node win = find(mem,ptr,null,null);
+ if( win!=null ) return folded(win);
if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
// Push a Load up through a Phi, as long as it collapses on at least
@@ -127,13 +178,17 @@ public class LoadNode extends MemOpNode {
!(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null;
if( find(memphi.in(2),ptr,stop,new BitSet())!=null ||
(!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) {
- PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep();
- phi.setType(_type); // Backedge searches may return this value Phi.
+ // Integer loop Phis widen to i64. Keep the load's declared
+ // width at the result, as for ordinary typed loop variables.
+ if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null;
+ PhiNode phi = new PhiNode(_name,memphi.region()).keep();
+ phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type);
for( int i=1; i<memphi.nIns(); i++ ) {
Node p = ptr instanceof PhiNode pp && pp.region()==memphi.region() ? pp.in(i) : ptr;
phi.addDef(load(memphi.in(i),p,stop,phi));
}
- return CodeGen.CODE.add(phi.unkeep());
+ CodeGen.CODE.add(phi.unkeep());
+ return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi;
}
}
@@ -142,8 +197,7 @@ public class LoadNode extends MemOpNode {
// 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();
+ Type zero = declaredType().makeZero();
assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
@@ -153,25 +207,39 @@ public class LoadNode extends MemOpNode {
// Register dependencies so a later pointer/offset rewrite retries the query.
private Node find(Node mem, Node ptr, Node stop, BitSet visit) {
if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1);
+ Field fld;
+ boolean external = ptr._type instanceof TypeMemPtr tmp &&
+ (fld=tmp._obj.field(_name))!=null && fld._extern;
while( mem!=null ) {
addDep(mem);
if( mem==stop ) return mem;
switch( mem ) {
case MemMergeNode merge: mem=merge.alias(_alias); break;
- case CastNode cast: mem=cast.in(1); break;
case StoreNode st:
+ // Alias 1 is still unresolved, not a proven different field.
+ if( st._alias==1 ) return null;
addDep(st.ptr());
- if( _alias==st._alias && ptr==st.ptr() && off()==st.off() ) return st;
- if( _alias==st._alias && !neverAlias(ptr,st.ptr()) &&
+ if( _alias==st._alias && (ptr==st.ptr() || ptr==st.nnptr()) && off()==st.off() ) {
+ // A full-width load cannot narrow the forwarded value.
+ if( declaredType() instanceof TypeInteger ti && ti.log_size()==3 &&
+ !addDep(st.val())._type.isa(_type) ) return null;
+ return st;
+ }
+ if( (external || _alias==st._alias) && (external || !neverAlias(ptr,st.ptr())) &&
!addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null;
mem=st.mem();
break;
case ProjNode proj:
- if( !(proj.in(0) instanceof NewNode nnn) ) return null;
- if( ptr instanceof ProjNode p && p.in(0)==nnn ) return nnn;
- if( !(ptr instanceof ProjNode p && p.in(0) instanceof NewNode) ) return null;
- mem=nnn.mem(); // Distinct allocations cannot overlap.
- break;
+ // New's memory is private; public memory is joined at Escape.
+ if( external || !(proj.in(0) instanceof NewNode nnn) ) return null;
+ if( declaredType()==Type.BOTTOM ) return null;
+ return nnn;
+ case EscapeNode esc:
+ if( external ) return null;
+ addDep(esc.self());
+ if( esc.self()==ptr ) { mem=esc.priv(); break; }
+ if( neverAlias(ptr,esc.self()) ) { mem=esc.pub(); break; }
+ return null;
case MemPhiNode phi:
addDep(phi.region());
// Only the original loop closes a successful cycle. Another
@@ -200,14 +268,14 @@ public class LoadNode extends MemOpNode {
if( win!=null ) {
if( win==stop ) return value;
if( win instanceof MemPhiNode mp ) {
- PhiNode phi=new PhiNode(_name,_declaredType,mp.region()).keep();
+ PhiNode phi=new PhiNode(_name,mp.region()).keep();
for( int i=1; i<mp.nIns(); i++ ) phi.addDef(load(mp.in(i),ptr,stop,value));
- return phi.unkeep().peephole();
+ return CodeGen.CODE.add(phi.unkeep().peephole());
}
- return folded(win).peephole();
+ Node fold=folded(win);
+ if( fold!=null ) return fold.peephole();
}
- LoadNode ld=new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr,off());
- ld.setDef(0,in(0));
+ LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off());
return ld.peephole();
}
@@ -239,7 +307,7 @@ public class LoadNode extends MemOpNode {
// Read-Only is a deep property, and cannot be cast-away
private Node castRO(Node rez) {
- if( (ptr()._type.isFinal() || _declaredType.isFinal()) && !rez._type.isFinal() )
+ if( (ptr()._type.isFinal() || declaredType().isFinal()) && !rez._type.isFinal() )
return new ReadOnlyNode(rez).peephole();
return rez;
}
@@ -247,16 +315,18 @@ 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( shift==0 ) {
+ 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());
}
-
}
diff --git a/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/src/main/java/com/seaofnodes/simple/node/LoopNode.java
index 1d493db7..df686bcf 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;
@@ -46,7 +45,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
@@ -56,12 +55,13 @@ 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();
- MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole());
- memout.setDef(0,f);
+ Node top = ConstantNode.make(Type.TOP).peephole();
+ Node memout = new MemMergeNode();
+ memout.addDef(f); // placeholder for control
+ memout.addDef(top);
for( Node u : _outputs )
- if( u instanceof BulkMemPhiNode ) memout.setDef(1,u);
- else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,phi);
+ 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();
@@ -73,7 +73,7 @@ public class LoopNode extends RegionNode {
// Nope, insert an aligned exit layer
RegionNode r = new RegionNode(_loc,null,ctrl).init();
ctrl = r; r._ltree = fun._ltree;
- mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init();
+ mem = new BulkMemPhiNode(r,mem).init();
expr = new PhiNode(r,expr).init();
}
// Append new Never exit
@@ -88,8 +88,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 fun;
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
index 9c0755b9..b25fcaaf 100644
--- a/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
@@ -3,7 +3,6 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.Utils;
import java.io.ByteArrayOutputStream;
// Generic machine-specific class, has a few Node implementations that have to
@@ -14,8 +13,8 @@ public abstract class MachConcreteNode extends Node implements MachNode {
public MachConcreteNode(Node[]nodes) { super(nodes); }
@Override public String label() { return op(); }
- @Override public Type compute () { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public Type compute () { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
p.open().p(op()).p(",");
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 7a329117..dfe8794d 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -4,99 +4,154 @@ import com.seaofnodes.print.ExprPrinter;
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.*;
-/** A default slice and explicit alias overrides. A null default means partial
- * memory: absent aliases are not covered, rather than having unknown contents.
+/**
+ * Memory Merge - a merge of many aliases into a "fat memory". All aliases
+ * are here, but most will be lazy - take the default fat memory.
*/
public class MemMergeNode extends Node {
- public MemMergeNode(MemMergeNode mem) { super(mem); }
- public MemMergeNode(Node bulk) { super(null,bulk); _type = TypeMem.BOT; }
- public MemMergeNode(Node bulk, int alias, Node precise) {
- this(bulk);
- alias(alias,precise);
- }
- public Node alias(int alias) {
- Node n = alias < nIns() ? in(alias) : null;
- assert n!=null || !(in(1) instanceof BulkMemPhiNode bulk) || !bulk.isSplit(alias);
- n = n==null ? in(1) : n;
- assert n!=null : "Alias not covered: "+alias;
- return n;
- }
+ public MemMergeNode( Node ...nodes) { super(nodes); _type = TypeMem.BOT; }
+ public MemMergeNode(MemMergeNode mem) { super(mem); }
+ @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(); mem.setDefX(bais.packed1()-1,null); return mem; }
- public void alias(int alias, Node mem) {
- assert alias > 1;
- while( nIns()<=alias ) addDef(null);
- setDef(alias,mem);
- }
+ @Override public String label() { return "ALLMEM"; }
+ @Override public boolean isMem() { return true; }
- // Scalar contents of one alias. New zeroes the allocated fields;
- // join it with the incoming contents for all previously allocated objects.
- // Phis use their cached types, so this query does not recurse around loops.
- static Type contents(Node mem, int alias, Node dep) {
- // A loop memory Phi can also be a user of the querying Store.
- // Its type changing still needs to revisit this query.
- dep.addDepForwards(mem);
- if( mem instanceof CastNode cast ) return contents(cast.in(1),alias,dep);
- if( mem instanceof MemMergeNode merge )
- return contents(merge.alias(alias),alias,dep);
- if( mem instanceof ProjNode proj && proj.in(0) instanceof NewNode nnn ) {
- assert proj._idx==1 && nnn.field(alias)!=null;
- dep.addDep(nnn);
- if( dep.addDep(nnn.in(0))._type.isHigh() ) return Type.TOP;
- return contents(nnn.mem(),alias,dep).meet(nnn.field(alias)._t.makeZero());
+ @Override
+ protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
+ p.p("MEM[ ");
+ for( int j=2; j<nIns(); j++ ) {
+ p.p(j);
+ p.p(":");
+ Node n = in(j);
+ while( n instanceof ScopeNode loop ) {
+ p.p("Lazy_");
+ n = loop.mem();
+ }
+ if( n==null ) p.p("___ ");
+ else p.n(n).p(" ");
}
- // Function parameters and call results may contain arbitrary heap values.
- // Their bulk memory types do not imply empty or zero-filled storage.
- if( mem._type==Type.TOP || mem._type==TypeMem.TOP ) return Type.TOP;
- if( mem._type instanceof TypeMem mt && mt._alias==alias ) return mt._t;
- return Type.BOTTOM;
+ return p.unchar(' ').p("]");
}
- @Override public String label() { return "MemMerge"; }
- @Override public boolean isMem() { return true; }
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- p.p("MEM[");
- for( int i=1; i<nIns(); i++ )
- if( in(i)!=null ) {
- p.p(i==1 ? "default:" : i+":");
- p.n(in(i)).p(" ");
+
+ @Override public Type compute() {
+ 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);
+ if( in !=null && in._type.isHigh() )
+ return TypeMem.TOP;
+ 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 ) { assert 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 p.p("]");
- }
+ return TypeMem.makePrivate(ts);
+ }
- @Override public Type compute() {
- for( Node n : _inputs )
- if( n != null && !n._type.isHigh() )
- return TypeMem.BOT;
- return TypeMem.TOP;
+ // Normal public memory; meet across escaped inputs
+ TypeMem mem = defmem;
+ for( int i=2; i<nIns(); i++ ) {
+ // Has this alias escaped?
+ if( XInt.bit(mem._escAs,i) &&
+ in(i) != null && !in(i)._type.isHigh() ) {
+ mem = (TypeMem)mem.meet(in(i)._type);
+ }
+ }
+ return mem;
}
@Override public Node idealize() {
+ assert nIns() != 0 && in(1)!=null; // Always have a default. 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 ) { setDef(i,cast.in(1)); progress=true; }
- if( in(i)!=null && in(i)==in(1) ) { setDef(i,null); progress=true; }
- if( in(i) instanceof MemMergeNode mem ) {
- setDef(i,CodeGen.CODE.add(mem.alias(i)));
- progress=true;
- }
- if( in(i)!=null ) allDefault=false;
+ if( in(1) == in(i) ) { setDef(i,null); progress=true; }
+ else { allDefault=false; }
}
- if( allDefault && in(1)!=null ) return in(1);
+ // If not merging any memory (all memory is just the default)
+ if( allDefault )
+ return in(1); // Become default memory
+
+ // Collapse stacked merged-mem
if( in(1) instanceof MemMergeNode mem ) {
- for( int i=2; i<mem.nIns(); i++ )
- if( mem.in(i)!=null && (i>=nIns() || in(i)==null) )
- alias(i,mem.in(i));
+ // A dead memory cycle can lose its last use when we bypass mem.
+ // Keep the peephole's result alive until the worklist sees it.
+ keep();
+ // Goal is to swap my default mem with mem's default mem
+ for( int i=2; i<mem.nIns(); i++ ) {
+ if( mem.in(i) != null ) {
+ // deeper default mem has a non-default
+ if( i>=nIns() || in(i)==null )
+ setDefX(i,mem.in(i));
+ }
+ }
CodeGen.CODE.add(mem.in(1));
setDef(1,mem.in(1));
- return this;
+ return unkeep();
}
- assert BulkMemPhiNode.checkMerge(this);
+
return progress ? this : null;
}
+
+
+ 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);
+ }
+
+ public Node alias( int alias, Node st ) { return setDefX(alias,st); }
+
+ // Now one-time do a useless-phi removal
+ void _useless( ) {
+ for( int i=1; i<nIns(); i++ ) {
+ if( in(i) instanceof PhiNode phi ) {
+ // Do an eager useless-phi removal
+ Node in = phi.peephole();
+ CodeGen.CODE.addAll(phi._outputs);
+ phi.moveDepsToWorklist();
+ if( in != phi ) {
+ if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
+ phi.subsume(in);
+ setDef(i,in); // Set the update back into Scope
+ }
+ }
+ }
+ }
+
+ @Override public boolean eq( Node n ) {
+ return this==n;
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index e7b2c792..365157d2 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -3,12 +3,16 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
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.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
/**
* Convenience common base for Load and Store.
@@ -19,93 +23,102 @@ import java.util.ArrayList;
* 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 decl, Node ctrl, Node mem, Node ptr, Node off) {
+ super(decl, 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 decl, Node ctrl, Node mem, Node ptr, Node off, Node value) {
+ this(loc, name, alias, isLoad, decl, 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;
}
- // 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;
+ 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);
}
- static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
- String mlabel() { return mlabel(_name); }
+ private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) {
+ int alias = bais.packed2();
+ return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias);
+ }
- @Override public boolean isMem() { return !_isLoad; }
+ @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 "[]"==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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return _printMach(p); }
protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { throw Utils.TODO(); }
- public int log_size() { return _declaredType.log_size(); }
+
// Extra conditions for factoring matching memory operations through a Phi.
// The caller has already checked the opcode, input shape and control.
boolean canDrop(MemOpNode other, Node dep) {
dep.addDep(ptr());
- return _alias==other._alias && _declaredType==other._declaredType && err()==null;
+ return _alias>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null;
}
@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() {
@@ -114,12 +127,20 @@ 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;
+
}
+
// Memory writers which must follow this read. A packaging node is not a
- // write, but can hide New's input, so follow this alias through aggregates.
+ // write; follow this alias through aggregates to its writers.
public ArrayList<Node> antiDeps() {
ArrayList<Node> deps = new ArrayList<>();
antiDeps(mem(),deps,new BitSet());
@@ -136,7 +157,7 @@ public abstract class MemOpNode extends Node {
antiDeps(merge,deps,visit);
} else if( use instanceof CallNode ||
(use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
- (use instanceof NewNode nnn && nnn.field(_alias)!=null) ||
+ (use instanceof EscapeNode esc && esc.fld()._alias==_alias) ||
(use instanceof MemPhiNode phi && phi._alias==_alias) ||
(use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
if( !deps.contains(use) ) deps.add(use);
diff --git a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
index 787333d9..cdfaaf97 100644
--- a/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -1,39 +1,64 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.type.TypeMem;
+import com.seaofnodes.simple.codegen.GlobalBits;
+import com.seaofnodes.simple.util.BAOS;
-/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */
+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,TypeMem.make(alias,Type.BOTTOM),inputs);
+ super(label, inputs);
assert alias > 1;
_alias = alias;
}
- public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._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);
+ }
- @Override public String label() { return "MemPhi_"+_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() {
+ @Override
+ public Type compute() {
+ Type t = super.compute();
assert BulkMemPhiNode.checkMem(this,_alias);
- if( !(region() instanceof RegionNode r) )
- return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type;
- if( r.inProgress() ) return r instanceof LoopNode ? _minType : TypeMem.BOT;
- Type t = Type.TOP;
- for( int i=1; i<nIns(); i++ )
- if( addDep(r.in(i))._type!=Type.XCONTROL && r.in(i)._type!=Type.TOP )
- t = t.meet(MemMergeNode.contents(in(i),_alias,this));
- return TypeMem.make(_alias,t);
+ 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() {
+ @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,CodeGen.CODE.add(mem.alias(_alias)));
+ setDef(i,mem.alias(_alias));
return this;
}
if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() &&
@@ -44,6 +69,9 @@ public class MemPhiNode extends PhiNode {
return super.idealize();
}
- @Override public boolean eq(Node n) { return _alias==((MemPhiNode)n)._alias && super.eq(n); }
+ @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 74439b8c..00000000
--- a/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package com.seaofnodes.simple.node;
-
-
-import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.type.TypeFloat;
-
-
-public class MinusFNode extends Node {
- public MinusFNode(Node in) { super(null, in); }
-
- @Override public String label() { return "MinusF"; }
-
- @Override protected String format() { return "(-%1)"; }
-
- @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 94c69785..e84eb611 100644
--- a/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -1,33 +1,81 @@
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.codegen.CodeGen;
+import com.seaofnodes.simple.type.*;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+
+public class MinusNode extends Node implements ModeNode {
+ // Mode is 0 for unknown, 1 for int, 2 for flt
+ byte _mode;
-public class MinusNode extends Node {
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 label() { return "Minus"; }
+ @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 protected String format() { return "(-%1)"; }
@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==null )
+ return Type.TOP;
+ if( t.isHigh() )
+ return _mode==0 ? Type.TOP :
+ _mode==1 ? TypeInteger.TOP :
+ t instanceof TypeFloat ? t : TypeFloat.F64.dual();
+ byte mode = _mode;
+ if( mode==0 ) {
+ if( t instanceof TypeFloat ) mode=2;
+ if( t instanceof TypeInteger ) mode=1;
+ }
+ if( mode==0 )
+ return CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal() ? Type.BOTTOM : Type.TOP;
+ 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
public Node idealize() {
// -(-x) is x
- if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type instanceof TypeInteger )
+ if( in(1) instanceof MinusNode minus && addDep(minus.in(1))._type.isa(compute()) )
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 6e1dc053..bb99cad5 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;
}
@@ -32,8 +35,8 @@ public class MulNode extends ArithNode {
// Mul of 1. We do not check for (1*x) because this will already
// canonicalize to (x*1)
long c = i.value();
- if( c==1 ) return lhs._type instanceof TypeInteger ? lhs : null;
- if( c==0 ) return Parser.ZERO;
+ if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize();
+ 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 66879b66..ba1dab8c 100644
--- a/src/main/java/com/seaofnodes/simple/node/NeverNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -11,7 +11,8 @@ import com.seaofnodes.simple.type.TypeTuple;
// "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 840d2759..5eedd0cd 100644
--- a/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -4,68 +4,65 @@ import com.seaofnodes.print.ExprPrinter;
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.HashMap;
+import java.util.IdentityHashMap;
-/** Allocate a zeroed object. Inputs {ctrl, $mem, size};
- * results {ptr, $mem}.
- * The memory input and result cover only the aliases in the allocated struct.
- */
+/**
+ * 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 Node ctrl() { return in(0); }
+ public Node size() { return in(1); }
- public NewNode(TypeMemPtr ptr, Node... nodes) {
- super(nodes);
- assert !ptr.nullable();
- _ptr = ptr;
- assert nodes.length==3;
- assert nodes[0]._type==Type.CONTROL || nodes[0]._type==Type.XCONTROL;
- assert nodes[1]._type instanceof TypeMem;
- assert nodes[2]._type instanceof TypeInteger || nodes[2]._type==Type.NIL;
+ // --- 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));
}
-
- public Node mem() { return in(1); }
- public Node size() { return in(2); }
-
- public Field field(int alias) {
- for( Field f : _ptr._obj._fields )
- if( !f._one && f._alias==alias ) return f;
- return null;
+ 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; }
-
+ @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); }
- @Override public String label() { return "new_"+(_ptr._obj.isAry() ? "ary_"+_ptr._obj._fields[1]._t.str() : _ptr._obj.str()); }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p("new ").p(_ptr._obj.str());
+ return p.p("new ").p(_ts.str());
}
-
- @Override
- public TypeTuple compute() {
- return in(0)._type.isHigh()
- ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP)
- : TypeTuple.make(_ptr,TypeMem.BOT);
+ @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));
}
- @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;
+ @Override public Node idealize() { return null; }
+
+ @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
+ Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts;
}
- @Override
- public boolean eq(Node n) { return this == n; }
+ // 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
- int hash() { return _ptr.hashCode(); }
+ @Override int hash() { return _ts.hashCode(); }
// ------------
// MachNode specifics, shared across all CPUs
@@ -74,30 +71,31 @@ 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);
_kills = kills;
}
public String op() { return "alloc"; }
- public RegMask regmap(int i) { return i==2 ? _arg3Mask : null; }
+ public RegMask regmap(int i) { return i==1 ? _arg3Mask : null; }
public RegMask outregmap() { return 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 414530b4..23a7e2c4 100644
--- a/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -4,13 +4,11 @@ import com.seaofnodes.print.BaseNode;
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.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;
@@ -70,8 +68,83 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
_hash = 0;
}
+ // Disk/serialized opcode tags
+ public enum Tag {
+ Add,And,EQ,NE,LT,LE,ULT,
+ CallEnd,Call,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,Sub,ToFloat,
+ XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt,ExternOffset,ToInteger;
+ 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 ToInteger-> new ToIntegerNode(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 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 ExternOffset-> new ExternOffsetNode(strs[bais.packed2()]);
+ 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 Region-> RegionNode.make(bais);
+
+ default -> throw Utils.TODO("Should not reach here");
+ };
+ }
+ };
+ 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(); }
public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
@@ -178,7 +251,8 @@ public abstract class Node extends BaseNode<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.
@@ -200,6 +274,12 @@ public abstract class Node extends BaseNode<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>
@@ -236,11 +316,13 @@ public abstract class Node extends BaseNode<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() {
@@ -260,7 +342,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
n.unlock();
int idx = n._inputs.find(this);
n._inputs.set(idx,nnn);
- n.moveDepsToWorklist(); // Rewiring can change a dependent query without changing type.
nnn.addUse(n);
CODE.add(n);
CODE.addAll(n._outputs);
@@ -331,13 +412,13 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
* Try to peephole at this node and return a better replacement Node.
* Always returns some not-null Node (often this).
*/
- public final Node peephole() {
- var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs;
+ public final Node peephole( ) {
+ var obs = CODE._midAssert ? null : CODE._obs;
if( obs != null ) obs.before(this);
Node n = peepholeOpt();
- Node rez = n == null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n);
- if( obs != null ) obs.after(this, n == null ? null : rez, false);
- return rez;
+ Node rez = n==null ? this : deadCodeElim(n._nid >= _nid ? n.peephole() : n);
+ if( obs != null ) obs.after(this, n==null ? null : rez, false);
+ return rez; // Cannot return null for no-progress
}
/**
@@ -363,11 +444,9 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
// Compute initial or improved Type
Type old = setType(compute());
- // Replace constant computations from non-constants with a constant
- // 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() )
+ // Replace constant computations from non-constants with a constant node.
+ if( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
+ _type.isHighOrConst() )
return ConstantNode.make(_type).peephole();
// Global Value Numbering
@@ -435,7 +514,8 @@ public abstract class Node extends BaseNode<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);
@@ -500,28 +580,33 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
// revisit them if `this` changes.
Ary<Node> _deps;
public int nDeps() { return _deps == null ? 0 : _deps.size(); }
- public Node dep(int idx) { return _deps.get(idx); }
+ public Node dep(int idx) { return _deps.at(idx); }
+
+ private boolean addDepImpl( Node dep ) {
+ // Running peepholes during the big assert cannot have side effects
+ // like adding dependencies.
+ if( CODE._midAssert ) return false;
+ if( CODE._obs != null ) CODE._obs.dep(this, dep);
+ 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 ) { return addDep(dep,false); }
-
- // Keep an explicit dependency when inspecting an immediate user's shape.
- <N extends Node> N addDepForwards(N dep) { return addDep(dep,true); }
+ 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
+ dep._deps.add(this);
+ return dep;
+ }
- private <N extends Node> N addDep(N dep, boolean forwards) {
- // Running peepholes during the big assert cannot have side effects
- // like adding dependencies.
- if( CODE._midAssert ) return dep;
- var obs = CodeGen.CODE._midAssert ? null : CodeGen.CODE._obs;
- if( obs != null ) obs.dep(this, dep);
- if( dep._deps==null ) dep._deps = new Ary<>(Node.class);
- if( dep._deps .find(this) != -1 ) return dep; // Already on list
- if( !forwards && dep._inputs .find(this) != -1 ) return dep; // No need for deps on immediate neighbors
- if( !forwards && dep._outputs.find(this) != -1 ) return dep;
+ public <N extends Node> N addDepForwards( N dep ) {
+ if( !addDepImpl(dep) ) return dep;
dep._deps.add(this);
return dep;
}
@@ -585,26 +670,6 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
/** Is this Node Memory related */
public boolean isMem() { 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
@@ -656,7 +721,7 @@ public abstract class Node extends BaseNode<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.
@@ -677,6 +742,20 @@ public abstract class Node extends BaseNode<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 775b069b..2328c287 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 com.seaofnodes.simple.util.Utils;
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 protected String format() { return "(!%1)"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/OrNode.java b/src/main/java/com/seaofnodes/simple/node/OrNode.java
index d16dd194..9d50d1a9 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 "|"; }
@@ -18,12 +19,13 @@ public class OrNode extends ArithNode {
public Node idealize() {
Node lhs = in(1);
Node rhs = in(2);
+
Type t1 = lhs._type;
Type t2 = rhs._type;
// Or of 0. We do not check for (0|x) because this will already
// canonicalize to (x|0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && t1.isa(TypeInteger.BOT) && forceInt(lhs) )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
diff --git a/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 95934e4b..899d7639 100644
--- a/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -2,28 +2,54 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-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.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 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
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- if( "main".equals(fun()._name) && _label.equals("arg") )
+ if( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
return p.p("arg");
p.p("Parm_").p(_label).open();
for( Node in : _inputs ) p.n(in).p(",");
@@ -35,20 +61,36 @@ public class ParmNode extends PhiNode {
@Override public boolean inProgress() { return in(0) instanceof FunNode fun && fun.inProgress(); }
@Override
+ public Type compute() {
+ if( !(region() instanceof RegionNode r) )
+ 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() || in(nIns()-1)==null )
+ return _declaredType;
+ // Set type to local top of the starting type
+ Type t = Type.TOP;
+ 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;
+ if( ctrl != Type.XCONTROL && ctrl != Type.TOP ) {
+ if( in(i)._type==Type.BOTTOM )
+ return Type.BOTTOM;
+ t = t.meet(in(i)._type);
+ }
+ }
+ return t.join(_declaredType);
+ }
+
+ @Override
public Node idealize() {
if( !(region() instanceof FunNode) )
- return in(1); // Input has collapse to e.g. starting control.
+ // A dead function can lose every caller before the Parm itself is
+ // removed; there is no replacement input in that transient form.
+ return nIns()==1 ? null : in(1); // Input has collapse to e.g. starting control.
// 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 6cfeecfe..0097c773 100644
--- a/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -3,41 +3,47 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
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.*;
+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);
}
-
- public static PhiNode make(String label, Type type, Node... inputs) {
- return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,type,inputs);
+ @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); }
@@ -52,23 +58,18 @@ public class PhiNode extends Node {
}
public CFGNode region() { return (CFGNode)in(0); }
- @Override public boolean isMem() { return _minType instanceof TypeMem; }
- boolean isRPC() { return false; }
+ @Override public boolean isMem() { return _type instanceof TypeMem; }
@Override
public Type compute() {
if( !(region() instanceof RegionNode r) )
- return region()._type==Type.XCONTROL || region()._type==Type.TOP ? (_type instanceof TypeMem ? TypeMem.TOP : Type.TOP) : _type;
+ return Type.TOP;
// During parsing Phis have to be computed type pessimistically.
if( r.inProgress() || in(nIns()-1)==null )
- // 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 Type.BOTTOM;
// 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;
@@ -78,91 +79,78 @@ public class PhiNode extends Node {
t = t.meet(in(i)._type);
}
}
- Type newt = t.join( _minType );
// Above-center ranges may still fall to unwidened constants.
// Widen only below-center ranges to preserve monotonicity.
- if( region() instanceof LoopNode && // Only around loops
- newt instanceof TypeInteger newi &&
- // Types changed and are falling (the optimistic case, expected to fall forever)
- newi != _type ) {
- if( !newi.isHigh() && !newi.isConstant() && (!(_type instanceof TypeInteger oldi) || newi._widen <= oldi._widen) )
- return newi.same_but_slightly_wider_than(_minType);
+ if( r instanceof LoopNode && // Only around loops
+ t instanceof TypeInteger ti && // Only widen integers
+ !ti.isHigh() && !ti.isConstant() ) { // Only widen live ranges
+ // Widen, to prevent infinite falling of TypeIntegers
+ return ti.same_but_slightly_wider_than();
}
- return newt;
+ 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._obj != tmp._obj ) {
- _minType = tmp.makeFrom(tmp2._obj);
- return this;
- }
- }
- if( r.inProgress() || r.nIns()<=1 )
+ RegionNode r = (RegionNode)region();
+ if( r.inProgress() || 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;
- // Let the bulk input split this alias before collapsing a precise Phi.
- if( live instanceof BulkMemPhiNode ) return null;
- // 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;
- // Phi(op(a,b,...),op(c,d,...)) -> op(Phi(a,c),Phi(b,d),...).
- Node down;
- if( same_op() && (down=drop_same_op())!=null ) return down;
+ // 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( 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 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.
- if( nIns()==3 ) {
- int nullx = -1;
- if( in(1)._type == in(1)._type.makeZero() ) nullx = 1;
- if( in(2)._type == in(2)._type.makeZero() ) nullx = 2;
- if( nullx != -1 ) {
- Node val = in(3-nullx);
- if( val instanceof CastNode cast )
- 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);
- while( idom != null && idom.nIns() > 0 && idom.in(0) != iff ) idom = idom.idom();
- if( idom instanceof CProjNode proj && proj._idx==1 )
- return val;
- } else if( ridom != null ) addDep(ridom);
- }
- }
+ // If merging Phi(ZERO, guardNZ(N)) at the matching `if(N)` join, the
+ // Phi is exactly N. The guard arm proves N is non-zero, and the other
+ // arm contributes N's zero value. This is only legal if N is already
+ // available at the Phi region; otherwise a return-scope/live-on-exit
+ // Phi can lose the zero arm and export a branch-local value upward.
+ Node unguard;
+ if( (unguard=matchingGuardMerge(r,1)) != null ) return unguard;
+ if( (unguard=matchingGuardMerge(r,2)) != null ) return unguard;
return null;
}
+ private Node matchingGuardMerge(RegionNode r, int nzIdx) {
+ Node zero = in(3-nzIdx);
+ if( !(in(nzIdx) instanceof GuardNode cast && cast._nonZero) ||
+ zero._type.makeZero()!=zero._type ||
+ cast.in(1)==this )
+ return null;
+ if( !(r.in(nzIdx) instanceof CProjNode nz && nz._idx==0 && nz.ctrl() instanceof IfNode iff) )
+ return null;
+ if( !(r.in(3-nzIdx) instanceof CProjNode z && z._idx==1 && z.ctrl()==iff) )
+ return null;
+ // Predicate was guarded?
+ Node n = cast.in(1), tested = null;
+ while( true ) {
+ if( iff.pred()==n ) tested = n;
+ if( !(n instanceof GuardNode guard && guard._nonZero) ) break;
+ n = guard.in(1);
+ }
+ CFGNode early = CFGNode.earlyCFG(n);
+ return tested!=null && (early==null || early.dominates(r,this)) ? n : null;
+ }
+
// 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 op = in(1);
- if( op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
+ if( op instanceof EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
op instanceof ProjNode || op instanceof NewNode || op instanceof ScopeNode || op instanceof MemMergeNode ) return false;
// A bulk Phi must split its aliases before factoring precise Stores.
if( this instanceof BulkMemPhiNode ) return false;
@@ -214,7 +202,7 @@ public class PhiNode extends Node {
ins[i] = in(i).in(j);
t = t.meet(ins[i]._type);
}
- // Not accepts both pointers and integers, but a Phi cannot mix them.
+ // Do not introduce an operand Phi mixing incompatible type families.
if( t==Type.BOTTOM ) {
for( int i=1; i<ins.length; i++ ) addDep(ins[i]);
cp.kill();
@@ -222,7 +210,7 @@ public class PhiNode extends Node {
}
PhiNode phi = j==1 && op instanceof MemOpNode mem
? new MemPhiNode(_label,mem._alias,ins)
- : PhiNode.make(_label,t.glb(false),ins);
+ : PhiNode.make(_label,t,ins);
x = phi.peephole();
}
cp.addDef(x);
@@ -269,39 +257,27 @@ 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;
- // BOTTOM means we mixed 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 )
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 92c6c2be..4b29cc3c 100644
--- a/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -2,12 +2,15 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
+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.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;
@@ -21,6 +24,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/CastMach.java b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
index e45898f5..9a9b94fd 100644
--- a/src/main/java/com/seaofnodes/simple/node/CastMach.java
+++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntMach.java
@@ -5,15 +5,13 @@ import com.seaofnodes.simple.codegen.Encoding;
import com.seaofnodes.simple.codegen.RegMask;
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); }
+/** 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 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..d3d38060
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
@@ -0,0 +1,36 @@
+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;
+
+
+/**
+ * Reinterpret a non-null managed pointer as its raw integer address.
+ *
+ * This node changes representation families but emits no code. 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 protected String format() { return "(i64*)%1"; }
+
+ @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 94c1b6f6..0a720d5b 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 com.seaofnodes.simple.type.TypeMemPtr;
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 protected String format() { return "(const)%1"; }
@Override public Type compute() {
diff --git a/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index 59030dee..8883ba8e 100644
--- a/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/RegionNode.java
@@ -2,10 +2,10 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-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 {
@@ -15,9 +15,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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
return p.p(label());
@@ -47,10 +47,10 @@ public class RegionNode extends CFGNode {
// If a CFG diamond with no merging, delete: "if( pred ) {} else {};"
if( !hasPhi() && nIns()>3 && // No Phi users, just a control user
- in(1) instanceof CProjNode p1 &&
- in(2) instanceof CProjNode p2 &&
- addDepForwards(p1).in(0)==addDepForwards(p2).in(0) &&
- p1.in(0) instanceof IfNode iff ) {
+ in(1) instanceof CProjNode p1 &&
+ in(2) instanceof CProjNode p2 &&
+ addDepForwards(p1).in(0)==addDepForwards(p2).in(0) &&
+ p1.in(0) instanceof IfNode iff ) {
// Replace with the iff.ctrl directly
if( nIns()==3 ) return iff.ctrl();
// Just delete the path for fat Regions
@@ -76,7 +76,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;
}
}
@@ -112,7 +112,8 @@ public class RegionNode extends CFGNode {
CodeGen.CODE.addAll(phi.delDef(path)._outputs);
}
}
- return isDead() ? Parser.XCTRL : delDef(path);
+ if( !isDead() ) delDef(path);
+ return isDead() ? CodeGen.CODE.XCTRL : this;
}
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 266657be..0431f7d5 100644
--- a/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -10,7 +10,15 @@ import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */
+/**
+ * The Return node has two inputs. The first input is a control node and the
+ * second is the data node that supplies the return value.
+ * <p>
+ * In this presentation, Return functions as a Stop node, since multiple <code>return</code> statements are not possible.
+ * The Stop node will be introduced in Chapter 6 when we implement <code>if</code> statements.
+ * <p>
+ * The Return's output is the value from the data node.
+ */
public class ReturnNode extends CFGNode {
public FunNode _fun;
@@ -20,6 +28,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); }
@@ -28,9 +37,6 @@ public class ReturnNode extends CFGNode {
@Override public FunNode fun() { return _fun; }
@Override
- public String label() { return "Return"; }
-
- @Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
p.p("return ");
if( expr()==null ) p.p("----");
@@ -43,13 +49,10 @@ public class ReturnNode extends CFGNode {
@Override
public Type compute() {
- if( inProgress () ) return TypeTuple.RET; // In progress
- // Inlining can delete the entry while callers still use this return.
- return TypeTuple.make(ctrl()._type,TypeMem.BOT,expr()._type);
+ 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
@@ -58,85 +61,47 @@ 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();
- }
-
- // Remember parsed type kinds for diagnostics; the optimized expression decides validity.
- 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;
- 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);
+ if( ctrl()._type == Type.CONTROL &&
+ expr()._type == TypeScalar.BOT )
+ return Parser.error("Cannot return generic scalar",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
@@ -148,13 +113,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 10a0e4aa..65f10a79 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 com.seaofnodes.simple.type.TypeFloat;
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 protected String format() { return "((f32)%1)"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/SarNode.java b/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 429be229..737b531f 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 long doOp( long x, long y ) { return x >> y; }
@Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
@@ -22,10 +22,11 @@ public class SarNode extends ArithNode {
public Node idealize() {
Node lhs = in(1);
Node rhs = in(2);
+
Type t2 = rhs._type;
// Sar of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >> 3 >> (y ? 1 : 2) ==> x >> (y ? 4 : 5)
diff --git a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 307fc0f9..92cad745 100644
--- a/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -5,18 +5,18 @@ import com.seaofnodes.print.ExprPrinter;
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.*;
/**
* The Scope node is purely a parser helper - it tracks names to nodes with a
- * stack of variable records.
+ * stack of variables.
*/
-public class ScopeNode extends Node {
+public class ScopeNode extends MemMergeNode {
/**
* The control is a name that binds to the currently active control
@@ -26,40 +26,38 @@ public class ScopeNode extends Node {
public static final String ARG0 = "arg";
public static final String MEM0 = "$mem";
- public Node in(Var v) { return in(v._idx); }
-
- @Override public Type compute() { return Type.BOTTOM; }
- @Override public Node idealize() { return null; }
-
- // All active/live variables in all nested scopes, all run together
+ // All active/live variables in all nested scopes, all run together.
+ // Maps 1-to-1 to the _inputs array.
public final Ary<Var> _vars;
// 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
public ScopeNode() {
- _type = Type.BOTTOM;
- _vars = new Ary<>(Var .class);
- _kinds = new Ary<>(Kind .class);
- _guards = new Ary<>(Node .class);
+ super();
+ _vars = new Ary<>(Var .class);
+ _kinds = new Ary<>(Kind.class);
+ _guards = new Ary<>(Node.class);
}
@Override public String label() { return "Scope"; }
@@ -70,7 +68,7 @@ public class ScopeNode extends Node {
int j=1;
for( int i=0; i<nIns(); i++ ) {
if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; }
- Var v = _vars.at(i);
+ Var v = var(i);
p.p(v._type());
p.p(" ");
if( v._final ) p.p("!");
@@ -89,7 +87,7 @@ public class ScopeNode extends Node {
public Node ctrl() { return in(0); }
- public Node mem() { return in(lookup(MEM0)); }
+ public Node mem() { return in(1); }
public Var var(int i) { return _vars.at(i); }
/**
@@ -103,7 +101,7 @@ public class ScopeNode extends Node {
* @return Node that was bound
*/
public <N extends Node> N ctrl(N n) { return setDef(0,n); }
- public Node mem(Node n) { update(MEM0,n); return n; }
+ public Node mem(Node n) { return setDef(1,n); }
public void push( Kind kind ) {
kind._lexSize = _vars.size();
@@ -112,7 +110,11 @@ public class ScopeNode extends Node {
// 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
@@ -121,68 +123,85 @@ public class ScopeNode extends Node {
// 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;
}
}
- // Allocation fields live in the scope immediately outside the constructor
- // block. Nested blocks retain that permission; surrounding bindings do not.
- public boolean canInit(Var v) {
- for( int i=_kinds._len-1; i>0; i-- )
- if( _kinds.at(i) instanceof Kind.Alloc )
- return v._idx >= _kinds.at(i-1)._lexSize && v._idx < _kinds.at(i)._lexSize;
- return false;
+ 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( _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;
}
- 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 ) {
+ // Find nearest enclosing function scope
+ public int enclosingFunction() {
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.Func func && !FunNode.isInstance(func._name) )
+ return i;
+ throw Utils.TODO("Should not reach here");
}
- // 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.
-
- // - 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
@@ -198,9 +217,9 @@ public class ScopeNode extends Node {
* 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
@@ -208,17 +227,45 @@ public class ScopeNode extends Node {
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;
}
+ // Normal lookup was tried and failed. Append a forward ref in the current
+ // lexical scope. Scope pops promote it outwards "as if" the final constant
+ // had been declared before the using scope.
+
+ public Var defineFRef( String id, Type t, boolean xfinal, Parser.Lexer loc ) {
+ Var var = new Var(nIns(),id,t,xfinal,loc,true);
+ FRefNode fref = new FRefNode(id,loc).init();
+ fref._type = fref._con = t;
+ _vars.add(var);
+ addDef(fref);
+ 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);
+ }
+
+
/**
* Lookup a name in all scopes starting from most deeply nested.
*
@@ -254,7 +301,7 @@ public class ScopeNode extends Node {
// 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,PhiNode.make(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;
@@ -292,9 +339,15 @@ public class ScopeNode extends Node {
n.keep();
dup.addDef(ctrl()); // Control input is just copied
- // Memory is one binding, with the same lazy loop Phi as scalar names.
- for( int i=1; i<nIns(); i++ )
- dup.addDef(loop && !_vars.at(i)._final ? this : in(i));
+ // 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++ )
+ // For lazy phis on loops we use a sentinel
+ // that will trigger phi creation on update
+ dup.addDef(loop && !var(i)._final ? this : in(i));
return dup;
}
@@ -308,14 +361,17 @@ public class ScopeNode extends Node {
*/
public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
- _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 = 1; 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
@@ -328,18 +384,38 @@ public class ScopeNode extends Node {
// 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);
}
}
+ public void balanceLoopFRefs( ScopeNode scope ) {
+ for( int i = nIns(); i < scope.nIns(); i++ ) {
+ Var n = scope.var(i);
+ if( n.isFRef() ) { // Loop body has forward refs
+ Var v = new Var(nIns(),n._name,n.type(),n._final,n._loc,true);
+ v._uninit = n._uninit;
+ _vars.add(v);
+ addDef(scope.in(i));
+ }
+ }
+ }
+
+ // ------------------------------------------------
// 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 ) {
@@ -347,49 +423,108 @@ public class ScopeNode extends Node {
assert ctrl instanceof LoopNode loop && loop.inProgress();
ctrl.setDef(2,back.ctrl());
- _endLoop(this,back,exit);
+ this._endLoop( this, back, exit);
back.kill(); // Loop backedge is dead
// Now one-time do a useless-phi removal
- _useless();
+ this._useless();
}
// Fill in the backedge of any inserted Phis
void _endLoop( ScopeNode scope, Node back, Node exit ) {
for( int i=1; i<nIns(); i++ ) {
- if( _vars.at(i)._final ) continue; // Final vars did not get modified in the loop
- if( _vars.at(i).type().isHighOrConst() ) continue; // Cannot lift higher than a constant, so no Phi
- if( back.in(i) != scope ) {
- PhiNode phi = (PhiNode)in(i);
+ if( var(i)._final ) continue; // Final vars did not get modified in the loop
+ Node n = in(i);
+ if( var(i).type().isHighOrConst() && !(n instanceof PhiNode phi && phi.inProgress()) ) { // Cannot lift higher than a constant, so no Phi
+ Node con = ConstantNode.make(var(i).type()).init();
+ n.subsume(con);
+ n = con;
+ } 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));
- }
- }
-
-
- // Now one-time do a useless-phi removal
- void _useless( ) {
- for( int i=1; i<nIns(); i++ ) {
- if( in(i) instanceof PhiNode phi ) {
- // Do an eager useless-phi removal
- Node in = phi.peephole();
- CodeGen.CODE.addAll(phi._outputs);
- phi.moveDepsToWorklist();
- if( in != phi ) {
- if( !phi.iskeep() ) // Keeping phi around for parser elsewhere
- phi.subsume(in);
- setDef(i,in); // Set the update back into Scope
- }
- }
+ 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 ) {
- assert ctrl instanceof CFGNode;
+ //
+ // This operation is lexical, not a full semantic theorem prover, and thus
+ // its behavior changes the language spec. Optimizations that hide the
+ // lexical structure (e.g. constant folding control flow) can hide what is
+ // guarded or how the guard is applied, making it somewhat fragile. Several
+ // good use cases are covered here, but not everything you might like.
+ //
+ // Simple lexical facts work well:
+ //
+ // ```
+ // if( ptr )
+ // return ptr.field; // `ptr` is guarded non-null here.
+ //
+ // if( a && a.ptr )
+ // return a.ptr.x; // `a` is guarded while parsing the RHS load,
+ // // and remains guarded on the true arm.
+ // ```
+ //
+ // Short-circuit expressions are lowered as nested Phis. We recursively
+ // decompose the recognized Phi shape, so chains such as:
+ //
+ // ```
+ // if( x && y && z )
+ // use(x,y,z);
+ // ```
+ //
+ // discover the nested `x && y` and then `x`, `y`, and `z`; this is not
+ // merely a one-level lookup. Likewise, nested tests carry lexical guards
+ // inwards:
+ //
+ // ```
+ // if( x && y )
+ // if( z && a )
+ // if( x.ptr && z.ptr )
+ // ...
+ // ```
+ //
+ // Here `x` is already guarded while parsing the outer true arm, `z` while
+ // parsing the inner true arm, and both are available before the matching
+ // loads. The later post-test decomposition is just a convenience for
+ // values still schedulable at the post-test control.
+ //
+ // The hard limit is that a semantic implication is not always a
+ // schedulable Node. For `(x && y)`, the true side implies that `y` was
+ // true, but `y` might be a value computed only on the RHS control arm:
+ //
+ // ```
+ // val ptr = ...;
+ // if( ptr && (ptr=fcn() ? 0 : arg ) {}
+ // ```
+ //
+ // A Guard pinned after the Phi merge and using the branch-local `ptr`
+ // value would give that value a use above its defining arm. GCM then has
+ // no legal block between the value's early control and the LCA of its
+ // uses. Thus `_addGuards` first checks that the predicate's effective
+ // early CFG dominates the control where the Guard would be pinned. If it
+ // does not, we skip that guard even if the fact is semantically true.
+ //
+ // A more complete answer would need a richer logical/proof node, or would
+ // need to rematerialize predicates on the guarded path instead of using
+ // branch-local nodes directly. Plain GuardNodes are intentionally kept to
+ // facts represented by values already available at the guard control.
+ //
+ // TODO: Make this more robust for shapes like:
+ // ```
+ // if( !ary || !ary# ) exit(); // facts about ary are lost, despite the early exit
+ // for( ...; ary# ) ...;
+ // ```
+ // Here the guard ends up guarding the whole expression (both ary and ary#
+ // are not-null), but loses that ary is not-null. A better answer might be
+ // a dedicated logical expression node, or some sort of gather-of-checked
+ // values to Guard post-test.
+
+ public void addGuards( CFGNode ctrl, Node pred, boolean invert ) {
_guards.add(ctrl); // Marker between guard sets
// add pred & its cast to the normal input list, with special Vars
if( pred==null || pred.isDead() )
@@ -397,10 +532,11 @@ public class ScopeNode extends Node {
_addGuards(ctrl,pred,invert);
}
- private void _addGuards( Node ctrl, Node pred, boolean invert ) {
+ private void _addGuards( CFGNode ctrl, Node pred, boolean invert ) {
if( pred==null || pred.isDead() )
return;
- if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) )
+ CFGNode early = CFGNode.earlyCFG(pred);
+ if( early != null && !early.dominates(ctrl,null) )
return;
// A single negation is handled below. For !!p (including p != null)
// or a negated short-circuit Phi, also discover the underlying facts.
@@ -410,8 +546,10 @@ public class ScopeNode extends Node {
_addGuards(ctrl,not.in(1),!invert);
pred.unkeep();
}
- // A false || or true && proves both operands. Only guard values
- // available at this control, not ones confined to the RHS branch.
+ // Short-circuit logic is represented by a Phi. For `a || b` being
+ // false, or `a && b` being true, both individual operands have the
+ // same proven value as the whole expression. The "skipped RHS" path
+ // is the Region input whose Phi value is the controlling If predicate.
if( pred instanceof PhiNode phi && phi.nIns()==3 && phi.region() instanceof RegionNode r && r.nIns()==3 )
for( int i=1; i<3; i++ )
if( r.in(i) instanceof CProjNode prj && prj.ctrl() instanceof IfNode iff ) {
@@ -425,38 +563,19 @@ public class ScopeNode extends Node {
// 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);
- }
- }
-
- // A Phi's value is available at its Region. Other data nodes require all
- // their inputs, including pinned control, to dominate the proposed guard.
- private static boolean availableAt(Node n, CFGNode ctrl, BitSet visit) {
- if( n==null || n instanceof ConstantNode || visit.get(n._nid) ) return true;
- visit.set(n._nid);
- if( n instanceof PhiNode phi ) n = phi.region();
- if( n instanceof CFGNode cfg ) {
- for( CFGNode dom=ctrl; dom!=null; dom=dom.idom() )
- if( dom==cfg ) return true;
- return false;
- }
- for( Node def : n._inputs )
- if( !availableAt(def,ctrl,visit) ) return false;
- return true;
+ if( zeroPred != null )
+ _addGuard(false,ctrl,zeroPred);
}
private static Node shortCircuitSkippedPred( Node val, Node pred, CProjNode prj, boolean invert ) {
- if( val == null ) return null;
+ if( val == null )
+ return null;
if( val == pred )
return (prj._idx==0)==invert ? val : null;
if( val instanceof NotNode not && not.in(1)==pred )
@@ -468,19 +587,20 @@ public class ScopeNode extends Node {
return null;
}
- 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,nonZero);
}
}
// Remove matching pred/cast pairs from this guarded region.
- public ScopeNode removeGuards( Node ctrl ) {
+ public void removeGuards( Node ctrl ) {
assert ctrl instanceof CFGNode;
// Pop the guards up to this region's marker.
while( true ) {
@@ -490,7 +610,6 @@ public class ScopeNode extends Node {
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
@@ -520,11 +639,65 @@ public class ScopeNode extends Node {
}
- private void replace( Node old, Node cast ) {
+ private void replace( Node old, Node cast, boolean nonZero ) {
assert old!=null && old!=cast;
for( int i=0; i<nIns(); i++ )
- if( in(i)==old )
+ if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) )
setDef(i,cast);
}
+ private static boolean nullCheckedPhi( Node n, Node old ) {
+ if( !(n instanceof PhiNode phi) || phi.nIns()!=3 )
+ return false;
+ if( phi.in(1)==null || phi.in(2)==null )
+ return false;
+ int zero = phi.in(1)._type == phi.in(1)._type.makeZero() ? 1 :
+ phi.in(2)._type == phi.in(2)._type.makeZero() ? 2 : -1;
+ if( zero == -1 )
+ return false;
+ Node val = phi.in(3-zero);
+ return val==old ||
+ val instanceof GuardNode guard && guard._nonZero && guard.in(1)==old;
+ }
+
+ // ------------------------------------------------------------------------
+ // 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 e2951784..f657cf51 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 long doOp( long x, long y ) { return x << y; }
@Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
@@ -30,13 +31,13 @@ public class ShlNode extends ArithNode {
if( rhs._type instanceof TypeInteger shl && shl.isConstant() ) {
// Shl of 0.
- if( (shl.value()&63)==0 && lhs._type instanceof TypeInteger )
+ if( (shl.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// (x + c) << i => (x << i) + (c << i)
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 0023431f..e93ea0e8 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 long doOp( long x, long y ) { return x >>> y; }
@Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
@@ -21,7 +21,7 @@ public class ShrNode extends ArithNode {
Type t2 = rhs._type;
// Shr of 0.
- if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && lhs._type instanceof TypeInteger )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && (i.value()&63)==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// TODO: x >>> 3 >>> (y ? 1 : 2) ==> x >>> (y ? 4 : 5)
diff --git a/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/src/main/java/com/seaofnodes/simple/node/SplitNode.java
index 211489fb..0e104c15 100644
--- a/src/main/java/com/seaofnodes/simple/node/SplitNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -3,13 +3,16 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
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;
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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
p.p("mov(");
@@ -17,6 +20,7 @@ public abstract class SplitNode extends MachConcreteNode {
else p.n(in(1));
return p.close();
}
+ 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..cd51f603
--- /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 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 e13d35c0..84c8a3a7 100644
--- a/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -5,39 +5,76 @@ import com.seaofnodes.print.ExprPrinter;
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.HashSet;
-import static com.seaofnodes.simple.util.Utils.TODO;
-/** Start supplies control, whole memory, and the argument, in that order. */
+/**
+ * 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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
return p.p(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.BOT,_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; }
@@ -46,4 +83,5 @@ public class StartNode extends LoopNode implements MultiNode {
@Override public int idepth() { return 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 9f7a925b..e52a49a2 100644
--- a/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -2,34 +2,45 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-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;
-public class StopNode extends CFGNode {
+import java.util.HashMap;
+import java.util.IdentityHashMap;
- public final String _src;
+// A collection of function ReturnNodes for a compilation unit.
+public class StopNode extends CFGNode {
- 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
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
// 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 p.n(ret1);
+
p.p("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.") )
p.n(ret).p(" ");
}
@@ -42,25 +53,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;
@@ -70,7 +92,10 @@ public class StopNode extends CFGNode {
if( validIDepth() ) 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 cacheIDepth(d);
}
}
diff --git a/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index 4e1d7ce3..4917e890 100644
--- a/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -2,9 +2,14 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.codegen.*;
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.HashMap;
+import java.util.IdentityHashMap;
/**
* Store represents setting a value to a memory based object, in chapter 10
@@ -12,7 +17,10 @@ import com.seaofnodes.simple.type.*;
*/
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
@@ -21,20 +29,43 @@ 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 decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init) {
+ this(loc,name,alias,decl,ctrl,mem,ptr,off,value,init,(byte)0);
+ }
+ private StoreNode(Parser.Lexer loc, String name, int alias, Type decl, Node ctrl, Node mem, Node ptr, Node off, Node value, boolean init, byte size) {
+ super(loc, name, alias, false, decl, 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);
}
- // Debugger label
@Override boolean canDrop(MemOpNode other, Node dep) {
return super.canDrop(other,dep) && _init==((StoreNode)other)._init;
}
+ // Debugger label
@Override public String label() { return "st_"+mlabel(); }
@Override public boolean isMem() { return true; }
+ public Node nnptr() {
+ if( !(ptr() instanceof GuardNode cast) ) return ptr();
+ assert cast._nonZero;
+ return cast.in(1);
+ }
public Node val() { return in(4); }
+ public int storeSize() { assert _size==1 || _size==2 || _size==4 || _size==8; return _size; }
+ public int log_size() { return Integer.numberOfTrailingZeros(storeSize()); };
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -43,42 +74,110 @@ public class StoreNode extends MemOpNode {
@Override
public Type compute() {
- Type val = val()._type;
- assert !_declaredType.isFRef();
- Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this));
- return TypeMem.make(_alias,t);
+ Type val = val()._type;
+ Type mem0 = mem()._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 )
+ { assert decl != Type.TOP; val = val.join(decl); }
+ // 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() {
- if( mem() instanceof MemMergeNode merge ) {
- setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
- return this;
+ // 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.
+ if( _size==0 && (_size= storageType()) != 0 ) {
+ unlock(); // Width participates in GVN identity.
+ return init();
}
+ assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias;
- if( mem() instanceof CastNode cast ) {
- setDef(1,cast.in(1));
+ // Simple store-after-MemMerge to a known alias can bypass. Happens when inlining.
+ Node mem = mem();
+ if( _alias != 1 && mem instanceof MemMergeNode merge ) {
+ setDef(1,merge.alias(_alias));
return this;
}
+ Field fld;
+ if( _alias==1 && ptr()._type instanceof TypeMemPtr tmp && (fld=tmp._obj.field(_name)) != null && _size != 0 ) {
+ // 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 store's 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.
+ 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;
+ CodeGen.CODE.add(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(null,mem);
+ if( !(mem instanceof BulkMemPhiNode) )
+ mmm.alias(fld._alias,st);
+ return mmm.init();
+ }
+
// Simple store-after-store on same address. Should pick up the
// required init-store being stomped by a first user store.
- if( mem() instanceof StoreNode st &&
- ptr()==st.ptr() && _alias==st._alias && // Must check same object
- off()==st.off() && // And same offset
+ if( mem instanceof StoreNode st &&
+ ptr()==st.ptr() && // Must check same object
+ off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index)
ptr()._type instanceof TypeMemPtr && // No bother if weird dead pointers
// 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;
}
// 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;
@@ -90,7 +189,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() ) {
@@ -106,15 +205,56 @@ public class StoreNode extends MemOpNode {
return this;
}
}
+
return null;
}
+ // 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 byte storageType() {
+ if( !(ptr()._type instanceof TypeMemPtr tmp) )
+ return 0;
+ TypeStruct obj = (TypeStruct)Parser.TYPES.get(tmp._obj._name);
+ Field fld = obj.field(_name);
+ if( fld == null || fld._t == null )
+ return 0;
+ if( fld._t == Type.BOTTOM )
+ return 0; // Store into a final field
+ assert fld._t != Type.TOP;
+ // Freshly parsed field stores retain the declaring structure on their
+ // symbolic offset even after the pointer's flow type sharpens.
+ if( fld._t.isConstant() ) {
+ if( !(off() instanceof ConFldOffNode coff) )
+ return 0;
+ fld = coff._ts.field(_name);
+ if( fld == null || fld._t == null )
+ return 0;
+ }
+ // Compute storage size from field type
+ byte size = (byte)(1 << fld._t.log_size());
+ assert size==1 || size==2 || size==4 || size==8;
+ return size;
+ }
+
+ // 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 -> throw Utils.TODO("Untested"); // esc.pub()==this ? esc.fld()._alias : 0;
+ default -> 0;
+ };
+ }
+
// Phi(mem, Store(phi,ptr,off,val)) -> Store(mem,ptr,off,Phi(load(mem),val)).
// With no observers of the backedge store, only the last value matters.
// The entry load preserves the field even when the loop takes zero trips.
Node sink(MemPhiNode phi) {
phi.addDepForwards(this);
- if( nOuts()!=1 ) return null;
+ if( nOuts()!=1 || _alias==1 || _size==0 || _init ) return null;
phi.addDep(ptr());
// Start with a fixed field of an allocation dominating the loop.
// In particular, do not sink stores through a loop-varying address.
@@ -138,9 +278,9 @@ public class StoreNode extends MemOpNode {
if( ctrl==null ) return null;
Node mem = phi.in(1);
- Node init = new LoadNode(_loc,_name,_alias,_declaredType,mem,ptr(),off()).peephole();
- Node value = CodeGen.CODE.add(new PhiNode(_name,_declaredType,loop,init,val()).peephole());
- Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init);
+ Node init = new LoadNode(_loc,_name,_alias,null,mem,ptr(),off()).peephole();
+ Node value = CodeGen.CODE.add(new PhiNode(_name,loop,init,val()).peephole());
+ Node sink = new StoreNode(_loc,_name,_alias,_con,null,mem,ptr(),off(),value,_init,_size);
// Break the memory cycle, killing the old store. Keep the Phi alive
// even if that store was its last use, until our caller replaces it.
phi.keep();
@@ -164,11 +304,16 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
- if( ptr()._type == Type.TOP )
- return null; // This means we have an error input, report elsewhere
+ assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Opto.ordinal();
+ if( ptr()._type == Type.TOP ) // Dead store?
+ return null;
TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- if( (tmp._ro || tmp._obj.field(_name)._final) && !_init )
+ Field f = tmp._obj.field(_name);
+ if( f!=null && (tmp._ro || 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
deleted file mode 100644
index 78b20e9f..00000000
--- a/src/main/java/com/seaofnodes/simple/node/StructNode.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.seaofnodes.simple.node;
-
-import com.seaofnodes.print.ExprPrinter;
-
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.SB;
-
-/**
- * Build a compound object
- */
-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
- protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- if( _ts==null ) return p.p("STRUCT?");
- p.p(_ts._name).p(" {");
- for( int i=0; i<nIns(); i++ ) {
- p.p(_ts._fields[i]._fname).p(":");
- p.p(in(i)==null ? Type.BOTTOM : in(i)._type);
- p.p("; ");
- }
- return p.unchar("; ").p("}");
- }
-
- @Override
- public TypeStruct compute() {
- if( _ts==null ) return TypeStruct.BOT;
- Field[] fs = new Field[_ts._fields.length];
- for( int i=0; i<fs.length; i++ )
- fs[i] = _ts._fields[i].makeFrom(in(i)==null ? Type.TOP : in(i)._type);
- return TypeStruct.make(_ts._name,false,fs);
- }
-
- @Override
- public Node idealize() { return null; }
-
- @Override
- public boolean eq(Node n) { return _ts == ((StructNode)n)._ts; }
-
- @Override
- int hash() { return _ts==null ? 0 : _ts.hashCode(); }
-}
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 2c4fed39..8eb1f4cc 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) )
@@ -32,8 +35,8 @@ public class SubNode extends ArithNode {
Type t1 = lhs._type;
Type t2 = rhs._type;
- // Keep invalid operands for type checking.
- if( t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
+ // Keep invalid operands and unresolved numeric modes for type checking.
+ if( _mode==1 && t1 instanceof TypeInteger x && t2 instanceof TypeInteger y ) {
if( y.isConstant() && y.value()==0 ) return lhs;
if( x.isConstant() && x.value()==0 ) return new MinusNode(rhs);
}
@@ -49,6 +52,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 840a95ec..151a326e 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 com.seaofnodes.simple.type.TypeInteger;
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 protected String format() { return "(flt)%1"; }
diff --git a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
index df740202..33806300 100644
--- a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
@@ -7,6 +7,7 @@ import com.seaofnodes.simple.type.TypeInteger;
// Truncate toward zero, saturating overflow; NaN converts to zero.
public class ToIntegerNode extends Node {
public ToIntegerNode(Node lhs) { super(null,lhs); }
+ @Override public Tag serialTag() { return Tag.ToInteger; }
@Override public String label() { return "ToInteger"; }
@Override protected String format() { return "(int)%1"; }
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 477b9af8..32e1deb9 100644
--- a/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
+++ b/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
@@ -8,7 +8,7 @@ import com.seaofnodes.simple.type.Type;
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 protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) { return p.p("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 75cea98e..c825f396 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 long doOp( long x, long y ) { return x ^ y; }
@Override TypeInteger doOp( TypeInteger x, TypeInteger y ) {
@@ -18,12 +18,13 @@ public class XorNode extends ArithNode {
public Node idealize() {
Node lhs = in(1);
Node rhs = in(2);
+
Type t1 = lhs._type;
Type t2 = rhs._type;
// Xor of 0. We do not check for (0^x) because this will already
// canonicalize to (x^0)
- if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && lhs._type instanceof TypeInteger )
+ if( t2.isConstant() && t2 instanceof TypeInteger i && i.value()==0 && forceInt(lhs) && lhs._type instanceof TypeInteger )
return lhs;
// Move constants to RHS: con*arg becomes arg*con
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 127f17ff..31bbde86 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
@@ -9,9 +9,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(); }
@@ -36,7 +38,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( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) );
+ enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop, _fp)) );
} else {
if( _bop=="!=" ) return; // Inverted, no code
enc.add4(Arm64.b(Arm64.OP_UJMP, 0));
@@ -56,7 +58,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,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop)));
+ enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.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 e1717cb3..4de28f3a 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
@@ -14,8 +14,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 dc21e075..db2540f7 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
@@ -4,21 +4,22 @@ import com.seaofnodes.isa.Arm64;
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(Arm64.load_pc(Arm64.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 7a9e74c6..72b76edd 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
@@ -7,19 +7,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();
Arm64.constant(enc,self,x);
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 8fc22a67..53494092 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
@@ -25,7 +25,7 @@ public class LoadARM extends MemOpARM {
// ldr(immediate - unsigned offset) | ldr(register)
@Override public void encoding( Encoding enc ) {
- ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size());
+ ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], 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 dfbe5e06..14eaf510 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
@@ -7,13 +7,13 @@ 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;
import java.lang.StringBuilder;
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);
@@ -22,6 +22,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
@@ -35,10 +37,10 @@ public abstract class MemOpARM extends MemOpNode implements MachNode {
@Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); }
- @Override public Type compute() { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
- 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 7110df62..8bb8983e 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
@@ -32,7 +32,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 afbbd3bf..e5b06c39 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
@@ -9,12 +9,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; }
@@ -22,7 +24,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 = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this));
+ int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.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 f93f0f24..dcfeb5d8 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
@@ -9,7 +9,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; }
@@ -25,7 +25,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(Arm64.load_str_imm(Arm64.OP_STORE_IMM_64, off, arm.RSP, src, 8));
return;
@@ -33,7 +33,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(Arm64.load_str_imm(Arm64.OP_LOAD_IMM_64, off, arm.RSP, dst, 8));
return;
@@ -57,10 +57,4 @@ public class SplitARM extends SplitNode {
enc.add4(Arm64.f_mov_general(Arm64.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 2cc54f76..0b90ac8b 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
@@ -28,7 +28,7 @@ public class StoreARM extends MemOpARM {
private static final int[] OP_STORE_RS = new int[]{ Arm64.OP_STORE_R_8, Arm64.OP_STORE_R_16, Arm64.OP_STORE_R_32, Arm64.OP_STORE_R_64, };
@Override public void encoding( Encoding enc ) {
- ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size());
+ ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], 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 98a56089..a053ba46 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
@@ -2,28 +2,30 @@ package com.seaofnodes.simple.node.cpus.arm;
import com.seaofnodes.isa.Arm64;
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 = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self);
+ enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
// add x0, x0, 0
- enc.add4(adrp);
- arm.imm_inst(enc,Arm64.OPI_ADD,0, 0);
+ arm.imm_inst(enc,Arm64.OPI_ADD,0, dst);
}
@Override public byte encSize(int delta) {
@@ -34,15 +36,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, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, rpc));
- // low 12 bits via add
- enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.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();
@@ -51,7 +45,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 d1cafa96..b2531f97 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
@@ -10,14 +10,16 @@ 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 ) {
- enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ if( _ext!=null ) enc.externalData(this,_ext);
+ else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
short dst = enc.reg(this);
// adrp x0, 0
enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
@@ -33,15 +35,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, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, adrp_delta >> 2, dst));
- // low 12 bits via add
- enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.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 32c1e2fc..660f1559 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
@@ -212,6 +212,14 @@ public class arm extends Machine {
enc.add4(body);
}
+ 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 , Arm64.adrp(1, imm21 & 3, OP_ADRP, imm21 >>> 2, rd));
+ enc.patch4(opStart+4, Arm64.imm_inst_l(OPI_ADD, target & 0xFFF, rd));
+ }
+
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);
@@ -225,10 +233,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 ) {
@@ -236,24 +250,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 |
@@ -271,7 +288,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() {
@@ -281,30 +298,33 @@ 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 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 MemPhiNode phi -> new MemPhiNode(phi);
case PhiNode phi -> new PhiNode(phi);
case ProjNode prj -> prj(prj);
case ReadOnlyNode read -> new ReadOnlyMach(read);
@@ -312,11 +332,12 @@ public class arm extends Machine {
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 ToIntegerNode cvt -> new F8ToIARM(cvt);
case XorNode xor -> xor(xor);
@@ -329,7 +350,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() )
@@ -348,13 +369,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);
@@ -367,22 +393,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) {
@@ -424,6 +458,12 @@ public class arm extends Machine {
: new LsrARM(lsr);
}
+ private Node prj(ProjNode prj) {
+ TypeInteger ret = cReturn(prj);
+ if( ret!=null ) return new ExtendARM(prj,ret);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
+ }
+
private static int off;
private static Node idx;
private Node st(StoreNode st) {
@@ -449,9 +489,4 @@ public class arm extends Machine {
return mop;
}
- private Node prj(ProjNode prj) {
- TypeInteger ret = cReturn(prj);
- return ret==null ? new ProjARM(prj) : new ExtendARM(prj,ret);
- }
-
}
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 14581111..59bad64e 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
@@ -2,19 +2,19 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.isa.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);
@@ -37,7 +37,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 f0ad8e88..b1426a3f 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
@@ -15,8 +15,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 5eee596d..cda4bc79 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
@@ -14,7 +14,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 51ddc9db..a110d156 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
@@ -9,12 +9,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 23ac8481..dd4a02a3 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
@@ -5,21 +5,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 9088abbd..35d2bc65 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
@@ -11,7 +11,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 2e3dfd8d..e9f3ec02 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
@@ -3,7 +3,9 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.isa.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
@@ -22,6 +24,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 adf21848..e0bbc219 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
@@ -11,6 +11,7 @@ import com.seaofnodes.simple.type.*;
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;
@@ -18,7 +19,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(); }
@@ -26,42 +28,11 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode {
@Override protected ExprPrinter<Node> _printMach(ExprPrinter<Node> p) { return p.p(".").p(_name); }
- @Override public Type compute() { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
// 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 965a3c7c..40d0359c 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
@@ -41,8 +41,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 8a16f6b7..d455337b 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
@@ -9,7 +9,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 ) {
@@ -25,7 +25,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));
@@ -33,7 +33,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));
@@ -58,10 +58,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 48cb7c78..c3fcf4d3 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
@@ -4,6 +4,7 @@ import com.seaofnodes.isa.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;
@@ -27,7 +28,7 @@ public class StoreRISC extends MemOpRISC {
if( i==1 ) return null; // mem
if( i==2 ) return riscv.RMASK; // ptr
// 2 - index
- if( i==4 ) return _sz >= '4'
+ if( i==4 ) return _bytes >= 4
? riscv.MEM_MASK // Word/dword value in GPR or FPR
: riscv.RMASK; // Byte/short stores require a GPR
return null; // Anti-dependence
@@ -42,6 +43,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 e57f6e09..44ef1a95 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
@@ -2,25 +2,27 @@ package com.seaofnodes.simple.node.cpus.riscv;
import com.seaofnodes.isa.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
@@ -33,9 +35,14 @@ public class TFPRISC 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 ) {
- // ADDI sign-extends its low 12 bits, so round the AUIPC high part.
- enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, (delta+0x800)>>12));
- enc.patch4(opStart+4,RiscV.i_type(RiscV.OP_IMM, rpc, 0, rpc, delta & 0xFFF));
+ int hi20 = (delta + 0x800) >> 12;
+ int lo12 = delta - (hi20 << 12);
+ // AUIPC (upper 20 bits)
+ // opstart of add
+ enc.patch4(opStart ,RiscV.u_type(RiscV.OP_AUIPC, rpc, hi20));
+ // addi(low 12 bits)
+ 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.
throw Utils.TODO();
@@ -43,7 +50,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 7bc2db7e..a5ff3753 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
@@ -9,14 +9,16 @@ import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
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 ) {
- enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ if( _ext!=null ) enc.externalData(this,_ext);
+ else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
short dst = enc.reg(this);
// AUIPC dst,#hi20_constant_pool
enc.add4(RiscV.u_type(RiscV.OP_AUIPC, dst, 0));
@@ -29,9 +31,9 @@ public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize {
// Delta is from opcode start
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short dst = enc.reg(this);
- // Round the high part because ADDI's low 12 bits are signed.
+ // AUIPC dst,#hi20_constant_pool
enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12));
- // ADDI dst,dst,#low12_constant_pool
+ // ADDI's low 12 bits are signed; round the AUIPC page accordingly.
enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF));
}
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 7c994624..19b91866 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
@@ -15,7 +15,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;
@@ -34,24 +33,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; }
@@ -92,7 +91,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,
@@ -138,10 +137,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 ) {
@@ -149,23 +154,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) |
@@ -186,7 +194,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) {
@@ -203,30 +211,33 @@ 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 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 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);
@@ -234,11 +245,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 ToIntegerNode cvt -> new F8ToIRISC(cvt);
case XorNode xor -> xor(xor);
@@ -249,11 +261,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);
@@ -271,8 +281,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) {
@@ -311,9 +323,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);
@@ -326,18 +342,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
@@ -394,7 +414,7 @@ public class riscv extends Machine {
private Node prj(ProjNode prj) {
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendRISC(prj,ret);
- 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 974f690f..74af9266 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
@@ -7,7 +7,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/CallX86.java b/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
index d1a43b83..6bafbf37 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
@@ -9,21 +9,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 7b7d98d5..4014045b 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
@@ -20,8 +20,8 @@ public class CmpMemX86 extends MemOpX86 {
short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
if( src==-1 ) {
assert !_swap;
- X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale);
- } else encVal(enc,_declaredType,ptr,idx,src,_off,_scale,_swap);
+ X86.cmpImm(enc,_con.log_size(),_imm,ptr,idx,_off,_scale);
+ } else encVal(enc,_con,ptr,idx,src,_off,_scale,_swap);
}
static void encVal(Encoding enc, Type decl, short ptr, short idx, short src, int off, int scale, boolean _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 da9bdce7..59f06341 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
@@ -4,17 +4,20 @@ import com.seaofnodes.isa.X86;
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();
_encSize=X86.rip(enc,0xF2,0x0F10,false,enc.reg(this)-x86_64_v2.XMM_OFFSET);
enc.largeConstant(this,_con,_encSize-4,2/*ELF PC32*/);
}
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 c7a20e4d..0e44aee7 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
@@ -11,6 +11,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 656b1543..2ffaa3d1 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
@@ -6,15 +6,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
@@ -23,6 +25,7 @@ public class IntX86 extends ConstantNode implements MachNode {
}
@Override public void encoding( Encoding enc ) {
+ if( _ext!=null ) throw Utils.TODO();
X86.constant(enc,enc.reg(this),_con==Type.NIL ? 0 : ((TypeInteger)_con).value());
}
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 d4f1413e..5c472dbe 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
@@ -13,7 +13,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
@@ -21,7 +21,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 309e62d9..fcca7f8f 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
@@ -6,7 +6,6 @@ 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;
import java.lang.StringBuilder;
// Generic X86 memory operand base.
@@ -42,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
@@ -59,8 +59,8 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode {
}
@Override public String label() { return op(); }
- @Override public Type compute() { throw Utils.TODO(); }
- @Override public Node idealize() { throw Utils.TODO(); }
+ @Override public Type compute() { throw new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
+ @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
// Register mask allowed on input i.
@Override public RegMask regmap(int i) {
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 1cfc1c42..d6a06c2f 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
@@ -16,7 +16,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 e8281975..b2b643d3 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
@@ -12,8 +12,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; }
@@ -26,10 +25,10 @@ public class SplitX86 extends SplitNode {
boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET;
// 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);
X86.stackCopy(enc,soff,off);
return;
}
@@ -37,7 +36,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;
}
@@ -45,10 +44,4 @@ public class SplitX86 extends SplitNode {
X86.move(enc,dstX,srcX,dstX ? dst-x86_64_v2.XMM_OFFSET : dst,
srcX ? src-x86_64_v2.XMM_OFFSET : 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 c83dfd44..9ffa25fc 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
@@ -27,8 +27,8 @@ public class StoreX86 extends MemOpX86 {
@Override public RegMask outregmap() { return null; }
@Override public void encoding( Encoding enc ) {
short ptr=enc.reg(ptr()), idx=enc.reg(idx()), src=enc.reg(val());
- if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale);
- else encVal(enc,_declaredType,ptr,idx,src,_off,_scale);
+ if( src==-1 ) X86.storeImm(enc,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale);
+ else encVal(enc,_con,ptr,idx,src,_off,_scale);
}
// Non-immediate encoding
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 fff2fb04..e3563713 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
@@ -3,39 +3,39 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2;
import com.seaofnodes.isa.X86;
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(); }
@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 ) {
- if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; }
- enc.relo(this);
- X86.rip(enc,0,0x8D,true,enc.reg(this));
+ if( _ext==null ) enc.relo(this);
+ else enc.external(this,_ext);
+ _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this));
}
- @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,
@@ -43,11 +43,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 c78ac6de..682597f4 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
@@ -7,30 +7,29 @@ import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
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 ) {
short dst = enc.reg(this);
- X86.rexF(dst, 0, 0, true, enc);
- enc.add1(0x8D).add1(X86.modrm(X86.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*/);
+ // 0 or 1 for REX depending on the dst.
+ _opLen = X86.rip(enc,0,0x8D,true,dst);
+ if( _ext!=null ) enc.externalData(this,_ext);
+ else 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 e283bc8f..ff15fdad 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
@@ -97,22 +97,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();
};
}
@@ -145,14 +152,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
@@ -186,9 +201,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);
@@ -198,13 +218,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) |
@@ -223,12 +246,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;
@@ -237,8 +266,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
@@ -250,30 +279,33 @@ 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 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 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);
@@ -281,14 +313,15 @@ 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 ToIntegerNode cvt -> new F8ToIX86(cvt);
- 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);
@@ -303,14 +336,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 )
- return new AddMemX86(add,address(ld),ld.ptr(),idx,off,scale, 0,lhs);
-
// 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 ) {
@@ -333,17 +364,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) )
@@ -367,8 +387,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);
}
@@ -393,12 +415,13 @@ 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) &&
- (val!=null || bool.op()=="==" || bool.op()=="!=") )
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld.declaredType()) &&
+ (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) ||
+ bool.op()=="==" || bool.op()=="!=") )
return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
// Vs immediate
@@ -415,18 +438,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);
@@ -439,7 +467,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);
}
@@ -462,7 +492,7 @@ public class x86_64_v2 extends Machine {
private Node prj( ProjNode prj ) {
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendX86(prj,ret);
- return new ProjX86(prj);
+ return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
}
private Node sar(SarNode sar) {
@@ -549,7 +579,7 @@ public class x86_64_v2 extends Machine {
private int imm( Node xval ) {
assert val == null && imm == 0;
- if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
+ if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti && ti.isConstant() && imm32(ti.value()) ) {
val = null;
imm = (int) ti.value();
assert imm == ti.value(); // In 32-bit range
diff --git a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
index 48296ce7..e8218afd 100644
--- a/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -9,6 +9,8 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import java.util.ArrayList;
import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.util.BAOS;
+import java.util.HashMap;
/** Machine-specific facts; traversal, columns and byte display live in print/. */
public final class ASMPrinter extends AssemblyAdapter<Node> {
@@ -24,7 +26,7 @@ public final class ASMPrinter extends AssemblyAdapter<Node> {
}
@Override public ArrayList<Node> blocks() {
var blocks=new ArrayList<Node>();
- var cfg=_code._cfg;
+ var cfg=_enc==null ? _code._cfg : _enc._cfg;
if( cfg!=null ) for( Node n : cfg ) blocks.add(n);
return blocks;
}
@@ -50,26 +52,24 @@ public final class ASMPrinter extends AssemblyAdapter<Node> {
if( n._type instanceof TypeMem || n._type instanceof TypeRPC ) return true;
if( postAlloc() && n instanceof CalleeSaveNode ) return true;
if( n instanceof MemMergeNode ) return true;
+ if( n instanceof EscapeNode ) return true;
return n.getClass()==ConstantNode.class;
}
- @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); }
+ @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal() ||
+ (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); }
@Override public boolean encoded() { return _enc!=null && _code._phase.ordinal()>=CodeGen.Phase.Encoding.ordinal(); }
@Override public int defaultSize() { return _code._mach==null ? 2 : _code._mach.defaultOpSize(); }
@Override public boolean littleEndian() { return _code._asmLittle; }
@Override public boolean prologue(Node n) { return ((FunNode)n)._frameAdjust!=0; }
@Override public byte[] bytes() { return encoded() ? _enc._bits.buf() : null; }
- @Override public int offset(Node n) {
- return encoded() && _enc._opStart!=null && n._nid<_enc._opStart.length ? _enc._opStart[n._nid] : -1;
- }
- @Override public int size(Node n) {
- return encoded() && _enc._opLen!=null && n._nid<_enc._opLen.length ? _enc._opLen[n._nid]&255 : 0;
- }
- private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos) {
+ @Override public int offset(Node n) { return encoded() ? _enc.opStart(n) : -1; }
+ @Override public int size(Node n) { return encoded() ? _enc.opLen(n)&255 : 0; }
+ private static ArrayList<Data> entries(Iterable<Encoding.Relo> relos, boolean ro) {
var data=new ArrayList<Data>();
for( Encoding.Relo r : relos ) {
+ if( r.readOnly()!=ro ) continue;
int align=1<<r._align;
int size=r._t instanceof TypeStruct ? (r._structSize+align-1)&-align : align;
- if( r._t instanceof TypeTuple t ) size=align*t._types.length;
data.add(new Data(r._t,r._t.str(),r._align,size));
}
return data;
@@ -78,8 +78,24 @@ public final class ASMPrinter extends AssemblyAdapter<Node> {
var pools=new ArrayList<Pool>();
if( !encoded() ) return pools;
int base=(codeEnd+15)&-16;
- if( !_enc._bigCons.isEmpty() && base<_enc._bits.size() )
- pools.add(new Pool("Constant Pool",_enc._bits.buf(),base,base,entries(_enc._bigCons.values())));
+ if( _enc._cpool.size()>0 )
+ pools.add(new Pool("Constant Pool",_enc._cpool.buf(),0,base,objects(true)));
+ base=(base+_enc._cpool.size()+15)&-16;
+ if( _enc._sdata.size()>0 )
+ pools.add(new Pool("Static Data",_enc._sdata.buf(),0,base,objects(false)));
return pools;
}
+ private ArrayList<Data> objects(boolean ro) {
+ var data=new ArrayList<Data>();
+ for( var obj : _enc._data.entries() )
+ if( !obj.external && obj.readOnly==ro )
+ data.add(new Data(obj,obj.symbol+": "+obj.type.str(),obj.alignment,obj.size));
+ return data;
+ }
+ // Retain this diagnostic entry point for checking that pool printing never lays out types.
+ static int printConstantPool(int address, SB sb, BAOS bits, HashMap<Node,Encoding.Relo> relos,
+ boolean ro, String name) {
+ sb.p(com.seaofnodes.print.ASMPrinter.pool(new Pool(name,bits.buf(),0,address,entries(relos.values(),ro))));
+ return address+bits.size();
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
index c912ab77..531a549a 100644
--- a/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
+++ b/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -14,6 +14,8 @@ public final class IRPrinter extends IRAdapter<Node> {
@Override public String type(Node n) { return n._type==null ? "" : n._type.str(); }
@Override public String inputMark(Node n, Node def) { return n instanceof MemMergeNode && def instanceof MemMergeNode ? "^" : " "; }
@Override public Kind kind(Node n) {
+ if( n instanceof StartCUNode ) return Kind.UNIT;
+ if( n instanceof StopCUNode ) return Kind.UNIT;
if( n instanceof StartNode ) return Kind.START;
if( n instanceof StopNode ) return Kind.STOP;
if( n instanceof FunNode ) return Kind.FUN;
@@ -47,15 +49,12 @@ public final class IRPrinter extends IRAdapter<Node> {
public static String prettyPrint(CodeGen code) {
var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>();
- var funs=new ArrayList<Node>();
- for( Node n : code._start._outputs ) if( n instanceof FunNode ) funs.add(n);
- units.add(new com.seaofnodes.print.IRPrinter.Unit<>(null,null,null,funs));
+ for( var cu : code._compunits.values() ) {
+ var funs=new ArrayList<Node>();
+ for( FunNode fun : code._linker )
+ if( fun!=null && fun._compunit==cu ) funs.add(fun);
+ units.add(new com.seaofnodes.print.IRPrinter.Unit<>(cu._cname,cu._start,cu._stop,funs));
+ }
return PRINT.program(code._start,code._stop,units);
}
- public static String _prettyPrint(CodeGen code) {
- if( code._cfg==null ) return prettyPrint(code);
- var blocks=new ArrayList<Node>();
- for( Node n : code._cfg ) blocks.add(n);
- return PRINT.scheduled(blocks);
- }
}
diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
index 8dc6ea47..9ca0ec51 100644
--- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
+++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -2,22 +2,30 @@ package com.seaofnodes.simple.print;
import com.seaofnodes.graph.GraphAdapter;
import com.seaofnodes.graph.GraphSnapshot.*;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.type.TypeMem;
-/** Chapter 24's view of the IR; browser, transport and layout are shared. */
+/** Chapter 25's view of the IR, including partially constructed parse-time graphs. */
public class SimpleGraphAdapter extends GraphAdapter<Node> {
+ @Override protected boolean show(Node n) {
+ if( n != CodeGen.CODE.ZERO && n != CodeGen.CODE.XCTRL ) return true;
+ for( int i=0; i<n.nOuts(); i++ )
+ if( n.out(i) != null ) return true;
+ return false; // The parser's keep is not a graph use.
+ }
+
@Override protected String edgeName(Node n, int i) {
return n instanceof ScopeNode scope && i < scope._vars.size()
- ? scope._vars.get(i)._name : null;
+ ? scope.var(i)._name : null;
}
@Override protected int edgeJump(Node n, int i) {
return n instanceof CallEndNode && i>0 && n.in(i) instanceof ReturnNode ret
? ref(ret.fun()) : 0;
}
- @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; }
+ @Override protected int projectionIndex(Node n) { return n instanceof Proj p ? p.idx() : -1; }
@Override protected boolean folding(Node n) {
if( n instanceof FunNode fun ) return fun.folding();
@@ -32,26 +40,41 @@ public class SimpleGraphAdapter extends GraphAdapter<Node> {
@Override protected Kind kind(Node n) {
if( n instanceof StopNode ) return Kind.STOP;
if( n instanceof ScopeNode ) return Kind.SCOPE;
+ if( n instanceof StartCUNode ) return Kind.UNIT;
if( n instanceof FunNode ) return Kind.FUN;
- if( n instanceof StartNode ) return Kind.CTRL;
+ // Start inherits Loop for whole-program analysis, but is not a source loop.
+ if( n instanceof StartNode ) return Kind.START;
if( n instanceof LoopNode ) return Kind.LOOP;
if( n instanceof RegionNode ) return Kind.REGION;
if( n instanceof PhiNode ) return Kind.PHI;
- return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA;
+ if( n instanceof CFGNode ) return Kind.CTRL;
+ return isMem(n) ? Kind.MEM : Kind.DATA;
}
@Override protected Role role(Node n, int i) {
- if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof CtrlNode || n instanceof XCtrlNode ) return Role.ASSOC;
- if( n instanceof PhiNode ) return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA;
- if( n instanceof ProjNode || n instanceof CProjNode )
+ if( n instanceof ScopeNode || n instanceof ConstantNode || n instanceof FunPtrNode ||
+ n instanceof CtrlNode || n instanceof XCtrlNode )
+ return Role.ASSOC;
+ if( n instanceof PhiNode )
+ return i == 0 ? Role.ASSOC : isMem(n) ? Role.MEM : Role.DATA;
+ if( n instanceof Proj )
return n instanceof CFGNode ? Role.CTRL : isMem(n) ? Role.MEM : Role.DATA;
+ // StartCU inherits Region, but slot 0 really connects it to Start.
+ if( n instanceof StartCUNode && i == 0 ) return Role.CTRL;
if( n instanceof RegionNode && i == 0 ) return Role.ASSOC;
+ // Preserve known slot roles even when an input is not attached/typed yet.
if( i == 0 || n instanceof RegionNode || n instanceof StopNode ) return Role.CTRL;
- if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) return Role.MEM;
+ if( n instanceof MemMergeNode ||
+ (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode)) ||
+ (n instanceof EscapeNode && i >= 2) ) return Role.MEM;
Node def = n.in(i);
+ if( def instanceof CFGNode ) return Role.CTRL;
if( def != null && isMem(def) ) return Role.MEM;
return Role.DATA;
}
- private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; }
+ private boolean isMem(Node n) {
+ // These Phi subclasses are memory even before their first type computation.
+ return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem;
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
index 3df1e0ea..bf8403e1 100644
--- a/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ b/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
@@ -7,7 +7,7 @@ import com.seaofnodes.simple.node.Node;
import java.util.ArrayList;
import java.io.IOException;
-/** Chapter 24's compilation context; capture and event bookkeeping are shared. */
+/** Chapter 25's compilation context; capture and event bookkeeping are shared. */
public class SimpleGraphObserver extends GraphCapture<Node> {
private CodeGen _code;
public SimpleGraphObserver() { super(new SimpleGraphAdapter()); }
diff --git a/src/main/java/com/seaofnodes/simple/type/Field.java b/src/main/java/com/seaofnodes/simple/type/Field.java
index 1f22cb05..beaa719c 100644
--- a/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -1,6 +1,7 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
import java.util.*;
@@ -8,6 +9,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 +19,28 @@ 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;
+ // Native C storage, outside the containing object's layout.
+ public boolean _extern;
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, boolean external ) { super(TFLD); init(fname,type,alias,xfinal,external); }
+ private Field init(String fname, Type type, int alias, boolean xfinal, boolean external ) {
_fname = fname;
_t = type;
_alias = alias;
_final = xfinal;
- _one = one;
+ _extern = external;
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, boolean external ) {
+ return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external);
}
// Malloc-from
- public Field malloc( ) { return malloc(_fname,null,_alias,_final,_one); }
+ public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); }
@Override Field free(Type t) {
Field f = (Field)t;
assert !f.isFree() && !f._terned;
@@ -52,27 +53,31 @@ 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 ) {
+ return make(fname,type,alias,xfinal,false);
+ }
+ public static Field make( String fname, Type type, int alias, boolean xfinal, boolean external ) {
+ Field f = malloc(fname,type,alias,xfinal,external);
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,_extern);
}
public Field makeFrom( boolean xfinal ) {
- return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_one);
+ return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern);
}
// 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 void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); }
+ 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 final Field EXT = make("test",TypeInteger.I32,-2,false,true);
+ public static void gather(ArrayList<Type> ts) { ts.add(TEST); ts.add(TEST2); ts.add(FLT); ts.add(EXT); }
@Override Field xmeet( Type that ) {
Field fld = (Field)that; // Invariant
@@ -81,17 +86,17 @@ 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);
+ if( _extern!=fld._extern ) return BOT;
+ return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern);
}
@Override
- Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_one); }
+ Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); }
@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,_extern);
(_dual = d)._dual = this; // Cross link duals
d._t = _t._terned ? _t.dual() : _t.rdual();
return d;
@@ -99,38 +104,51 @@ public class Field extends Type {
@Override boolean _isConstant() { return _t._isConstant(); }
@Override boolean _isFinal() { return _final && _t._isFinal(); }
- @Override Field _makeRO() { return 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,_extern); }
+ @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) ^ (_extern ? 2048 : 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 && _extern==f._extern && _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+external-storage flag
+ @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));
+ baos.write(_extern ? 1 : 0);
+ }
+ 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,bais.read()!=0);
+ }
@Override
public SB _print( SB sb, BitSet visit, boolean html ) {
- sb.p(_final?"":"!").p(_one?"$":"").p(_fname).p(":").p(_alias).p(" : ");
+ sb.p(_extern ? "extern C " : "").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 b9a6a1f3..980daf0f 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 _type==t._type && 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("Should not reach here: type has no children"); }
+ public void set( int idx, Type t ) { throw Utils.TODO("Should not reach here: type has no children"); }
+ 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,10 +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);
- // Constant arrays meet their integer element types.
+ // TypeConAry meet its element type is OK
if( this instanceof TypeConAry tcon && t instanceof TypeInteger ti ) return tcon.ymeet(ti);
if( t instanceof TypeConAry tcon && this instanceof TypeInteger ti ) return tcon.ymeet(ti);
- return Type.BOTTOM; // Mixing 2 unrelated types
+ // 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.
@@ -280,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
@@ -305,7 +322,7 @@ public class Type /*implements Cloneable*/ {
case TXCTRL-> TCTRL;
case TNIL -> TXNIL;
case TXNIL -> TNIL;
- default -> throw Utils.TODO(); // Should not reach here
+ default -> throw Utils.TODO("Should not reach here: unknown simple type");
});
}
@@ -319,24 +336,13 @@ 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; }
- // Writable array slots are invariant in their element access permissions.
- // Otherwise an alias could insert a readonly pointer into a writable slot.
- public boolean accessISA(Type dst) { return true; }
-
- // Is forward-reference
- public boolean isFRef() { return false; }
-
// Cap at limits
public Type oob() { return isHigh() ? TOP : BOTTOM; }
@@ -344,7 +350,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
@@ -357,7 +363,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());
@@ -381,6 +388,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 )
@@ -422,10 +432,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;
@@ -434,7 +446,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++ ) {
@@ -447,7 +459,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.
@@ -456,8 +468,12 @@ public class Type /*implements Cloneable*/ {
boolean _isConstant() { return _type==TNIL; }
// Are all reachable struct Fields are final?
+ // Writable array slots are invariant in their element access permissions.
+ // Otherwise an alias could insert a readonly pointer into a writable slot.
+ public boolean accessISA(Type dst) { return true; }
+
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;
@@ -465,31 +481,155 @@ public class Type /*implements Cloneable*/ {
}
Type _makeRO() { return this; }
- // Compute greatest lower bound in the lattice. If values are in memory,
- // ints and floats cannot widen.
- public final boolean isGLB(boolean mem) { return recurClose(recurOpen()._isGLB(mem)); };
- boolean _isGLB(boolean mem) {
- return switch(_type) {
- case TBOT -> false;
- case TNIL -> false;
- case TCTRL -> true;
- case TXCTRL -> false;
- case TXNIL -> false;
- case TTOP -> false;
- default -> throw Utils.TODO();
- };
+ /** Declared shape used when a value is published into mutable memory. */
+ public final Type makeStorage() {
+ return recurOpen()._makeStorage().recurClose();
}
- public final Type glb(boolean mem) {
- if( isGLB(mem) ) return this;
- Type glb = recurOpen()._glb(mem).recurClose();
- assert this.isa(glb);
- return glb;
+ Type _makeStorage() { assert is_simple(); return BOTTOM; }
+
+ // 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 _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; }
- Type _close() { return this; }
+ 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("Should not reach here: unknown simple type tag");
+ };
+ }
+
+ // 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("Should not reach here: unknown packed type tag");
+ };
+ }
+
+ // 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 0bc264fc..c9093e3b 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -1,13 +1,14 @@
package com.seaofnodes.simple.type;
-import java.io.ByteArrayOutputStream;
+import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
+import java.util.HashMap;
/**
* Represents a constant array of primitives
*/
-public abstract class TypeConAry<A> extends Type {
+public abstract class TypeConAry<A> extends TypeScalar {
boolean _any;
byte _widen;
// One of byte or int array
@@ -55,8 +56,7 @@ public abstract class TypeConAry<A> extends Type {
@Override public boolean isHigh() { return this==TOP; }
@Override boolean _isConstant() { return true; }
- @Override Type _glb(boolean mem) { return this; }
- @Override boolean _isGLB(boolean mem) { return true; }
+ @Override Type _makeStorage() { return this; }
// Meet-over-elements type
public Type elem() {
@@ -74,6 +74,17 @@ public abstract class TypeConAry<A> extends Type {
public abstract long at8(int idx);
public abstract int len();
@Override public abstract int log_size();
- public void write( ByteArrayOutputStream baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); }
+ public void write( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); }
+ // 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()]));
+ }
}
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
index 095cdeca..5877c309 100644
--- a/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
+++ b/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
@@ -1,6 +1,6 @@
package com.seaofnodes.simple.type;
-import java.io.ByteArrayOutputStream;
+import com.seaofnodes.simple.util.BAOS;
import java.util.Arrays;
public class TypeConAryB extends TypeConAry<byte[]> {
@@ -16,5 +16,5 @@ public class TypeConAryB extends TypeConAry<byte[]> {
@Override public String str() { return "[\""+new String(_ary)+"\"]"; }
@Override boolean eq(Type t) { return t instanceof TypeConAryB ary && _widen==ary._widen && 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/TypeFloat.java b/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
index f3de581c..d410e9cf 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;
@@ -51,7 +53,7 @@ public class TypeFloat extends Type {
case 0 -> ""+_con+((float)_con==_con ? "f" : "");
case 32 -> "f32";
case 64 -> "flt";
- default -> throw Utils.TODO();
+ default -> throw Utils.TODO("Should not reach here: unknown float size");
};
}
private boolean isF32() { return ((float)_con)==_con; }
@@ -88,16 +90,36 @@ public class TypeFloat extends Type {
return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual
}
- @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; }
- @Override Type _glb(boolean mem) {
- if( !mem ) return F64;
+ @Override boolean _isConstant() { return _sz==0; }
+
+ @Override Type _makeStorage() {
if( _isConstant() ) return isF32() ? F32 : F64;
return isHigh() ? dual() : this;
}
- @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("Should not reach here: unknown float type tag");
+ };
+ }
+
@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 50743f69..56f47bff 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,72 +46,107 @@ 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 ) {
- // Trim trailing args which match the implicit default.
- Type def = open ? Type.BOTTOM : Type.TOP;
- int nargs = sig.length;
- while( nargs > 0 && sig[nargs-1] == def )
- nargs--;
- if( nargs != sig.length )
- sig = nargs == 0 ? TEMPTY : Arrays.copyOf(sig,nargs);
+ 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); }
+ // Trim trailing args which match the default.
+ private static TypeFunPtr makeAdjusted( byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
+ int nargs = sig.length;
+ while( nargs > 0 && sig[nargs-1] == def )
+ nargs--;
+ if( nargs != sig.length )
+ sig = nargs == 0 ? ARG_EMPTY : Arrays.copyOf(sig,nargs);
+ return make(nil,open,sig,ret,fidxs);
+ }
+ public static TypeFunPtr make( boolean nil, Type[] sig, Type ret ) { return makeAdjusted((byte)(nil ? 3 : 2),true,sig,ret, XInt.FULL); }
+ public static TypeFunPtr make1( byte nil, boolean open, Type[] sig, Type ret, int fidx ) {
+ return makeAdjusted(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(_nil,_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);
+ ts.add(TEST);
+ ts.add(TEST0);
+ ts.add(BOT);
+ ts.add(MAIN);
+ ts.add(CALLOC);
+ ts.add(make((byte)2,false,TINT,TypeInteger.I32, XInt.SET1));
+ ts.add(make((byte)2,true ,TINT,TypeInteger.BOT, XInt.SET3));
+ ts.add(make1((byte)2,true,
+ new Type[]{TypeInteger.BOT},
+ TypeInteger.BOT, 65));
+ ts.add(make1((byte)2,true,
+ new Type[]{TypeInteger.BOT, TypeScalar.BOT},
+ TypeInteger.BOT, 65));
// Explicit trailing defaults must agree with the implicit argument tail.
for( boolean open : new boolean[]{false,true} ) {
- Type def = open ? Type.BOTTOM : Type.TOP;
- ts.add(make((byte)2,open,TINT,TypeInteger.I32,1));
- ts.add(make((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1));
- ts.add(make((byte)2,open,new Type[]{def,def},TypeInteger.I32,1));
- ts.add(make((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1));
+ Type def = open ? TypeScalar.BOT : TypeScalar.TOP;
+ ts.add(make1((byte)2,open,TINT,TypeInteger.I32,1));
+ ts.add(make1((byte)2,open,new Type[]{TypeInteger.BOT,def,def},TypeInteger.I32,1));
+ ts.add(make1((byte)2,open,new Type[]{def,def},TypeInteger.I32,1));
+ ts.add(make1((byte)2,open,new Type[]{def,TypeInteger.BOT},TypeInteger.I32,1));
}
}
+ 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;
@@ -125,7 +156,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++ )
@@ -137,7 +168,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();
@@ -147,53 +178,96 @@ 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 public boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); }
+ @Override public 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 _makeStorage() { return this; }
+
+ @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 && fidx() < 64 ) {
+ 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);
@@ -242,28 +316,26 @@ public class TypeFunPtr extends TypeNil {
return "{"+printFIDX()+"}";
}
+ @Override
SB _print(SB sb, BitSet visit, boolean html ) {
if( this==BOT ) return sb.p("FunBot");
if( this==BOT.dual() ) return sb.p("FunTop");
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 74fbfb9f..a0c2266d 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.
@@ -50,8 +52,8 @@ public class TypeInteger extends Type {
return this;
}
- public Type same_but_slightly_wider_than( Type minType ) {
- return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : minType;
+ public Type same_but_slightly_wider_than( ) {
+ return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT;
}
// Force max widen
@Override public Type widen() {
@@ -121,14 +123,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; }
@@ -157,7 +162,7 @@ public class TypeInteger extends Type {
return make(Math.min(_min,i._min), Math.max(_max,i._max), (byte)Math.max(_widen,i._widen));
}
- @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); }
+ @Override TypeInteger xdual() { return malloc(_max,_min,(byte)(3-_widen)); }
@Override public TypeInteger nonZero() {
if( isHigh() ) return this;
@@ -168,13 +173,42 @@ public class TypeInteger extends Type {
}
@Override public Type makeZero() { return ZERO; }
@Override boolean _isConstant() { return _min == _max; }
- @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; }
- @Override Type _glb(boolean mem) {
- if( !mem ) return BOT;
- // GLB is not well-defined in memory; depends on the size of the memory field
+ @Override Type _makeStorage() {
if( _isConstant() ) return BOT;
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("Should not reach here: unknown integer type tag");
+ };
+ }
@Override int hash() { return Utils.fold(_min) * Utils.fold(_max) + _widen; }
@Override public boolean eq( Type t ) {
diff --git a/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index d5ee8bd8..9ae68bcf 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,62 @@ 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 static final TypeMem TOP = make(1, Type.TOP );
- public static final TypeMem BOT = make(1, Type.BOTTOM);
+ 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));
+ }
+ public TypeMem escapesAliases(Type t) {
+ return make(_alias,_t,_one,_clz,_final, _escFs, escapeAlias(_escAs,t));
+ }
- public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); }
+ 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);
+ ts.add(SELF_MEM);
+ ts.add(START);
+ ts.add(make(2,TypeInteger.ZERO,true,false,true,XInt.EMPTY,XInt.make(2)));
+ ts.add(make(2,TypeInteger.BOT,false,false,false,XInt.EMPTY,XInt.FULL));
+ }
@Override
TypeMem xmeet(Type t) {
@@ -59,52 +117,137 @@ 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 and class facts are kept only when both sides agree.
+ // Finality is a low-side fact, and flips on the dual side.
+ boolean highAlias1 = (this._alias==1 && this._t.isHigh()) || (that._alias==1 && that._t.isHigh());
+ boolean xfinal = highAlias1 ? (_final & that._final) : (_final | that._final);
+ return make(alias, mt, _one & that._one, _clz & that._clz, xfinal, 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 ) { assert !d._t.isHigh(); d._final = false; }
return d;
}
+ @Override public TypeMem makeZero() { return TOP; }
@Override public boolean isHigh() { return _t.isHigh(); }
- @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 boolean _isConstant() { return _one && _clz && _alias!= 1 && _t._isConstant(); }
+ @Override public int log_size() { throw Utils.TODO("Should not reach here: memory has no scalar storage size"); }
+ @Override boolean _isFinal() { throw Utils.TODO("Should not reach here"); }
+ @Override TypeMem _makeStorage() { throw Utils.TODO("Should not reach here"); /*return make(_alias,_t._makeStorage(),_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) {
if( this==BOT ) return sb.p("MemBot");
if( this==TOP ) return sb.p("MemTop");
- 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() {
+ if( this==BOT ) return "MemBot";
+ if( this==TOP ) return "MemTop";
+ 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 && !ts.isAry()
+ ? 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 && !ts.isAry()
+ ? 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 9fc90544..175b1c63 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.*;
/**
@@ -29,22 +30,26 @@ public class TypeMemPtr extends TypeNil {
// Deep read-only access through this pointer; independent of slot finality.
public boolean _ro;
- 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; _ro=obj==TypeStruct.BOT; _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; _ro=obj==TypeStruct.BOT; _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) {
- return make(nil,obj,one,obj==TypeStruct.BOT);
+ return make(nil,obj,one,true);
}
- private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean ro) {
- TypeMemPtr tmp = malloc(nil, obj, one).access(ro);
+ public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub) {
+ return make(nil,obj,one, pub,obj==TypeStruct.BOT);
+ }
+ private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean pub, boolean ro) {
+ TypeMemPtr tmp = malloc(nil, obj, one, pub).access(ro);
TypeMemPtr t2 = tmp.intern();
if( t2==tmp ) return tmp;
return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
@@ -53,6 +58,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);
@@ -63,17 +70,22 @@ 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,_ro); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one, _pub,_ro); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
- @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one,_ro); }
- public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); }
+ /** Inferred mutable local: nullable, public and non-singleton. */
+ public TypeMemPtr makeVar() { return (TypeMemPtr)makeStorage(); }
+ /** Public-memory shape: nullable and non-singleton, preserving the referent. */
+ @Override TypeMemPtr _makeStorage() { return make(_nil,_obj._makeStorage(),false,true,_ro); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one, _pub,_ro); }
+ @Override public Type nonZero() { return makeFrom((byte)(_nil <= 1 ? 1 : 2)); }
private TypeMemPtr access(boolean ro) { _ro=ro; return this; }
public TypeMemPtr withAccess(boolean ro) {
if( _ro==ro ) return this;
- TypeMemPtr ptr = malloc(_nil, _obj, _one);
+ TypeMemPtr ptr = malloc(_nil, _obj, _one, _pub);
ptr._ro = ro;
TypeMemPtr res = ptr.intern();
if( res==ptr ) return ptr;
@@ -103,69 +115,73 @@ 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,_ro | that._ro);
+ // 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,_ro | that._ro);
}
@Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one).access(!_ro); }
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); }
@Override TypeMemPtr rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, _one).access(!_ro);
+ TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro);
(_dual = d)._dual = this; // Cross link duals
d._obj = _obj._terned ? _obj.dual() : _obj.rdual();
return d;
}
// RHS is NIL; do not deep-dual when crossing the center line
- @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,_ro); }
+ @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,true,_ro); }
- @Override boolean _isConstant() { return _one && _obj._isConstant(); }
+ @Override boolean _isConstant() { return _one; }
@Override boolean _isFinal() { return _ro; }
@Override TypeMemPtr _makeRO() { return withAccess(true); }
- @Override boolean _isGLB(boolean mem) { return _ro && _nil==3 && !_one && _obj.isGLB2(); }
- @Override TypeMemPtr _glb(boolean mem) { return make((byte)3,_obj.glb2(),false,true); }
- @Override TypeMemPtr _close() { return makeFrom(_obj._close()); }
-
+ @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); }
- // 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 || (_ro && !that._ro) ) 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 Type _upgradeType(HashMap<String,Type> TYPES) {
+ return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
}
- // Is forward-reference
- @Override public boolean isFRef() { return _obj.isFRef(); }
-
@Override public int log_size() { return 3; } // (1<<3)==8-byte pointers
- @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); }
+ @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0) ^ (_pub ? 4096 : 0); }
@Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj == ptr._obj;
+ return _ro==ptr._ro && 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 _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj);
+ return _ro==ptr._ro && 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, public/private and read-only access
+ @Override int TAGOFF() { return 16; }
+ @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)
+ + (!_ro ? 0 : 8) );
+ }
+ static TypeMemPtr packed( int tag, BAOS bais ) {
+ return malloc((byte)((tag&1)+2),null,(tag&2)==2,(tag&4)==4).access((tag&8)==8);
+ }
@Override public String str() {
if( this==BOT ) return "PtrBot";
if( this==TOP ) return "PtrTop";
if( this== NOTBOT) 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..2c1c1d76 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
@@ -51,7 +51,6 @@ public abstract class TypeNil extends Type {
public boolean nullable() { return _nil==3; }
@Override boolean _isConstant() { return false; }
- @Override Type _glb(boolean mem) { return Type.NIL; }
final String q() { return _nil == 1 || _nil == 2 ? "" : "?"; }
final String x() { return isHigh() ? "~" : ""; }
diff --git a/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/src/main/java/com/seaofnodes/simple/type/TypePtr.java
index 75478119..38941ed1 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.
@@ -23,7 +26,7 @@ public class TypePtr extends TypeNil {
public static void gather(ArrayList<Type> ts) { ts.add(PTR); ts.add(NPTR); }
- TypeNil makeFrom(byte nil) { throw Utils.TODO(); }
+ TypeNil makeFrom(byte nil) { throw Utils.TODO("Should not reach here: plain pointer nil state is canonical"); }
@Override public TypeNil xmeet(Type t) {
TypePtr that = (TypePtr) t;
@@ -47,8 +50,16 @@ public class TypePtr extends TypeNil {
// 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
@Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
- boolean _isGLB(boolean mem) { return this==PTR; }
- @Override TypePtr _glb(boolean mem) { return PTR; }
+ @Override TypePtr _makeStorage() { 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..8a12f50a 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 _isGLB(boolean mem) { return true; }
+ @Override boolean _isConstant() { return XInt.isConstant(_rpcs); }
+ @Override TypeRPC _makeStorage() { return this; }
+
+ // 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..eaba0a1c
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
@@ -0,0 +1,57 @@
+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 Type _makeStorage() { 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 bb6ce4cb..82c37a12 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,74 +77,196 @@ 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));
}
- public TypeStruct makeHigh() {
+ // 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 makeInit() {
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);
+ for( int i=0; i<_fields.length; i++ ) {
+ if( _fields[i]._extern ) { fs[i] = _fields[i]; continue; }
+ Type zero = _fields[i]._t.makeZero();
+ fs[i] = _fields[i].makeFrom(_fields[i]._t != Type.BOTTOM && 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() {
- Object key = isAry() ? _uid : _name;
- TypeStruct ts = (TypeStruct)VISIT.get(key);
+ @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(key,false);
+ TypeStruct base = name==null ? (TypeStruct)TYPES.get(_name) : this;
+ assert base != null;
+ 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);
@@ -129,14 +274,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);
@@ -145,7 +291,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;
}
@@ -171,28 +317,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 || f0._extern != f1._extern )
return BOT;
}
@@ -206,7 +351,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;
@@ -227,12 +372,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
@@ -240,18 +383,23 @@ 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
+ assert !_open; // who is asking about constant open structs? if( _open ) return false; // Infinite BOT fields
+ assert !VISIT.containsKey(_uid); //expect constants to not include self-cycles. if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant
VISIT.put(_uid,this);
// Check all fields for being constant
for( Field field : _fields )
@@ -262,89 +410,76 @@ 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 ts;
+ @Override TypeStruct _makeStorage() {
+ return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage);
}
// log_size for a struct is not defined, unless its exactly some power of
// 2. *Total size* is well-defined, and is available in the offsets.
- @Override public int log_size() { throw Utils.TODO(); }
+ @Override public int log_size() { throw Utils.TODO("Should not reach here: structs have no scalar log size"); }
@Override public int size() { return offset(_fields.length); }
@Override public int alignment() {
int align = 0;
for( Field f : _fields )
- align = Math.max(align, f._t.alignment());
+ if( !f._extern ) align = Math.max(align, f._t.alignment());
return align;
}
// 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();
@@ -353,7 +488,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++ )
@@ -381,15 +515,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 ) {
@@ -399,12 +553,17 @@ public class TypeStruct extends Type {
if( isAry() && field("[]")._t instanceof TypeConAry con )
return sb.p(con.str());
sb.p(_name);
- if( html || isAry() )
+ if( isAry() )
+ // Array names deliberately canonicalize mutable and immutable
+ // element variants. The verbose debugger print must retain the
+ // otherwise invisible lattice coordinate.
+ return sb.p(_fields[1]._final ? "[final]" : "[mutable]");
+ if( html )
return sb;
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("}");
}
@@ -415,7 +574,10 @@ public class TypeStruct extends Type {
}
- 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();
@@ -453,7 +615,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 )
+ if( !_fields[i]._extern )
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.
@@ -465,7 +627,7 @@ 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;
+ if( _fields[i]._extern ) { _offs[idx++] = -1; 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..075698dc 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() {
@@ -67,11 +94,18 @@ public class TypeTuple extends Type {
return false;
return true;
}
- @Override boolean _isFinal() { throw Utils.TODO(); }
+ @Override boolean _isFinal() { throw Utils.TODO("Should not reach here: tuples are not stored object values"); }
+
+ @Override TypeMemPtr _makeRO() { throw Utils.TODO("Should not reach here: tuples cannot be made read-only"); }
- @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 log_size() { throw Utils.TODO("Should not reach here: tuples have no scalar storage size"); }
@Override public int alignment() {
assert isConstant();
int align = 0;
@@ -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..752c1419
--- /dev/null
+++ b/src/main/java/com/seaofnodes/simple/type/XInt.java
@@ -0,0 +1,268 @@
+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);
+ int max = Math.max(xs.length,ys.length);
+ for( int i=1; i<max; i++ ) {
+ int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
+ int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
+ if( (x | y)!=x ) isX=false;
+ if( (x | y)!=y ) isY=false;
+ }
+ if( isX ) return xs;
+ if( isY ) return ys;
+ // Get a free one
+ int[] ms = free(max);
+ for( int i=1; i<min; i++ )
+ ms[i] = xs[i] | ys[i];
+ for( int i=min; i<ms.length; i++ ) {
+ int x = i < xs.length ? xs[i] : isHigh(xs) ? -1 : 0;
+ int y = i < ys.length ? ys[i] : isHigh(ys) ? -1 : 0;
+ ms[i] = x | y;
+ }
+ 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 6a6d3ca3..7705a187 100644
--- a/src/main/smp/sys.smp
+++ b/src/main/smp/sys.smp
@@ -1,199 +1,11 @@
-// -*- 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);
- };
- };
-};
+val adt = adt; // Abstract Data Types
+val ary = ary; // Array utilities
+val aryi64 = aryi64; // Variable sized array of i64
+val aryu8 = aryu8; // Variable sized array of u8
+val char = char; // Character utilities
+val io = io; // IO library
+val libc = libc; // libc FFI bindings
+val scan = scan; // A simple string parser
diff --git a/src/main/smp/sys/adt.smp b/src/main/smp/sys/adt.smp
new file mode 100644
index 00000000..a4fe40bf
--- /dev/null
+++ b/src/main/smp/sys/adt.smp
@@ -0,0 +1,3 @@
+
+// Abstract data types.
+val bitset = bitset;
diff --git a/src/main/smp/sys/adt/bitset.smp b/src/main/smp/sys/adt/bitset.smp
new file mode 100644
index 00000000..d02cd2fe
--- /dev/null
+++ b/src/main/smp/sys/adt/bitset.smp
@@ -0,0 +1,57 @@
+
+// A set of non-negative integers, represented one bit per value.
+struct bitset {
+ i64[] !_bits;
+
+ new bitset = { ->
+ _bits = new i64[1];
+ };
+
+ // Return 0 for missing, non-zero for true
+ val get = { int bit ->
+ if( (bit >> 6) >= _bits# ) return false;
+ i64 mask = 1 << (bit & 63);
+ return (_bits[bit >> 6] & mask);
+ };
+
+ val _grow = { int bit ->
+ int x = bit >> 6;
+ int len = _bits#;
+ if( x < len ) return self;
+ while( x >= len ) len *= 2;
+ var bits = new i64[len];
+ for( int i=0; i<_bits#; i++ )
+ bits[i] = _bits[i];
+ _bits = bits;
+ return self;
+ };
+
+ // Set
+ val set = { int bit ->
+ i64 mask = 1 << (bit & 63);
+ _grow(bit)._bits[bit >> 6] |= mask;
+ return self;
+ };
+
+ // Test and set
+ val tset = { int bit ->
+ int idx = bit >> 6;
+ i64 mask = 1 << (bit & 63);
+ if( _grow(bit)._bits[idx] & mask )
+ return true; // Already set
+ _bits[idx] |= mask;
+ return false; // Was false (and now set)
+ };
+
+ val clr = { int bit ->
+ if( (bit >> 6) >= _bits# ) return self;
+ i64 mask = 1 << (bit & 63);
+ _bits[bit >> 6] &= (mask ^ -1);
+ return self;
+ };
+
+ val clear = { ->
+ for( int i=0; i<_bits#; i++ ) _bits[i] = 0;
+ return self;
+ };
+};
diff --git a/src/main/smp/sys/ary.smp b/src/main/smp/sys/ary.smp
new file mode 100644
index 00000000..8312e94c
--- /dev/null
+++ b/src/main/smp/sys/ary.smp
@@ -0,0 +1,24 @@
+
+// Array utilities
+
+// Copy byte array contents. Copies the shorter of the buffers and len
+val copy = { u8[] dst, int doff, u8[~] src, int soff, int len ->
+ if( len > dst# - doff ) len = dst# - doff;
+ if( len > src# - soff ) len = src# - soff;
+ for( int i=0; i<len; i++ )
+ dst[i+doff] = src[i+soff];
+ return dst;
+};
+
+// Compare lexigraphically array contents. Compares up to shorter array or
+// len. Returns <0 or >0 for first non-matching byte. Returns <0 or >0 for
+// the larger array after that, or 0 if both arrays are fully equal.
+val cmp = { u8[~] src1, u32 off1, u8[~] src2, u32 off2, u32 len ->
+ u32 len1 = len > src1# - off1 ? src1# - off1 : len;
+ u32 len2 = len > src2# - off2 ? src2# - off2 : len;
+ u32 max = len1 > len2 ? len2 : len1;
+ for( int i=0; i<max; i++ )
+ if( src1[off1+i] != src2[off2+i] )
+ return src1[off1+i] - src2[off2+i]; // Bytes determine compare
+ return len1 - len2; // Length determines compare
+};
diff --git a/src/main/smp/sys/aryi64.smp b/src/main/smp/sys/aryi64.smp
new file mode 100644
index 00000000..98b72605
--- /dev/null
+++ b/src/main/smp/sys/aryi64.smp
@@ -0,0 +1,82 @@
+
+// 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;
+ };
+
+ val get = { int index -> return buf[index]; };
+
+ // Write the bracketed array-list form to aryu8.
+ val str = { ->
+ // TODO: clunky default constructor syntax
+ var str = new aryu8().add('[');
+ for( int i=0; i<len; i++ )
+ str.num(buf[i]).add(',').add(' ');
+ if( len ) str.undo(2);
+ return str.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.bad ) return null;
+ if( !s.match(',') )
+ break;
+ };
+ // Close the list
+ if( !s.match(']') ) return null;
+ s.skip();
+ return s.x == s.buf# ? nums : null;
+};
+
+val copy = { i64[] dst, int doff, i64[~] 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/aryu8.smp b/src/main/smp/sys/aryu8.smp
new file mode 100644
index 00000000..21cf920b
--- /dev/null
+++ b/src/main/smp/sys/aryu8.smp
@@ -0,0 +1,87 @@
+
+// Ary/Vector of u8s - a resizable array of bytes.
+struct aryu8 {
+ u32 !len = 0;
+ u8[] !buf;
+
+ new aryu8 = { -> buf = new u8[1]; };
+
+ // 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[~] xs ->
+ _grow(xs#);
+ ary.copy(buf,len,xs,0,xs#);
+ len += xs#;
+ 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);
+ };
+};
+
+
+// Parse one integer, ignore trailing data; returns 0 with an empty string.
+// If you want a more robust parsing, use the `scan` class
+val atoi = { u8[~] str ->
+ int x = 0;
+ while( x < str# && str[x] <= ' ' ) x++;
+ if( x == str# ) return 0;
+ bool neg = false;
+ if( str[x]=='-' ) { neg=true; x++; }
+ i64 n = 0;
+ while( x < str# && '0' <= str[x] <= '9' )
+ n = n*10 + str[x++]-'0';
+ return neg ? -n : n;
+};
+
+// Zero-terminate string for libc strings. No error checking, just copies and
+// appends a zero.
+{ u8[~] -> u8[] } str0 = { u8[~] str ->
+ u8[] !strz = new u8[str#+1];
+ ary.copy(strz,0,str,0,str#);
+ strz[str#] = '\0';
+ strz;
+};
diff --git a/src/main/smp/sys/char.smp b/src/main/smp/sys/char.smp
new file mode 100644
index 00000000..22ca4213
--- /dev/null
+++ b/src/main/smp/sys/char.smp
@@ -0,0 +1,8 @@
+
+val toupper = { u8 c ->
+ return c >= 'a' && c <= 'z' ? c - ('a' - 'A') : c;
+};
+
+val tolower = { u8 c ->
+ return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
+};
diff --git a/src/main/smp/sys/io.smp b/src/main/smp/sys/io.smp
new file mode 100644
index 00000000..d6263ffe
--- /dev/null
+++ b/src/main/smp/sys/io.smp
@@ -0,0 +1,64 @@
+
+// Simple I/O Library
+
+// Write byte array to stdout
+val p = { u8[~] str -> write(1,str); };
+val p_noInline = { u8[~] str -> write(1,str); };
+
+// Return fd contents as a u8[].
+val read = { i32 fd ->
+ var buf = new u8[8*1024];
+ i64 ptr = buf;
+ int len = 0;
+ while( true ) {
+ i32 rez = libc.read(fd,ptr,buf#);
+ if( rez > buf# ) libc._exit(libc.errno());
+ len += rez;
+ if( rez < buf# )
+ break;
+ libc._exit(-2); // Need to implement for more than 8K input
+ }
+ var out = new u8[len];
+ ary.copy(out,0,buf,0,len);
+};
+
+// Write all of str to fd.
+val write = { i32 fd, u8[~] str ->
+ i64 ptr = str; // Convert array pointer to a i64 for the native call
+ libc.write(fd,ptr,str#);
+};
+
+// Write all of str to fd.
+val writeOff = { i32 fd, u8[~] str, u32 off, u32 len ->
+ i64 ptr = str; // Convert array pointer to a i64 for the native call
+ ptr += off; // Skip offset
+ // Cap at available length
+ if( len > str# - off ) len = str# - off;
+ // Do the write
+ libc.write(fd,ptr,len);
+};
+
+// Return stdin as a u8[].
+val stdin = { -> read(0); };
+
+// Create file for writing. Return fd, or -1 and errno has the error code
+val create = { u8[~] str ->
+ i64 ptr = sys.aryu8.str0(str);
+ i32 perms = 6*64 + 6*8 + 6; // 0666
+ return libc.creat(ptr, perms);
+};
+
+// Open file for reading. Return fd, or -1 and errno has the error code
+val open = { u8[~] str ->
+ i64 ptr = sys.aryu8.str0(str);
+ return libc.open(ptr, sys.libc.O_RDONLY, 0);
+};
+
+// Close an open fd. Return 0, or -1 and errno has the error code
+val close = { i32 fd -> return libc.close(fd); };
+
+// Remove a file. Return 0, or -1 and errno has the error code
+val remove = { u8[~] str ->
+ i64 ptr = sys.aryu8.str0(str);
+ return libc.unlink(ptr);
+};
diff --git a/src/main/smp/sys/libc.smp b/src/main/smp/sys/libc.smp
new file mode 100644
index 00000000..17ee65cb
--- /dev/null
+++ b/src/main/smp/sys/libc.smp
@@ -0,0 +1,44 @@
+
+// libc bindings:
+// https://www.man7.org/linux/man-pages/man2/<libc call>.2.html
+
+// The buffers are int-versions of zero-terminated strings.
+// int buf = aryu8.str0("some_file");
+// buf mode perm-> fd
+{ i64 i32 i32 -> i32 } open = "C";
+// buf perm -> fd
+{ i64 i32 -> i32 } creat = "C";
+
+// fd buf len -> len
+{ i32 i64 u32 -> i32 } write = "C";
+// fd buf len -> len
+{ i32 i64 u32 -> i32 } read = "C";
+// fd -> err
+{ i32 -> i32 } close = "C";
+// path -> err
+{ i64 -> i32 } unlink = "C";
+
+i32 O_RDONLY = 0;
+i32 O_WRONLY = 1;
+i32 O_RDWR = 2;
+i32 O_CREAT = 512; // 02000 on x86_64-pc-cygwin
+i32 O_TRUNC = 1024; // 04000 on x86_64-pc-cygwin
+
+i32 STDIN = 0;
+i32 STDOUT = 1;
+i32 STDERR = 2;
+
+// status
+{ i32 -> i32 } _exit = "C";
+// errno is not an ordinary C data symbol; read it through the native runtime.
+{ i32 } simple_errno = "C";
+val errno = { -> simple_errno(); };
+// 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/main/smp/sys/scan.smp b/src/main/smp/sys/scan.smp
new file mode 100644
index 00000000..44bedaaa
--- /dev/null
+++ b/src/main/smp/sys/scan.smp
@@ -0,0 +1,39 @@
+
+// Scanner; a simple string parser.
+struct scan {
+ int !x; // Cursor
+ bool !bad; // True after a malformed token
+ u8[~] buf; // Buffer to parse
+
+ 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( x >= buf# ) return false;
+ if( buf[x] != c ) return false;
+ x++;
+ return true;
+ };
+
+ // Parse a simple number
+ val number = { ->
+ skip();
+ bool neg = match('-');
+ if( x >= buf# || buf[x] < '0' || '9' < buf[x] ) {
+ bad = true;
+ return 0;
+ }
+ i64 n = 0;
+ while( x < buf# && '0' <= buf[x] & buf[x] <= '9' )
+ n = n*10 + (buf[x++] - '0');
+ return neg ? -n : n;
+ };
+};
diff --git a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
index a3b98fe0..c2713e4f 100644
--- a/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
+++ b/src/test/java/com/seaofnodes/simple/BrainFuckTest.java
@@ -4,10 +4,8 @@ import com.seaofnodes.isa.eval.EvalRisc5;
import com.seaofnodes.isa.eval.EvalArm64;
-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;
@@ -27,24 +25,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];
@@ -52,7 +50,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];
@@ -65,9 +63,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, 25, 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 8923566a..f1e4af12 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter03Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter03Test.java
@@ -59,7 +59,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 ce808084..1132375e 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter04Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter04Test.java
@@ -91,7 +91,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 6ff4ac95..79af6ae7 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
@@ -177,7 +180,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 0f4ca169..cdafb43b 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter10Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter10Test.java
@@ -2,8 +2,6 @@ package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
import org.junit.Test;
-import com.seaofnodes.simple.node.*;
-import java.util.BitSet;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -15,35 +13,40 @@ public class Chapter10Test {
} ) {
String src = "struct S { int x; }; " + body;
var code = new CodeGen(src).parse().opto().typeCheck();
+ // This chapter also has constructor/class-init Returns in the CompUnit.
+ var entry = (com.seaofnodes.simple.node.FunNode)code._start.uctrl();
org.junit.Assert.assertTrue(body,
- code.expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr);
+ entry.ret().expr()._type instanceof com.seaofnodes.simple.type.TypeMemPtr);
}
}
- // Issue #246: null-check guards start in Chapter 10; arrays arrive in Chapter 15.
private static final String NULLABLE_POINT_SOURCE = """
- struct Point { int x; };
+ struct Point { int x; new Point = { int v -> x = v; }; };
!Point?[] !points = new !Point?[2];
- points[arg] = new Point { x = 42; };
+ points[arg] = new Point(42);
Point? p = points[1];
""";
@Test
public void testNullGuards() {
+ // Issue #246: != null lowers to !!p. Follow every negation, on either arm.
for( String body : new String[] {
"if (p != null) return p.x; return -1;",
"if (null != p) return p.x; return -1;",
"if (!!!!p) return p.x; return -1;",
"if (!!!p) return -1; return p.x;",
- "int b = !!p; if (b) return p.x + b - 1; return -1;",
+ "val b = !!p; if (b) return p.x + b - 1; return -1;",
"if (!(p == null || arg == 0)) return p.x; return -1;"
- } ) {
- CodeGen code = new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
- assertEquals(body,"-1",Eval2.eval(code,0));
- assertEquals(body,"42",Eval2.eval(code,1));
- }
+ } )
+ for( long seed : new long[] { 1, 42, 126 } ) {
+ CodeGen code = new CodeGen(NULLABLE_POINT_SOURCE+body,seed,true)
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals(body,"-1",Eval2.eval(code,0));
+ assertEquals(body,"42",Eval2.eval(code,1));
+ code.driver(CodeGen.Phase.LocalSched);
+ }
}
@Test
@@ -70,13 +73,10 @@ public class Chapter10Test {
"if (!!p) { int x = p.x; } return p.x;"
} ) {
try {
- new CodeGen(NULLABLE_POINT_SOURCE+body).parse().opto().typeCheck();
+ new CodeGen(NULLABLE_POINT_SOURCE+body).driver(CodeGen.Phase.TypeCheck);
fail(body);
- } catch( RuntimeException e ) {
- // Known null is rejected during parsing in these chapters.
- String expected = body.startsWith("if (p == null)")
- ? "Accessing unknown field 'x' from 'null'" : "Might be null accessing 'x'";
- assertEquals(body,expected,e.getMessage());
+ } catch( Parser.ParseException e ) {
+ assertEquals(body,"Might be null accessing 'x'",e.getMessage());
}
}
}
@@ -109,15 +109,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;
@@ -139,7 +139,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;]; return Test.Vector2D; ]", code.print());
}
@Test
@@ -151,8 +151,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
@@ -177,7 +177,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;]; return Test.Bar; ]", code.print());
}
@Test
@@ -190,7 +190,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
@@ -216,28 +216,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;
@@ -299,7 +299,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;]; return Test.Iter; ]", code.print());
}
@@ -318,44 +318,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;]; return Test.s0; ]", 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;]; return Test.s0; ]", code.print());
+ }
+
+
@Test
public void testBug3() {
CodeGen code = new CodeGen("""
@@ -364,7 +383,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
@@ -394,8 +413,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;]; return Test.s0; ]", code.print());
}
@Test
@@ -423,7 +442,7 @@ s0 v1 = v0;
return v1;
""");
code.parse().opto();
- assertEquals("return (const)s0;", code.print());
+ assertEquals("Stop[ return (const)Test.s0; return MEM[ 2:.f0=0;]; return Test.s0; ]", code.print());
}
@@ -470,121 +489,4 @@ return 0;
assertEquals("return 0;", code.print());
}
- @Test public void testReadBeforeStores() {
- var code = new CodeGen("""
- struct S { int x; int y; };
- !S !a = new S; !S !b = new S;
- a.x = 11; b.x = 22;
- !S !p = a; if (arg) p = b;
- int before = p.x;
- a.y = 55; b.y = 66;
- a.x = 33; b.x = 44;
- return before*100 + p.x;
- """).parse().opto();
- StopNode stop = code._stop;
- assertEquals("1133",Eval2.eval(code,0));
- assertEquals("2244",Eval2.eval(code,1));
- }
-
- static final String NESTED_MEMORY = """
- struct S { int x; int y; int z; };
- !S !s = new S;
- s.x = 5; s.y = 7; s.z = 11;
- while (arg > 0) {
- s.x = s.x + 1;
- int j = 3;
- while (j > 0) {
- j = j - 1;
- if (j == 1) continue;
- s.y = s.y + arg;
- if (arg == 2) break;
- s.z = s.z + 1;
- }
- arg = arg - 1;
- }
- return s;
- """;
-
- @Test public void testMemoryAcrossNestedLoops() {
- var code = new CodeGen(NESTED_MEMORY).parse().opto();
- StopNode stop = code._stop;
- for (int arg=0; arg<7; arg++) {
- assertEquals("S{x="+(5L+arg)+",y="+(7L+arg*(arg+1)-(arg>=2 ? 2 : 0))+
- ",z="+(11L+2*arg-(arg>=2 ? 2 : 0))+"}",Eval2.eval(code,arg));
- }
- }
-
- @Test public void testMemoryAtEarlyReturns() {
- var code = new CodeGen("""
- struct S { int x; int y; int z; };
- !S !s = new S;
- s.x = 3; s.y = 5; s.z = 7;
- if (arg) { s.x = 11; s.z = 13; return s; }
- s.y = 17;
- return s;
- """).parse().opto();
- StopNode stop = code._stop;
- for (int arg=0; arg<2; arg++) {
- assertEquals(arg==0 ? "S{x=3,y=17,z=7}" : "S{x=11,y=5,z=13}",Eval2.eval(code,arg));
- }
- }
-
- @Test public void testKeepLoadsAtMerge() {
- var code = new CodeGen("""
- struct S { int x; };
- !S !a = new S; !S !b = new S;
- a.x = arg; b.x = arg+1;
- !S !p = a; !S !q = b;
- if (arg<0) { p=b; q=a; }
- int v;
- if (arg>1) v=p.x; else v=q.x;
- return v;
- """).parse().opto();
- StopNode stop = code._stop;
- // The one-step safety check stops at the memory aggregate.
- assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
- assertEquals("-1",Eval2.eval(code,-1));
- assertEquals("1",Eval2.eval(code,0));
- assertEquals("3",Eval2.eval(code,3));
- }
-
- @Test public void testDropStores() {
- var code = new CodeGen("""
- struct S { int x; };
- !S !s = new S;
- if (arg) s.x=arg+1; else s.x=arg+2;
- return s;
- """).parse().opto();
- StopNode stop = code._stop;
- assertEquals(1,countMemoryNodes(stop,StoreNode.class,new BitSet()));
- assertEquals("S{x=2}",Eval2.eval(code,0));
- assertEquals("S{x=4}",Eval2.eval(code,3));
- }
-
- @Test public void testKeepReadsBeforeWrites() {
- var code = new CodeGen("""
- struct S { int x; int y; };
- !S !a = new S; !S !b = new S;
- a.x=arg; b.x=arg+1;
- !S !p=a; !S !q=b;
- if (arg<0) { p=b; q=a; }
- int v;
- if (arg>1) { v=p.x; p.x=41; p.y=5; }
- else { v=q.x; q.x=42; }
- return v;
- """).parse().opto();
- StopNode stop = code._stop;
- assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
- assertEquals("-1",Eval2.eval(code,-1));
- assertEquals("1",Eval2.eval(code,0));
- assertEquals("3",Eval2.eval(code,3));
- }
-
- private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) {
- if (n==null || seen.get(n._nid)) return 0;
- seen.set(n._nid);
- int cnt=kind.isInstance(n) ? 1 : 0;
- for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen);
- return cnt;
- }
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter11Test.java b/src/test/java/com/seaofnodes/simple/Chapter11Test.java
deleted file mode 100644
index 3d5d2686..00000000
--- a/src/test/java/com/seaofnodes/simple/Chapter11Test.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package com.seaofnodes.simple;
-
-import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-public class Chapter11Test {
- @Test public void testIndependentField() {
- var code = new CodeGen("""
- struct S { int x; int y; };
- !S !s = new S;
- s.x = 42;
- while (arg > 0) { s.y = s.y + arg; arg = arg - 1; }
- return s.x;
- """).parse().opto();
- var stop = code._stop;
- assertEquals("return 42;",stop.toString());
- assertEquals("42",Eval2.eval(code,5));
- assertNull(stop.walk(n -> n instanceof LoadNode ? n : null));
- }
-
- @Test public void testParallelMemoryPhis() {
- var code = new CodeGen("""
- struct S { int x; int y; int z; };
- !S !a = new S; !S !b = new S;
- !S !p = a; if (arg) p = b;
- while (arg > 0) {
- a.x = p.x + arg;
- a.y = p.y + 2;
- a.z = p.z + 3;
- arg = arg - 1;
- }
- return a;
- """).parse().opto();
- var stop = code._stop;
- int[] phis = {0};
- stop.walk(n -> {
- // Store sinking can replace field memory Phis with value Phis.
- if (n instanceof PhiNode) phis[0]++;
- if (n instanceof MemMergeNode m && m.in(1) instanceof BulkMemPhiNode b)
- for(int a=b._aliases.nextSetBit(0); a>=0; a=b._aliases.nextSetBit(a+1))
- assertTrue(a<m.nIns() && m.in(a)!=null);
- assertFalse(n instanceof PhiNode && n.isMem() &&
- !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode));
- return null;
- });
- assertTrue(phis[0]>=4); // Three fields and the loop counter.
- assertEquals("S{x=1,y=2,z=3}",Eval2.eval(code,4));
- }
-
- @Test public void testLateSliceWorklist() {
- // Seed 97 exposed a store selecting a bulk predecessor without queuing
- // it. Keep both the worklist assertion and the final heap check.
- for (int seed : new int[]{0,97,123,456}) {
- var code = new CodeGen(Chapter10Test.NESTED_MEMORY,com.seaofnodes.simple.type.TypeInteger.BOT,seed,false).parse();
- code.opto();
- assertEquals("S{x=9,y=25,z=17}",Eval2.eval(code,4));
- }
- }
-}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
index ac3b81ec..07cd3831 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter12Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter12Test.java
@@ -12,11 +12,11 @@ public class Chapter12Test {
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
@@ -42,22 +42,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));
}
@@ -65,17 +65,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());
}
@@ -83,8 +83,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();
@@ -96,12 +96,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 Test.M; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:.next=Parm_m(Test.N.N,*Test.M {i64 !m; }); 7:.i=0;]; return MEM[ 2:___ 3:___ 4:___ 5:___ 6:___ 7:.i=0;]; return Test.N; ]", code.print());
}
@Test
@@ -109,12 +109,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
@@ -134,20 +134,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();
@@ -159,10 +159,10 @@ return 0;
public void testForwardRef0() {
CodeGen code = new CodeGen(
"""
-struct S1 { !S2? !s; };
+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()); }
}
@@ -170,12 +170,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 Test._S2;", code.print());
}
@Test
@@ -199,9 +199,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;
@@ -211,40 +211,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;
@@ -277,7 +277,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/Chapter13Test.java b/src/test/java/com/seaofnodes/simple/Chapter13Test.java
index 62bc1654..7cfa5817 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter13Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter13Test.java
@@ -1,6 +1,5 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.print.IRPrinter;
import org.junit.Test;
@@ -46,13 +45,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;
@@ -67,10 +83,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;
@@ -88,19 +104,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());
}
@@ -108,10 +124,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;
@@ -125,27 +141,29 @@ 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());
+ for( int arg : new int[]{-1,0,1,3,100} )
+ assertEquals("0",Eval2.eval(code,arg));
}
@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;
""");
@@ -157,9 +175,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;
@@ -175,9 +193,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;
@@ -195,26 +213,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;
@@ -225,7 +243,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/Chapter14FloatTest.java b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
index 6377bc14..c1354e34 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java
@@ -1,11 +1,9 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.node.StopNode;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
public class Chapter14FloatTest {
@Test public void testNumericAssignments() {
@@ -53,22 +51,23 @@ public class Chapter14FloatTest {
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
@@ -83,8 +82,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/Chapter14Test.java b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
index c342eeed..086bb824 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter14Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter14Test.java
@@ -94,12 +94,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 d0a42ccf..f363847a 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter15Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter15Test.java
@@ -26,14 +26,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
@@ -98,34 +98,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;
}
@@ -141,13 +141,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
@@ -168,22 +168,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?[mutable]? !_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; new _S = { int[] ary -> b=ary; }; };
return 0;
""");
code.parse().opto();
@@ -209,7 +209,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));
}
@@ -227,12 +227,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();
@@ -269,8 +269,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 f73cbf57..91e5cd38 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter16Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter16Test.java
@@ -48,8 +48,8 @@ return x+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();
@@ -60,8 +60,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();
@@ -74,19 +74,19 @@ return z.x;
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));
@@ -96,28 +96,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));
@@ -127,7 +126,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
@@ -139,34 +138,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));
- }
- static final String CONSTRUCTOR_MEMORY = """
- struct S { int x; int y; };
- struct T { int z=arg+40; };
- !T !t = new T;
- !S !a = new S { x=11; y=7; };
- !S !b = new S { x=22; y=9; };
- !S !p=a;
- if (arg) p=b;
- int before=p.x;
- !S !c = new S {
- x=p.x+1;
- { int i=0; while (i<2) { p.y=p.y+1; i=i+1; } }
- y=p.y;
- };
- p.x=33;
- return before*10000+c.x*100+c.y+t.z;
- """;
-
- @Test public void testConstructorMemory() {
- var code = new CodeGen(CONSTRUCTOR_MEMORY).parse().opto();
- assertEquals("111249",Eval2.eval(code,0));
- assertEquals("222352",Eval2.eval(code,1));
+ 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/Chapter17aTest.java b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java
index 8597b799..52acff57 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter17aTest.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter17aTest.java
@@ -24,8 +24,8 @@ return y;
@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();
@@ -36,20 +36,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 (const)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; };
-~Point p = new Point { x=5; y=6; };
+struct _Point { int x=3, y=4; new _Point = { int xx, int yy -> x=xx; y=yy; }; };
+~_Point p = new _Point(5,6);
p.x++;
return p;
""");
@@ -60,8 +60,8 @@ return p;
@Test
public void testStructFinal2() {
CodeGen code = new CodeGen("""
-struct Point { int x=3, y=4; };
-~Point p = new Point;
+struct _Point { int x=3, y=4; };
+~_Point p = new _Point;
p.x++;
return p;
""");
@@ -71,18 +71,21 @@ return p;
@Test
public void testStructFinal3() {
- for( String fields : new String[] { "var x; var y;", "int[] !x;", "int[] !x;" } ) {
- String src = "struct Point { "+fields+" }; Point p = new Point; p.x++; return p;";
- try { new CodeGen(src).parse().opto(); fail(fields); }
- catch( Exception e ) { assertEquals("'Point' is not fully initialized, field 'x' needs to be set in a constructor",e.getMessage()); }
- }
+ CodeGen code = new CodeGen("""
+struct _Point { var x; var y; };
+_Point p = new _Point;
+p.x++;
+return p;
+""");
+ try { code.parse().opto(); fail(); }
+ 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;
""");
@@ -93,14 +96,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));
}
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
index 24dc0229..b6768d6f 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter17bTest.java
@@ -81,8 +81,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;
""");
@@ -134,13 +134,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() {
@@ -158,14 +158,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));
@@ -178,20 +178,20 @@ return s.x;
}
@Test public void testVar7() {
- CodeGen code = new CodeGen("struct S{int x;}; ~S 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; };}; ~S 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));
@@ -209,8 +209,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
@@ -224,16 +224,16 @@ 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
-~Foo xfoo = foo; // Throw away mutability
+~_Foo xfoo = foo; // Throw away mutability
int x4 = xfoo.bar.x; // Ok to read through xfoo, gets 4
foo.bar.x++; // Bumps to 5
int x5 = xfoo.bar.x; // Ok to read through xfoo, gets 5
@@ -258,41 +258,64 @@ 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 {
- if (arg) b = new B; // Constructor ends with partial init of b
-}.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()); }
+ for( String fields : new String[] {
+ "!B !b; new A = { -> if (arg) b = new B; };",
+ "!B !b; new A = { -> if (arg) return 0; b = new B; };",
+ "!B !b; new A = { !B b -> b = new B; };", // Argument shadows field
+ "new A = { -> }; !B !b;", // Field declared later
+ } ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return 0;");
+ try { code.parse(); fail(fields); }
+ catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' is only partially set in the constructor",e.getMessage()); }
+ }
+ }
+
+ @Test
+ public void testDefaultConstructorFields() {
+ for( String field : new String[] { "!B !b;", "!B !b;" } ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+field+" }; return new A;");
+ try { code.parse(); fail(field); }
+ catch( Parser.ParseException e ) { assertEquals("'Test.A' is not fully initialized, field 'b' needs to be set in a constructor",e.getMessage()); }
+ }
+ for( String fields : new String[] {
+ "B b = new B;",
+ "!B !b; b = new B;",
+ "new A = { -> }; B b = new B;",
+ "!B !b; new A = { -> if (arg) { b = new B; return 0; } b = new B; };",
+ } ) {
+ CodeGen code = new CodeGen("struct B {}; struct A { "+fields+" }; return new A.b != null;");
+ code.parse().opto().typeCheck();
+ assertEquals("1",Eval2.eval(code,0));
+ assertEquals("1",Eval2.eval(code,1));
+ }
+ // No returning path means no object can escape uninitialized.
+ new CodeGen("struct B {}; struct A { !B !b; new A = { -> while (true) {} }; }; return 0;").parse();
}
@Test
public void testVar16() {
CodeGen code = new CodeGen(
"""
-struct S{};
+struct S{ new S = { -> x = 2; }; };
val x = 1;
-~S s = new S{x = 2;};
+~S 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
@@ -311,7 +334,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,23 +360,36 @@ return arg ? 0 : arg;
}
@Test
+ public void testShortCircuitGuard() {
+ CodeGen code = new CodeGen("""
+struct S { int !x; };
+val get = { !S s -> s.x; };
+!S? !s = arg ? new S;
+if( !s || !arg ) return 0;
+return get(s);
+""");
+ code.parse().opto().typeCheck();
+ }
+
+ @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();
@@ -366,8 +402,8 @@ 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();
@@ -396,7 +432,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("Cannot return generic scalar",e.getMessage()); }
}
// ---------------------------------------------------------------
@@ -471,7 +507,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()); }
}
@@ -480,39 +516,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 []*B?;", code.print());
- assertEquals("*B {... }?[]", Eval2.eval(code,0));
+ 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 44c365dd..82f99fc0 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter18Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter18Test.java
@@ -94,7 +94,7 @@ public class Chapter18Test {
new CodeGen(test[0]).parse().opto().typeCheck();
fail("Expected incompatible return types: " + test[0]);
} catch( Parser.ParseException e ) {
- assertEquals(test[1],e.getMessage());
+ assertEquals("Cannot return generic scalar",e.getMessage());
}
}
}
@@ -102,10 +102,10 @@ public class Chapter18Test {
@Test public void testPrintingForwardReferenceScope() throws Exception {
new CodeGen("return 0;").parse();
- var declared = TypeMemPtr.make(TypeStruct.open("PrintForward"));
+ var declared = TypeMemPtr.make(TypeStruct.make("PrintForward",true,true));
var scope = new ScopeNode();
scope.define("ptr",declared,false,new ConstantNode(TypeInteger.ZERO),null);
- Parser.TYPES.put("PrintForward",TypeMemPtr.make(TypeStruct.make("PrintForward",false)));
+ Parser.TYPES.put("PrintForward",TypeMemPtr.make(TypeStruct.make("PrintForward",false,false)));
var field = Var.class.getDeclaredField("_type");
field.setAccessible(true);
var v = scope.var(0);
@@ -123,7 +123,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));
}
@@ -141,7 +141,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));
@@ -158,7 +158,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 ) );
}
@@ -171,31 +171,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()); }
@@ -210,7 +229,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));
}
@@ -223,7 +242,7 @@ var fcn = arg ? { int x -> x*x; } : { int x -> x+x; };
return fcn(3);
""");
code.parse().opto();
- assertEquals("Stop[ return #2; return (Parm_x($fun21,i64)*x); return (Parm_x($fun22,i64)<<1); ]", 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));
}
@@ -231,9 +250,12 @@ 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 #2; return Phi(Region,1,(Parm_x(_fact,arg,(x-1))*#2)); ]") ||
+ ir.equals("Stop[ return Phi(Region,1,(arg*#2)); return Phi(Region,1,(Parm_x(_fact,(arg-1),(x-1))*#2)); ]") ||
+ 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));
@@ -241,31 +263,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));
}
@@ -273,17 +319,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));
}
@@ -299,8 +345,8 @@ for(;;) {
}
""");
code.driver(Phase.LocalSched);
- assertEquals("return Top;", code._stop.toString());
- assertNull(Eval2.eval(code,0)); // Infinite loop: i2i remains null.
+ assertEquals("Stop[ return Top; return Parm_i(x,i64); ]", code.print());
+ assertNull( Eval2.eval( code, 0 ) ); // Infinite loop, <clinit> never exits
}
@@ -308,23 +354,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));
}
@@ -359,7 +405,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()); }
}
@@ -371,7 +417,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()); }
}
@@ -383,16 +429,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(
"""
@@ -415,8 +459,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));
@@ -438,7 +482,7 @@ for(;;) {
}
""");
code.parse().opto().typeCheck().GCM();
- assertEquals("return 0;", code._stop.toString());
+ assertEquals("return 0;", code.print());
assertEquals("0", Eval2.eval(code, 0));
}
@@ -447,15 +491,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,1); 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));
}
@@ -463,82 +507,16 @@ 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));
}
- static final String CALL_MEMORY = """
- struct S { int x; int y; };
- val bump = { !S !s, int d -> int old=s.x; s.x=old+d; return old; };
- !S !a = new S { x=10; y=7; };
- !S !b = new S { x=20; y=9; };
- !S !p=a; if (arg) p=b;
- int before=p.x;
- int old=bump(p,3);
- int after=p.x;
- bump(a,5);
- return before*1000000+old*10000+after*100+a.x+a.y;
- """;
-
- @Test public void testCallMemory() {
- var code = new CodeGen(CALL_MEMORY).parse().opto();
- int[] calls={0};
- code._stop.walk(n -> { if(n instanceof CallNode) calls[0]++; return null; });
- assertEquals(2,calls[0]); // Keep real calls so this checks scheduling across them.
- assertEquals("10101325",Eval2.eval(code,0));
- assertEquals("20202322",Eval2.eval(code,1));
- code._stop.walk(n -> {
- if(n instanceof CallNode call) {
- assertEquals(5,call.nIns()); // No scheduling edges may become extra arguments.
- assertTrue(call.fptr()._type instanceof TypeFunPtr);
- }
- return null;
- });
- }
-
- static final String RECURSIVE_MEMORY = """
- struct S { int x; };
- val rec = { !S !s, int n ->
- if (n==0) return s.x;
- int before=s.x;
- s.x=before+1;
- return before+rec(s,n-1);
- };
- !S !s=new S { x=10; };
- int value=rec(s,arg);
- return value*100+s.x;
- """;
-
- @Test public void testRecursiveMemory() {
- var code = new CodeGen(RECURSIVE_MEMORY).parse().opto();
- for(int n=0; n<7; n++)
- assertEquals(Long.toString((10L*(n+1)+n*(n+1)/2)*100+10+n),Eval2.eval(code,n));
- }
-
- static final String INLINE_MEMORY = """
- struct S { int x; int y; };
- val make = { !S s ->
- S t = new S { x=s.x+1; y=3; };
- s.y=9;
- return t;
- };
- !S !a = new S { x=arg+40; y=7; };
- S t=make(a);
- return a.x*10000+a.y*100+t.x+t.y;
- """;
-
- @Test public void testInlineAllocationMemory() {
- var code = new CodeGen(INLINE_MEMORY).parse().opto();
- assertNull(code._stop.walk(n -> n instanceof CallNode ? n : null));
- assertEquals("400944",Eval2.eval(code,0));
- assertEquals("420946",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 8a840787..cb7d492f 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter19Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter19Test.java
@@ -6,8 +6,6 @@ import com.seaofnodes.simple.type.*;
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.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -15,14 +13,14 @@ import static org.junit.Assert.*;
public class Chapter19Test {
@Test public void testPrintingFunctionLookup() {
var code = new CodeGen("return 0;").parse();
- var tfp = TypeFunPtr.make((byte)2,false,new Type[]{TypeInteger.constant(987654)},TypeInteger.constant(123456),1L<<30);
- var con = new ConstantNode(tfp);
+ var tfp = TypeFunPtr.make1((byte)2,false,new Type[]{TypeInteger.constant(987654)},TypeInteger.constant(123456),30);
+ var con = ConstantNode.raw(tfp);
var call = new CallNode(null,code._start,con,con);
// Diagnostic lookup may construct/intern the return-erased linker key.
con.toString();
org.junit.Assert.assertNull(call.name());
- var fun = new FunNode(null,tfp,"printerTarget");
+ var fun = new FunNode(null,tfp,"printerTarget",null);
fun._name = "printerTarget";
fun.addDef(code._start);
code.link(fun);
@@ -40,18 +38,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
@@ -64,12 +63,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;
@@ -78,107 +71,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[final]); 5:._hashCode=0;]; return MEM[ 2:___ 3:___ 4:___ 5:._hashCode=0;]; return Test.String; 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
@@ -188,7 +215,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
@@ -199,7 +226,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
@@ -209,7 +236,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
@@ -258,10 +285,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
@@ -349,7 +376,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 ) {
@@ -359,15 +386,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 ->
@@ -402,8 +430,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));
}
@@ -411,97 +439,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,i64),x); return (shli,Parm_x($fun22,i64)); ]", 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());
- }
-
- @Test public void testSelectedMemory() {
- for( String cpu : new String[]{"x86_64_v2","riscv","arm"} )
- for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY,
- Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY} ) {
- var code = new CodeGen(src).parse().opto().typeCheck();
- var aliases = memoryPhis(code._stop);
- code.loopTree().instSelect(cpu,"SystemV");
- assertEquals(aliases,memoryPhis(code._stop));
- code.GCM().localSched();
- code._stop.walk(n -> {
- if( !(n instanceof CFGNode) && !(n instanceof ProjNode) )
- assertTrue(n.in(0) instanceof CFGNode);
- if( n instanceof NewNode nn ) {
- assertEquals(2,((TypeTuple)nn._type)._types.length);
- assertTrue(nn.mem() instanceof MemMergeNode);
- assertNull(nn.mem().in(1)); // Partial allocation input.
- nn.cacheRegs(code);
- MachNode mach = (MachNode)nn;
- assertNull(mach.regmap(1));
- assertNotNull(mach.regmap(2));
- assertNotNull(mach.outregmap(0));
- assertNull(mach.outregmap(1));
- }
- if( n instanceof CallNode call ) {
- assertEquals(4,call.nIns()); // ctrl, memory, two args; direct target is embedded.
- }
- return null;
- });
- }
- }
-
- private static java.util.ArrayList<String> memoryPhis(Node stop) {
- var phis = new java.util.ArrayList<String>();
- // Count only definitions reachable from Stop, as instruction selection does.
- // A dead Phi cycle can still appear through the reverse use edges.
- var seen = new java.util.BitSet();
- var work = new java.util.ArrayList<Node>();
- work.add(stop);
- for( int i=0; i<work.size(); i++ ) {
- Node n = work.get(i);
- if( n==null || seen.get(n._nid) ) continue;
- seen.set(n._nid);
- for( Node def : n._inputs ) work.add(def);
- if( n instanceof MemPhiNode phi ) phis.add("alias:"+phi._alias);
- if( n instanceof BulkMemPhiNode phi ) phis.add("bulk:"+phi._aliases);
- assertFalse(n instanceof PhiNode && n.isMem() &&
- !(n instanceof MemPhiNode) && !(n instanceof BulkMemPhiNode) && !(n instanceof ParmNode));
- }
- java.util.Collections.sort(phis);
- return phis;
- }
-
- @Test public void testFoldedReadBeforeWrites() {
- var code = new CodeGen("""
- struct S { int x; };
- !S !a = new S { x=11; }; !S !b = new S { x=22; };
- !S !p = a; if( arg ) p = b;
- int before = p.x + arg;
- a.x=33; b.x=44;
- return before;
- """).parse().opto().typeCheck().loopTree().instSelect("x86_64_v2","SystemV").GCM().localSched();
- var read = (MemOpNode)code._stop.walk(n ->
- n instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.AddMemX86 ? n : null);
- assertNotNull(read);
- int writes = 0;
- for( Node use : read.antiDeps() )
- if( use instanceof MemOpNode st && st.isMem() ) {
- assertSame(read.cfg0(),st.cfg0());
- assertTrue(st._inputs.find(read)>=0);
- assertTrue(read.cfg0()._outputs.find(read)<st.cfg0()._outputs.find(st));
- assertNull(((MachNode)st).regmap(st._inputs.find(read)));
- writes++;
- }
- assertEquals(1,writes); // The next Store follows this first clobber through memory.
+ 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 4ca1dbda..b46a54b2 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter20Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter20Test.java
@@ -4,8 +4,6 @@ import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.print.ASMPrinter;
-import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -52,11 +50,11 @@ public class Chapter20Test {
int stores=0;
for( var bb : code._cfg )
for( var node : bb.outs() )
- if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st ) {
+ if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st && st._name.equals("x") ) {
stores++;
assertEquals(-1,st.regmap(4).nextReg((short)15));
}
- assertEquals(1,stores);
+ assertTrue(stores>0);
}
}
@@ -71,14 +69,12 @@ return f(s);
// The entry of g can disappear before its Return is folded into f.
// Exercise both worklist orders; deleting the entry does not kill 123.
for( int seed=0; seed<64; seed++ ) {
- CodeGen code = new CodeGen(src,com.seaofnodes.simple.type.TypeInteger.BOT,seed,true).parse().opto().typeCheck();
+ CodeGen code = new CodeGen(src,seed,true).parse().opto().typeCheck();
assertEquals("seed "+seed, "123", Eval2.eval(code,0));
}
}
- @org.junit.Rule public final org.junit.rules.ErrorCollector _errors = new org.junit.rules.ErrorCollector();
-
@Test public void testAllocatorMasks() { com.seaofnodes.simple.codegen.RegAllocTestSupport.masks(); }
@Test public void testAllocatorUnion() { com.seaofnodes.simple.codegen.RegAllocTestSupport.union(); }
@Test public void testAllocatorCopyClobber() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyClobber(); }
@@ -97,40 +93,38 @@ return f(s);
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));
}
- // Collect differences so every target runs; JUnit still fails the test.
- private void testTarget(String src, String cpu, String os, int spills, String stop) {
- _errors.checkSucceeds(() -> { testCPU(src,cpu,os,spills,stop); return null; });
- }
-
- static void testCPU(String src, String cpu, String os, int spills, String stop) {
+ static void testCPU( String src, String cpu, String os, int spills, String stop ) {
CodeGen code = new CheckedCodeGen(src);
code.driver(CodeGen.Phase.RegAlloc,cpu,os);
- SpillStats.record(code,"Chapter20",cpu,os);
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
- if( stop!=null ) assertEquals(stop,code._stop.toString());
+ int delta = spills>>3;
+ if( delta==0 ) delta = 1;
+ if( spills != -1 && !CodeGen.iterSeedOverridden() )
+ assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta);
+ if( stop != null )
+ assertEquals(stop, code.print());
}
- private void testAllCPUs( String src, int spills, String stop ) {
- testTarget(src,"x86_64_v2", "SystemV",spills,stop);
- testTarget(src,"riscv" , "SystemV",spills,stop);
- testTarget(src,"arm" , "SystemV",spills,stop);
+ private static void testAllCPUs( String src, int spills, String stop ) {
+ testCPU(src,"x86_64_v2", "SystemV",spills,stop);
+ testCPU(src,"riscv" , "SystemV",spills,stop);
+ testCPU(src,"arm" , "SystemV",spills,stop);
}
@Test public void testAlloc0() {
- testTarget("return new u8[arg];","x86_64_v2","SystemV",4,"return []u8;");
- testTarget("return new u8[arg];","riscv","SystemV",5,"return []u8;");
- testTarget("return new u8[arg];","arm","SystemV",5,"return []u8;");
+ testCPU("return new u8[arg];","x86_64_v2", "SystemV",3,"return []u8;");
+ testCPU("return new u8[arg];","riscv" , "SystemV",5,"return []u8;");
+ testCPU("return new u8[arg];","arm" , "SystemV",5,"return []u8;");
}
@Test public void testBasic1() {
String src = "return arg | 2;";
- testTarget(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
- testTarget(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
- testTarget(src,"arm" , "SystemV",0,"return (ori,arg);");
+ testCPU(src,"x86_64_v2", "SystemV",1,"return (ori,mov(arg));");
+ testCPU(src,"riscv" , "SystemV",0,"return ( arg | #2 );");
+ testCPU(src,"arm" , "SystemV",0,"return (ori,arg);");
}
@Test
@@ -138,50 +132,77 @@ return f(s);
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;
}
};
-return sqrt(arg) + sqrt(arg+2);
+return _sqrt(arg) + _sqrt(arg+2);
""";
- testTarget(src,"x86_64_v2", "SystemV",40,null);
- testTarget(src,"riscv" , "SystemV",17,null);
- testTarget(src,"arm" , "SystemV",18,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() {
String src =
"""
-// Newtons approximation to the square root
-val 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( next == guess ) return guess;
+ if( guess-epsilon <= next && next <= guess+epsilon )
+ return guess;
guess = next;
}
};
-flt farg = arg;
-return sqrt(farg) + sqrt(farg+2.0);
+flt farg = arg; return _test_sqrt_noInline(farg) + _test_sqrt_noInline(farg+2.0);
""";
- testTarget(src,"x86_64_v2", "SystemV",23,null);
- testTarget(src,"riscv" , "SystemV",18,null);
- testTarget(src,"arm" , "SystemV",18,null);
+ testCPU(src,"x86_64_v2", "SystemV",40,null);
+ testCPU(src,"riscv" , "SystemV",17,null);
+ testCPU(src,"arm" , "SystemV",18,null);
+ }
+
+ @Test
+ public void testIterPrettyPrintIsLocalAndReadOnly() {
+ CodeGen code = new CodeGen("""
+ val f_noInline = { flt x -> while(1) { if(x) return x; } };
+ flt y=arg;
+ return f_noInline(y)+f_noInline(y+2.0);
+ """).driver(CodeGen.Phase.Iter);
+ assertNull(code._start._ltree);
+ int pre = code._start._pre;
+
+ String p0 = code.toString();
+ String p1 = code.toString();
+ assertEquals(p0,p1);
+ assertNull(code._start._ltree);
+ assertEquals(pre,code._start._pre);
+
+ int main0 = p0.indexOf("--- class:Test.<clinit>");
+ int main1 = p0.indexOf("--- class:Test.<clinit> ----------------------",main0);
+ int fun0 = p0.indexOf("--- f_noInline ",main1);
+ int fun1 = p0.indexOf("--- f_noInline ----------------------",fun0);
+ assertTrue(main0 < main1 && main1 < fun0 && fun0 < fun1);
+ assertTrue(p0.indexOf("ToFloat",main0) < p0.indexOf("Call",main0));
+ assertTrue(p0.indexOf("CallEnd",fun0) == -1 || p0.indexOf("CallEnd",fun0) > fun1);
}
@Test
public void testAlloc2() {
String src = "int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];";
- testTarget(src,"x86_64_v2","SystemV",3,"return .[];");
- testTarget(src,"riscv","SystemV",6,"return .[];");
- testTarget(src,"arm","SystemV",6,"return .[];");
+ testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
+ testCPU(src,"riscv" , "SystemV", 6,"return .[];");
+ testCPU(src,"arm" , "SystemV", 6,"return .[];");
}
+
+
@Test
public void testArray1() {
String src =
@@ -195,19 +216,23 @@ for( int i=0; i<ary#-1; i++ )
ary[i+1] += ary[i];
return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
""";
- testTarget(src,"x86_64_v2", "SystemV",5,"return .[];");
- testTarget(src,"riscv" , "SystemV",7,"return (add,.[],(mul,.[],1000));");
- testTarget(src,"arm" , "SystemV",5,"return (add,.[],(mul,.[],1000));");
+ testCPU(src,"x86_64_v2", "SystemV",-1,"return mov(.[]);");
+ testCPU(src,"riscv" , "SystemV", 9,"return (add,.[],(mul,.[],1000));");
+ testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
}
+
@Test
public void testString() {
- String src = """
+ String src =
+"""
struct String {
u8[] !cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
+// Compare two Strings
val equals = { !String !self, !String s ->
if( self == s ) return true;
if( self.cs# != s.cs# ) return false;
@@ -217,6 +242,7 @@ val equals = { !String !self, !String s ->
return true;
};
+// Return the String hashCode (cached, and never 0)
val hashCode = { !String self ->
self._hashCode
? self._hashCode
@@ -225,30 +251,23 @@ val hashCode = { !String self ->
val _hashCodeString = { !String self ->
int hash=0;
- if( self.cs ) {
- for( int i=0; i< self.cs#; i++ )
- hash = hash*31 + self.cs[i];
- }
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
if( !hash ) hash = 123456789;
return hash;
};
-
-!String !s = new String { cs = new u8[17]; };
-s.cs[0] = 67; // C
-s.cs[1] = 108; // l
-hashCode(s);
""";
- testTarget(src,"x86_64_v2", "SystemV",9,null);
- testTarget(src,"riscv" , "SystemV",3,null);
- testTarget(src,"arm" , "SystemV",0,null);
+ testCPU(src,"x86_64_v2", "SystemV",21,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
+ testCPU(src,"arm" , "SystemV",8,null);
}
@Test
public void testCast() {
String src = "struct Bar { int x; }; var b = arg ? new Bar; return b ? b.x++ + b.x++ : -1;";
- testTarget(src,"x86_64_v2","SystemV",1,null);
- testTarget(src,"riscv","SystemV",2,null);
- testTarget(src,"arm","SystemV",2,null);
+ testCPU(src,"x86_64_v2", "SystemV",3,null);
+ testCPU(src,"riscv" , "SystemV",6,null);
+ testCPU(src,"arm" , "SystemV",6,null);
}
@Test
@@ -260,15 +279,15 @@ hashCode(s);
@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;
""";
- testTarget(src,"x86_64_v2", "SystemV",0,"return Phi(Region,1,2,0);");
- testTarget(src,"riscv" , "SystemV",0,"return Phi(Region,1,2,0);");
- testTarget(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
@@ -281,147 +300,8 @@ while (arg > 0) {
}
return arg;
""";
- testTarget(src,"x86_64_v2", "SystemV",3,null);
- testTarget(src,"riscv" , "SystemV",2,null);
- testTarget(src,"arm" , "SystemV",2,null);
- }
- // Original Chapter 20 allocation workload, kept fixed for cohort comparisons.
- @Test
- public void testBrainfuck() {
- var program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.".getBytes(java.nio.charset.StandardCharsets.UTF_8);
- var encoded = new StringBuilder("u8[] !program = new u8[").append(program.length).append("];");
- for (int i = 0; i < program.length; i++) {
- int value = program[i] & 0xFF;
- encoded.append("program[").append(i).append("] = ").append(value).append(";");
- }
-
- String src = encoded + """
-
-int d = 0;
-u8[] !output = new u8[0];
-u8[] !data = new u8[100];
-
-for( int pc = 0; pc < program#; pc++ ) {
- var command = program[pc];
- if (command == 62) {
- d++;
- } else if (command == 60) {
- d--;
- } else if (command == 43) {
- data[d]++;
- } else if (command == 45) {
- data[d]--;
- } else if (command == 46) {
- // 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) {
- data[d] = 42;
- } else if (command == 91) {
- if (data[d] == 0) {
- for( int d = 1; d > 0; ) {
- command = program[++pc];
- if (command == 91) d++;
- if (command == 93) d--;
- }
- }
- } else if (command == 93) {
- if (data[d]) {
- for( int d = 1; d > 0; ) {
- command = program[--pc];
- if (command == 93) d++;
- if (command == 91) d--;
- }
- }
- }
-}
-return output;
-""";
- testTarget(src,"x86_64_v2", "SystemV",40,null);
- testTarget(src,"riscv" , "SystemV",28,null);
- testTarget(src,"arm" , "SystemV",28,null);
- //assertEquals("Hello World!\n", Eval2.eval(code, 0, 10000));
- }
- // Original Chapter 20 allocation workload, kept fixed for cohort comparisons.
- @Test
- public void testMergeSort() {
- String src =
-"""
-// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort
-
-val merge_sort = { int[] !a, int[] !b, int n ->
- copy_array(a, 0, n, b);
- split_merge(a, 0, n, 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[] !a = new int[arg];
-int[] !b = new int[a#];
-
-for (int i = 0; i < a#; i++)
- a[i] = a# - i;
-
-merge_sort(a, b, a#);
-
-return a;
-""";
- testTarget(src,"x86_64_v2", "SystemV",52,null);
- testTarget(src,"riscv" , "SystemV",44,null);
- testTarget(src,"arm" , "SystemV",41,null);
-//assertEquals("int[ 1,2,3,4,5,6,7,8,9,10,11]", Eval2.eval(code, 11));
- }
-
-
- @Test public void testMemoryAllocation() {
- for( String cpu : new String[]{"x86_64_v2","riscv","arm"} )
- for( String src : new String[]{Chapter10Test.NESTED_MEMORY,Chapter16Test.CONSTRUCTOR_MEMORY,
- Chapter18Test.CALL_MEMORY,Chapter18Test.RECURSIVE_MEMORY,
- "int[] !a = new int[3]; a[arg]=arg; return a[0]+1;"} ) {
- var code = new CodeGen(src).parse().opto().typeCheck().loopTree()
- .instSelect(cpu,"SystemV").GCM().localSched().regAlloc();
- com.seaofnodes.simple.codegen.RegAllocTestSupport.checkRegisters(code);
- code._stop.walk(n -> {
- // Whole memory, precise Phis, and memory projections need no register.
- if( n._type instanceof com.seaofnodes.simple.type.TypeMem )
- assertEquals(-1,code._regAlloc.regnum(n));
- if( n instanceof com.seaofnodes.simple.node.NewNode nn ) {
- assertTrue(code._regAlloc.regnum(nn.proj(0))>=0);
- assertEquals(-1,code._regAlloc.regnum(nn.proj(1)));
- }
- return null;
- });
- }
+ testCPU(src,"x86_64_v2", "SystemV",3,null);
+ testCPU(src,"riscv" , "SystemV",2,null);
+ testCPU(src,"arm" , "SystemV",2,null);
}
}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java
deleted file mode 100644
index bab7a86a..00000000
--- a/src/test/java/com/seaofnodes/simple/Chapter21AllocTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.seaofnodes.simple;
-
-import java.io.IOException;
-import java.nio.file.*;
-import org.junit.Test;
-
-// Revised Chapter 20 examples introduced with native encoding.
-public class Chapter21AllocTest {
- @Test
- public void testNewtonInteger() {
- String src =
-"""
-// Newtons approximation to the square root
-val sqrt = { int x ->
- int guess = x;
- while( 1 ) {
- int next = (x/guess + guess)/2;
- if( next == guess ) return guess;
- guess = next;
- }
-};
-return sqrt(arg) + sqrt(arg+2);
-""";
- Chapter21Test.testCPU(src,"x86_64_v2", "Win64" ,40,null);
- Chapter21Test.testCPU(src,"riscv" , "SystemV",19,null);
- Chapter21Test.testCPU(src,"arm" , "SystemV",19,null);
- }
-
- @Test
- public void testNewtonFloat() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp"))
- + "flt farg = arg; return test_sqrt(farg) + test_sqrt(farg+2.0);";
- Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",39,null);
- Chapter21Test.testCPU(src,"riscv" , "SystemV",17,null);
- Chapter21Test.testCPU(src,"arm" , "SystemV",18,null);
- }
-
- @Test
- public void testArray1() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/array1.smp"));
- Chapter21Test.testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
- Chapter21Test.testCPU(src,"riscv" , "SystemV", 8,"return (add,.[],(mul,.[],1000));");
- Chapter21Test.testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
- }
-
-
- @Test
- public void testString() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash21.smp"));
- Chapter21Test.testCPU(src,"x86_64_v2", "SystemV", 9,null);
- Chapter21Test.testCPU(src,"riscv" , "SystemV", 3,null);
- Chapter21Test.testCPU(src,"arm" , "SystemV", 1,null);
- }
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index 5b2c3a54..0cfdb577 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -10,14 +10,33 @@ 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.*;
public class Chapter21Test {
+ @Test public void testFloatToInteger() throws IOException {
+ String src = "val float_to_int={ flt x -> int i=x; return i; };"+
+ "val float_to_u8={ flt x -> u8 i=x; return i; };";
+ TestC.runC(src,"floatToInteger","",-1);
+ for( double x : new double[]{0.0,-0.0,6.92,-6.92,255.75,-257.75,
+ 0x1p63,Math.nextDown(0x1p63),-0x1p63,Math.nextDown(-0x1p63),
+ Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NaN} ) {
+ for( String name : new String[]{"float_to_int","float_to_u8"} ) {
+ long expected = name.equals("float_to_u8") ? ((long)x)&255 : (long)x;
+ EvalRisc5 r = TestRisc5.build(src,name,0,-1,false);
+ r.fregs[riscv.FA0-riscv.F_OFFSET]=x;
+ assertEquals(0,r.step(1000));
+ assertEquals(name+"("+x+")",expected,r.regs[riscv.A0]);
+ EvalArm64 a = TestArm64.build(name,src,0,-1,false);
+ a.fregs[arm.D0-arm.D_OFFSET]=x;
+ assertEquals(0,a.step(1000));
+ assertEquals(name+"("+x+")",expected,a.regs[arm.X0]);
+ }
+ }
+ }
+
@Test public void testReadBeforeConditionalStore() {
var code = new CodeGen("""
struct S { int x; }; !S !a=new S; !S !b=new S;
@@ -52,35 +71,10 @@ public class Chapter21Test {
}
- @Test public void testReturnedFunctionEncoding() throws IOException {
- String src = "if(arg) return {->42;}; return {->43;};";
- CodeGen code = new CodeGen(src).driver("riscv","SystemV",null);
- for( int arg : new int[]{0,1} ) {
- byte[] image = new byte[1<<16];
- byte[] bits = code._encoding.bits();
- System.arraycopy(bits,0,image,0,bits.length);
- EvalRisc5 cpu = new EvalRisc5(image,1<<15);
- cpu._pc = code._encoding._opStart[code.link(code._main)._nid];
- cpu.regs[riscv.A0] = arg;
- assertEquals(0,cpu.step(100));
- assertEquals(0,cpu._pc);
- cpu._pc = (int)cpu.regs[riscv.A0];
- assertEquals(0,cpu.step(100));
- assertEquals(0,cpu._pc);
- assertEquals(arg==0 ? 43 : 42,cpu.regs[riscv.A0]);
- }
- // Anonymous bodies need code, but no exported ELF name.
- new CodeGen(src).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,"build/objs/anonymous.o");
- }
-
@Test public void testArmFloatMemory() throws IOException {
for( String type : new String[]{"f32","f64"} ) {
String src = type+"[] !a=new "+type+"[3]; a[arg]=1.25; return a[1];";
- CodeGen code = new CodeGen(src).driver("arm","SystemV",null);
- byte[] image = new byte[1<<20];
- byte[] bits = code._encoding.bits();
- System.arraycopy(bits,0,image,0,bits.length);
- EvalArm64 cpu = new EvalArm64(image,1<<16);
+ EvalArm64 cpu = TestArm64.build("probe","val probe={ int arg -> "+src+" };",1,-1,false);
cpu.regs[arm.X0]=1;
assertEquals(0,cpu.step(1000));
assertEquals(1.25,cpu.fregs[0],0);
@@ -156,6 +150,7 @@ public class Chapter21Test {
struct _Vec {
u32 !len;
int[] !buf;
+ new _Vec = { int[] initial -> buf=initial; };
val grow = { int sz ->
var buf2 = new int[sz];
for( int i=0; i<len; i++ ) buf2[i]=buf[i];
@@ -168,7 +163,7 @@ public class Chapter21Test {
};
};
val run = { int arg ->
- val v = new _Vec{buf=new int[4];}.add(2).add(3).add(5).add(7).add(11);
+ val v = new _Vec(new int[4]).add(2).add(3).add(5).add(7).add(11);
return v.len*100+v.buf[arg];
};
""";
@@ -176,7 +171,7 @@ public class Chapter21Test {
}
private static int checkArmCalls(String src, String entryName, int arg, int result) throws IOException {
- CodeGen code = new CodeGen(src).driver("arm","SystemV",null);
+ CodeGen code = new CodeGen(src).driver("arm","SystemV",true,false);
byte[] image = new byte[1<<20];
System.arraycopy(code._encoding.bits(),0,image,0,code._encoding.bits().length);
EvalArm64 cpu = new EvalArm64(image,1<<16);
@@ -184,7 +179,7 @@ public class Chapter21Test {
int frames=0, calls=0;
for( var bb : code._cfg ) {
if( bb instanceof com.seaofnodes.simple.node.FunNode fun ) {
- int off = code._encoding._opStart[fun._nid];
+ int off = code._encoding.opStart(fun);
if( entryName.equals(fun._name) ) { cpu._pc=off; entry=true; }
int frame=fun._frameAdjust;
assertEquals(0,frame&15);
@@ -194,7 +189,7 @@ public class Chapter21Test {
}
}
if( bb instanceof com.seaofnodes.simple.node.ReturnNode ret ) {
- int off=code._encoding._opStart[ret._nid];
+ int off=code._encoding.opStart(ret);
int frame=ret.fun()._frameAdjust;
if( frame>0 ) {
assertEquals(0x910003FF | (frame<<10),cpu.ld4s(off)); // ADD SP,SP,#bytes
@@ -220,52 +215,6 @@ public class Chapter21Test {
@Test public void testCoalescing() { com.seaofnodes.simple.codegen.RegAllocTestSupport.coalescing(); }
@Test public void testCopyForward() throws Exception { com.seaofnodes.simple.codegen.RegAllocTestSupport.copyForward(); }
- @Test public void testNarrowStores() throws IOException {
- for( String type : new String[]{"i8","u8","i16","u16"} ) {
- String src = "struct S { "+type+" x; }; !S !s = new S; s.x = arg; return 0;";
- CodeGen code = new CodeGen(src).driver("riscv","SystemV",null);
- int stores=0;
- for( var bb : code._cfg )
- for( var node : bb.outs() )
- if( node instanceof com.seaofnodes.simple.node.cpus.riscv.StoreRISC st ) {
- stores++;
- assertEquals("Byte/short stores have no FP encoding",-1,st.regmap(4).nextReg((short)31));
- }
- assertEquals(1,stores);
- byte[] image = new byte[1<<20];
- byte[] bits = code._encoding.bits();
- System.arraycopy(bits,0,image,0,bits.length);
- EvalRisc5 cpu = new EvalRisc5(image,1<<16);
- cpu.regs[riscv.A0] = 0x8765;
- assertEquals(0,cpu.step(100));
- assertEquals(type.endsWith("8") ? 0x65 : 0x8765,
- type.endsWith("8") ? cpu.ld1z(1<<16) : cpu.ld2z(1<<16));
- code = new CodeGen(src).driver(CodeGen.Phase.Encoding,"x86_64_v2",TestC.CALL_CONVENTION);
- stores=0;
- for( var bb : code._cfg )
- for( var node : bb.outs() )
- if( node instanceof com.seaofnodes.simple.node.cpus.x86_64_v2.StoreX86 st ) {
- stores++;
- assertEquals("Byte/short stores have no XMM encoding",-1,st.regmap(4).nextReg((short)15));
- }
- assertEquals(1,stores);
- }
- }
-
- @Test public void testNativeExitStatus() throws IOException {
- Path source = Path.of("build/objs/nativeExit.c");
- Files.createDirectories(source.getParent());
- Files.writeString(source,"int main() { return 7; }\n");
- try {
- TestC.gcc(source.toString(),null,null,false,
- "build/objs/nativeExit"+(TestC.OS.startsWith("Windows") ? ".exe" : ""));
- } catch( AssertionError error ) {
- assertTrue(error.getMessage(),error.getMessage().contains("Program exit status"));
- return;
- }
- fail("A failing native program must fail the test, even with empty stdout");
- }
-
@Test public void testArmSubtractRegisters() {
EvalArm64 cpu = new EvalArm64(new byte[16],16);
cpu.st4(0,0xCB020020); // SUB X0,X1,X2, no shift or flag update.
@@ -309,21 +258,41 @@ public class Chapter21Test {
}
- // Frozen Jig runs in Chapter23AllocTest; the revised input is in Chapter24AllocTest.
- @Test @Ignore
+ @Ignore
+ @Test
public void testJig() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
- testCPU(src,"x86_64_v2", "Win64" ,-1,null);
+ String src =
+"""
+struct _s0 {
+ bool v1;
+ i16 v2;
+ int v3;
+ i8 v4;
+ byte v5;
+};
+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,"riscv" , "SystemV",-1,null);
testCPU(src,"arm" , "SystemV",-1,null);
}
static void testCPU( String src, String cpu, String os, int spills, String stop ) {
CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
- SpillStats.record(code,"Chapter21",cpu,os);
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 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());
}
@@ -335,14 +304,14 @@ public class Chapter21Test {
}
@Test public void testInfinite() {
- String src = "struct S { int i; }; !S !s = new S; while(1) 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",0,"return Top;");
testCPU(src,"arm" , "SystemV",0,"return Top;");
}
@Test
- public void testArray1() throws IOException {
+ public void testArray1() {
String src =
"""
int[] !ary = new int[arg];
@@ -354,19 +323,19 @@ for( int i=0; i<ary#-1; i++ )
ary[i+1] += ary[i];
return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
""";
- testCPU(src,"x86_64_v2", "SystemV",-1,"return .[];");
- testCPU(src,"riscv" , "SystemV", 7,"return (add,.[],(mul,.[],1000));");
+ testCPU(src,"x86_64_v2", "SystemV",-1,"return mov(.[]);");
+ testCPU(src,"riscv" , "SystemV", 9,"return (add,.[],(mul,.[],1000));");
testCPU(src,"arm" , "SystemV", 5,"return (add,.[],(mul,.[],1000));");
}
@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 {
@@ -374,18 +343,19 @@ 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", 9,"return mov(mov(Test._S));");
+ testCPU(src,"riscv" , "SystemV",10,"return mov(mov(Test._S));");
+ testCPU(src,"arm" , "SystemV",11,"return mov(mov(Test._S));");
}
@Test
- public void testString() throws IOException {
+ public void testString() {
String src =
"""
struct String {
u8[] !cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
// Compare two Strings
@@ -413,9 +383,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", 1,null);
+ testCPU(src,"x86_64_v2", "SystemV",21,null);
+ testCPU(src,"riscv" , "SystemV",8,null);
+ testCPU(src,"arm" , "SystemV",8,null);
}
@Test public void testStringExport() throws IOException {
@@ -424,6 +394,7 @@ val _hashCodeString = { !String self ->
struct String {
u8[] !cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
// Compare two Strings
@@ -451,10 +422,10 @@ val _hashCodeString = { !String self ->
return hash;
};
""";
- TestC.run(src, "stringHash",null, "", 9);
+ TestC.runC(src, "stringHash", "",21);
}
- @Test public void testLoop2() throws IOException {
+ @Test public void testLoop2() {
String src =
"""
int i = 0;
@@ -467,9 +438,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 {
@@ -498,9 +469,9 @@ val test_sqrt = { flt x ->
}
};
""";
- TestC.run(src, "newtonFloat", null, result, 34);
+ TestC.runC(src, "newtonFloat", result, TestC.CALL_CONVENTION.equals("win64") ? 42 : 34);
- 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);
@@ -508,7 +479,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);
@@ -558,11 +529,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, 186);
+ 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, 92, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "sieve", 100, 90, false);
int trap = R5.step(10000);
assertEquals(0,trap);
// Return register A0 holds sieve(100)
@@ -574,7 +545,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, 94, false);
+ EvalArm64 A5 = TestArm64.build("sieve", src, 100, 88, false);
int trap_arm = A5.step(10000);
assertEquals(0, trap_arm);
int ary_arm = (int)A5.regs[arm.X0];
@@ -587,7 +558,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 ){
@@ -599,9 +570,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
@@ -616,8 +587,19 @@ val fib = {int n ->
}
@Test public void testPerson() throws IOException {
+ String src =
+ """
+ struct Person {
+ i32 age;
+ };
+
+ val fcn = { !Person?[] !ps, int x ->
+ if( ps[x] )
+ ps[x].age++;
+ };
+ """;
String person = "6\n";
- TestC.run("person21", 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
@@ -628,7 +610,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("person21", ps, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 2, false);
R5.regs[riscv.A1] = 1; // Index 1
R5.st8(ps,3); // Length
R5.st8(ps+1*8,p0);
@@ -644,7 +626,7 @@ val fib = {int n ->
assertEquals(17+1,R5.ld8(p1));
assertEquals(60+0,R5.ld8(p2));
- EvalArm64 A5 = TestArm64.build("person21", ps, 0, false);
+ EvalArm64 A5 = TestArm64.build("fcn", src, ps, 2, false);
A5.regs[arm.X1] = 1; // Index 1
A5.st8(ps, 3);
A5.st8(ps+1*8,p0);
@@ -662,14 +644,21 @@ 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 ->
+ return
+ i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 +
+ i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ;
+};
+""";
String arg_count = "191.000000\n";
- TestC.run("arg_count", arg_count,
- TestC.CALL_CONVENTION.equals("Win64") ? 32 : 9);
+ TestC.runC(src, "arg_count", arg_count, TestC.CALL_CONVENTION.equals("win64") ? 35 : 9);
- EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", 0, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "addAll", 0, 4, false);
// Todo: handle stack(imaginary stack in emulator)
// pass in float arguments
@@ -682,14 +671,13 @@ val fib = {int n ->
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);
@@ -699,7 +687,7 @@ val fib = {int n ->
assertEquals(22.8, result, 0.00001);
// arm
- EvalArm64 A5 = TestArm64.build("no_stack_arg_count", 0, 0, 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 c77221bf..7484a6ae 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter22Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter22Test.java
@@ -5,13 +5,11 @@ import com.seaofnodes.isa.eval.EvalRisc5;
import com.seaofnodes.isa.eval.EvalArm64;
import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
+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.*;
@@ -30,8 +28,8 @@ public class Chapter22Test {
var enc = code._encoding;
// Link the external call to a tiny independent native stub.
var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null);
- int start = enc._opStart[call._nid];
- ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc._opLen[call._nid],0x1000-start);
+ int start = enc.opStart(call);
+ ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc.opLen(call),0x1000-start);
for( long value : values ) {
long expected = switch(i) {
case 0 -> (byte)value; case 1 -> value&255;
@@ -45,7 +43,6 @@ public class Chapter22Test {
if( target.equals("arm") ) {
EvalArm64 cpu = new EvalArm64(image,1<<15);
// MOVZ X0,0x1100; LDR X0,[X0]; RET X30.
- cpu._pc = enc._opStart[code.link(code._main)._nid];
cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000);
cpu.st4(0x1008,0xd65f03c0);
cpu.st8(0x1100,value);
@@ -55,7 +52,6 @@ public class Chapter22Test {
} else {
EvalRisc5 cpu = new EvalRisc5(image,1<<15);
// AUIPC a0,0; LD a0,12(a0); RET.
- cpu._pc = enc._opStart[code.link(code._main)._nid];
cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503);
cpu.st4(0x1008,0x00008067);
// RV64 widens even u32 returns by sign-extending bit 31.
@@ -83,148 +79,79 @@ public class Chapter22Test {
val test_i64={->return c_i64();};
val test_cmp={->return c_i32()==-1;};
""";
- for( String conv : new String[]{"SystemV","Win64"} )
- TestC.run(src,conv,com.seaofnodes.simple.type.TypeInteger.BOT,conv.equals("SystemV") ? "sysv_abi" : "ms_abi",
- "src/test/java/com/seaofnodes/simple/progs/c_returns.c",
- "build/objs/c_returns"+conv,"S","",-1);
- }
-
-
-
- @Test public void testReturnedFunctions() throws IOException {
- for( String src : new String[]{"return {->42;};", "val f={->42;}; return f;", "return sys.io.p;"} )
- for( String target : new String[]{"riscv","arm"} ) {
- CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV").exportELF(null);
- byte[] image = new byte[1<<16];
- byte[] bits = code._encoding.bits();
- System.arraycopy(bits,0,image,0,bits.length);
- boolean library = src.contains("sys.io.p");
- // u8[]: four-byte length followed by its bytes.
- image[0x2000]=2; image[0x2004]='o'; image[0x2005]='k';
- int entry = code._encoding._opStart[code.link(code._main)._nid];
- if( target.equals("riscv") ) {
- EvalRisc5 cpu = new EvalRisc5(image,1<<15);
- cpu._pc = entry;
- assertEquals(0,cpu.step(1000));
- assertEquals(0,cpu._pc);
- cpu._pc = (int)cpu.regs[riscv.A0]; // Call the returned address.
- cpu.regs[library ? riscv.A1 : riscv.A0] = 0x2000; // Methods also receive self.
- assertEquals(0,cpu.step(1000));
- assertEquals(0,cpu._pc);
- if( library ) assertEquals("ok",cpu._stdout.toString());
- else assertEquals(42,cpu.regs[riscv.A0]);
- } else {
- EvalArm64 cpu = new EvalArm64(image,1<<15);
- cpu._pc = entry;
- assertEquals(0,cpu.step(1000));
- assertEquals(0,cpu._pc);
- cpu._pc = (int)cpu.regs[0];
- cpu.regs[library ? 1 : 0] = 0x2000;
- assertEquals(0,cpu.step(1000));
- assertEquals(0,cpu._pc);
- if( library ) assertEquals("ok",cpu._stdout.toString());
- else assertEquals(42,cpu.regs[0]);
- }
- }
- CodeGen unused = new CodeGen("({->42;}); return 0;").parse().opto();
- assertEquals(1,unused._stop.nIns());
- }
-
- @Test public void testReturnedFunctionsNative() throws IOException {
- String src = "val factory={int x -> if(x) return {->42;}; return {->43;};}; val printer={->return sys.io.p;};";
- String obj = "build/objs/funptr.o";
- new CheckedCodeGen(src).driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,obj);
- assertEquals("ok",TestC.gcc(obj,"","src/test/java/com/seaofnodes/simple/progs/funptr.c",false,
- "build/objs/funptr"+(TestC.OS.startsWith("Windows") ? ".exe" : "")));
- }
-
-
- @Test public void testRiscvRightShifts() {
- // SRAI, SRLI, SRA, SRL: a0 = -32 shifted by 4.
- int[] ops = {0x40455513,0x00455513,0x40b55533,0x00b55533};
- for( int i=0; i<ops.length; i++ ) {
- EvalRisc5 r5 = new EvalRisc5(new byte[1<<16],1<<15);
- r5.st4(0,ops[i]);
- r5.regs[riscv.A0] = -32;
- r5.regs[riscv.A0+1] = 4;
- assertEquals(0,r5.step(1));
- assertEquals((i&1)==0 ? -2L : 0x0fff_ffff_ffff_fffeL,r5.regs[riscv.A0]);
- }
+ for( String conv : new String[]{"SystemV","win64"} )
+ TestC.run(src,"c_returns"+conv,null,conv,
+ conv.equals("SystemV") ? "sysv_abi" : "ms_abi",
+ TestC.C_DRIVERS_DIR+"c_returns.c","",-1);
}
- @Test public void testRiscvPointerRelocation() {
- CodeGen code = new CheckedCodeGen("return \"x\";").driver(CodeGen.Phase.Encoding,"riscv","SystemV");
- var enc = code._encoding;
- assertEquals(1,enc._bigCons.size());
- var ptr = (com.seaofnodes.simple.node.cpus.riscv.TMPRISC)enc._bigCons.keySet().iterator().next();
- int start = enc._opStart[ptr._nid];
- for( int delta : new int[]{0x7ff,0x800,0xfff,0x1000,0x1800,-1,-0x800,-0x801} ) {
- ptr.patch(enc,start,enc._opLen[ptr._nid],delta);
- // Place the program above zero so backward targets are valid addresses.
- byte[] image = new byte[1<<16];
- System.arraycopy(enc.bits(),0,image,0x4000,enc._bits.size());
- EvalRisc5 r5 = new EvalRisc5(image,1<<15);
- r5._pc = 0x4000;
- assertEquals(0,r5.step(100));
- assertEquals(0x4000+start+delta,r5.regs[riscv.A0]);
- }
- }
@Test public void testSubZeroTypeError() {
try {
new CodeGen("return null-0;").parse().opto().typeCheck();
fail("Subtraction must reject null even when the other operand is zero");
} catch( Parser.ParseException e ) {
- assertEquals("Cannot '-' null",e.getMessage());
+ assertEquals("Cannot null - 0",e.getMessage());
}
}
- @Test public void testInfiniteReturn() {
- 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",0,"return Top;");
- testCPU(src,"arm","SystemV",0,"return Top;");
- }
-
- // Frozen Jig runs in Chapter23AllocTest; the revised input is in Chapter24AllocTest.
- @Test @Ignore
+ @Ignore
+ @Test
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);
+ String src = """
+struct s0 {
+ bool v1;
+ i16 v2;
+ int v3;
+ i8 v4;
+ byte v5;
+};
+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,"riscv" , "SystemV",-1,null);
testCPU(src,"arm" , "SystemV",-1,null);
}
static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
- SpillStats.record(code,"Chapter22",cpu,os);
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 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(65, testCPUSize(src, "x86_64_v2","win64", 3 ));
+ assertEquals(76, testCPUSize(src, "riscv", "SystemV", 4 ));
+ assertEquals(76, 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);
@@ -237,24 +164,23 @@ 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);
- EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away_2", src, 0, 4, false);
+ EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away_2", src, 0, 3, false);
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(65, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(76, testCPUSize(src, "riscv", "SystemV",4 ));
+ assertEquals(76, testCPUSize(src, "arm", "SystemV",4 ));
}
@@ -262,13 +188,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);
@@ -276,9 +202,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(55, testCPUSize(src, "x86_64_v2","win64",3 ));
+ assertEquals(72, testCPUSize(src, "riscv", "SystemV",5 ));
+ assertEquals(72, testCPUSize(src, "arm", "SystemV",5));
// do assertEquals here
}
@@ -287,17 +213,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
@@ -308,7 +234,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, 2, false);
R5.regs[riscv.A1] = 1; // Index 1
R5.st8(ps,3); // Length
R5.st8(ps+1*8,p0);
@@ -324,7 +250,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, 2, false);
A5.regs[arm.X1] = 1; // Index 1
A5.st8(ps, 3);
A5.st8(ps+1*8,p0);
@@ -344,47 +270,62 @@ 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]);
assertEquals("Hello, World!",arm._stdout.toString());
}
+
+ @Test
+ public void testStrLoad() {
+ CodeGen code = new CodeGen("return \"abc\"[1];");
+ code.parse().opto();
+ assertEquals("return .[];", code.print());
+ }
+
+
@Test
public void testFinalArray() {
String src = """
@@ -392,16 +333,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));
}
@@ -413,10 +354,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/Chapter23AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
deleted file mode 100644
index f3cc7512..00000000
--- a/src/test/java/com/seaofnodes/simple/Chapter23AllocTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.seaofnodes.simple;
-
-import java.io.IOException;
-import java.nio.file.*;
-import org.junit.Ignore;
-import org.junit.Test;
-
-// Revised or newly enabled allocation examples in Chapter 23.
-public class Chapter23AllocTest {
- @Test
- public void testString() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/stringHash.smp"));
- Chapter23Test.testCPU(src,"x86_64_v2", "SystemV", 9,null);
- Chapter23Test.testCPU(src,"riscv" , "SystemV", 3,null);
- Chapter23Test.testCPU(src,"arm" , "SystemV", 1,null);
- }
-
- @Ignore
- @Test
- public void testJig21() throws IOException {
- String src = Files.readString(Path.of("src/test/java/com/seaofnodes/simple/progs/jig.smp"));
- Chapter23Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null);
- Chapter23Test.testCPU(src,"riscv" , "SystemV",-1,null);
- Chapter23Test.testCPU(src,"arm" , "SystemV",-1,null);
- }
-
- @Ignore
- @Test
- public void testJig22() 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"));
- Chapter23Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null);
- Chapter23Test.testCPU(src,"riscv" , "SystemV",-1,null);
- Chapter23Test.testCPU(src,"arm" , "SystemV",-1,null);
- }
-
-}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter23Test.java b/src/test/java/com/seaofnodes/simple/Chapter23Test.java
index 884e77e5..053621bd 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter23Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter23Test.java
@@ -11,7 +11,6 @@ import com.seaofnodes.simple.type.*;
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;
@@ -25,7 +24,7 @@ public class Chapter23Test {
@Test public void testDiagnosticTypeAccessors() throws Exception {
new CodeGen("return 0;").parse();
var tfp = TypeFunPtr.TEST;
- var con = new ConstantNode(tfp);
+ var con = ConstantNode.raw(tfp);
var call = new CallNode(null,con,con,con);
var field = Type.class.getDeclaredField("VISIT");
field.setAccessible(true);
@@ -45,335 +44,252 @@ public class Chapter23Test {
}
- @Test
- public void testOrRhsLoopLocal() throws IOException {
- checkOrRhsLoop("""
- struct S { int f; };
- int x = 0;
- int result = (arg & 1) || (new S {
- while (x < 3) x++;
- f = ++x;
- }).f;
- return x * 10 + result;
- """);
- }
-
- @Test
- public void testOrRhsLoopMemory() throws IOException {
- checkOrRhsLoop("""
- struct S { int f; };
- !S !s = new S;
- int result = (arg & 1) || (new S {
- while (s.f < 3) s.f++;
- s.f++;
- f = s.f;
- }).f;
- return s.f * 10 + result;
- """);
- }
-
- private static void checkOrRhsLoop(String src) throws IOException {
- CodeGen code = new CheckedCodeGen(src).driver("riscv", "SystemV", null);
- SpillStats.record(code,"Chapter23","riscv","SystemV");
- for (int arg : new int[]{0, 1, 2, 3}) {
- byte[] image = new byte[1 << 20];
- System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding._bits.size());
- EvalRisc5 r5 = new EvalRisc5(image, 1 << 16);
- r5.regs[riscv.A0] = arg;
- assertEquals("Execution must finish", 0, r5.step(1000));
- // False LHS runs the loop and final increment, returning 4.
- // True LHS skips all RHS effects and returns the LHS value, 1.
- assertEquals("OR result and side effects for arg=" + arg,
- (arg & 1) == 0 ? 44 : 1, r5.regs[riscv.A0]);
- }
- }
-
- @Ignore
- @Test
+ @Test @Ignore
public void testJig() throws IOException {
- String src =
-"""
-var apply = { i64 x, { i64 -> u32 } fcn ->
- return fcn(x);
-}
-""";
+ //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,"riscv" , "SystemV",-1,null);
+ testCPU(src,"arm" , "SystemV",-1,null);
}
static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
- SpillStats.record(code,"Chapter23",cpu,os);
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 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 testLogicalValueSemantics() {
+ assertEquals("42", Eval2.eval(new CodeGen("return 7 && 42;").parse().opto(), 0));
+ assertEquals("0", Eval2.eval(new CodeGen("return 0 && 42;").parse().opto(), 0));
+ assertEquals("7", Eval2.eval(new CodeGen("return 7 || 42;").parse().opto(), 0));
+ assertEquals("42", Eval2.eval(new CodeGen("return 0 || 42;").parse().opto(), 0));
+ }
+
+ @Test
+ public void testMixedScalarNotPhiPredicate() {
+ CodeGen code = new CodeGen("""
+struct S {};
+S? s = arg ? new S;
+return !(!s || !arg) ? 1 : 0;
+""");
+ code.parse().opto().typeCheck();
+ assertEquals("0", Eval2.eval(code, 0));
+ assertEquals("1", Eval2.eval(code, 1));
}
@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, 12, 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, 12, 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, 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
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" ,23,null);
+ testCPU(src,"x86_64_v2", "win64",23,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] <= ' ' )
@@ -381,23 +297,26 @@ struct Scan {
return self;
};
};
-~Scan s = new Scan{ buf = " q"; };
+~_Scan s = new _Scan(" q");
return s.skip().x;
- """;
+""";
try { new CodeGen(src).parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); }
+ catch( Exception e ) {
+ // Bad error message, but basically requires a mutable 'x' field.
+ org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa"));
+ }
};
@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] <= ' ' )
@@ -408,14 +327,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" ,4,null);
+ testCPU(src,"x86_64_v2", "win64",4, null);
};
diff --git a/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java b/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java
deleted file mode 100644
index 7fae2c27..00000000
--- a/src/test/java/com/seaofnodes/simple/Chapter24AllocTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package com.seaofnodes.simple;
-
-import com.seaofnodes.isa.eval.EvalRisc5;
-
-import com.seaofnodes.isa.eval.EvalArm64;
-
-import java.io.IOException;
-import java.nio.file.*;
-import com.seaofnodes.simple.node.cpus.arm.arm;
-import com.seaofnodes.simple.node.cpus.riscv.riscv;
-import org.junit.Ignore;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-// Revised inherited programs belong to the chapter which changed their inputs.
-public class Chapter24AllocTest {
- @Test
- public void testNewtonInteger() {
- String src =
-"""
-// Newtons approximation to the square root
-val sqrt = { int x ->
- int guess = x;
- while( 1 ) {
- int next = (x/guess + guess)/2;
- if( next == guess ) return guess;
- guess = next;
- }
-};
-int cast_int = arg+2;
-return sqrt(arg) + sqrt(cast_int);
-""";
- Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,40,null);
- Chapter24Test.testCPU(src,"riscv" , "SystemV",19,null);
- Chapter24Test.testCPU(src,"arm" , "SystemV",19,null);
- }
-
- @Ignore
- @Test
- public void testJig21() throws IOException {
- String src =
-"""
-struct s0 {
- bool v1;
- i16 v2;
- int v3;
- i8 v4;
- byte v5;
-};
-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;
-""";
- Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null);
- Chapter24Test.testCPU(src,"riscv" , "SystemV",-1,null);
- Chapter24Test.testCPU(src,"arm" , "SystemV",-1,null);
- }
-
- @Test public void testStackArguments() 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.
- 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 ->
- return
- i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 +
- i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ;
-};
-""";
- String arg_count = "191.000000\n";
-
- TestC.run(src, "arg_count", null, arg_count, TestC.CALL_CONVENTION.equals("Win64") ? 32 : 9);
-
- EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", src, 0, 4, false);
-
- // Todo: handle stack(imaginary stack in emulator)
- // pass in float arguments
- R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 1.1;
- R5.fregs[riscv.FA1 - riscv.F_OFFSET] = 1.1;
- R5.fregs[riscv.FA2 - riscv.F_OFFSET] = 1.1;
- R5.fregs[riscv.FA3 - riscv.F_OFFSET] = 1.1;
- R5.fregs[riscv.FA4 - riscv.F_OFFSET] = 1.1;
- R5.fregs[riscv.FA5 - riscv.F_OFFSET] = 1.1;
- 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.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);
-
- double result = R5.fregs[riscv.FA0 - riscv.F_OFFSET];
-
- assertEquals(22.8, result, 0.00001);
-
- // arm
- EvalArm64 A5 = TestArm64.build("no_stack_arg_count", src, 0, 4, false);
-
- A5.fregs[arm.D0 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D1 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D2 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D3 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D4 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D5 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D6 - arm.D_OFFSET] = 1.1;
- A5.fregs[arm.D7 - arm.D_OFFSET] = 1.1;
-
- A5.regs[arm.X1] = 2;
- A5.regs[arm.X2] = 2;
- A5.regs[arm.X3] = 2;
- A5.regs[arm.X4] = 2;
- A5.regs[arm.X5] = 2;
- A5.regs[arm.X6] = 2;
- A5.regs[arm.X7] = 2;
-
- int trap_arm = A5.step(100);
- assertEquals(0,trap_arm);
-
- double result1 = A5.fregs[arm.D0 - arm.D_OFFSET];
- assertEquals(22.8, result1, 0.00001);
- }
-
- @Ignore
- @Test
- public void testJig22() 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;
-};
-
-fib(10);
-""";
- Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null);
- Chapter24Test.testCPU(src,"riscv" , "SystemV",-1,null);
- Chapter24Test.testCPU(src,"arm" , "SystemV",-1,null);
- }
-
- @Test
- public void testBubbleSort() 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"));
- Chapter24Test.testCPU(src,"x86_64_v2", "Win64" ,-1,null);
- Chapter24Test.testCPU(src,"riscv" , "SystemV",-1,null);
- Chapter24Test.testCPU(src,"arm" , "SystemV",-1,null);
- }
-}
diff --git a/src/test/java/com/seaofnodes/simple/Chapter24Test.java b/src/test/java/com/seaofnodes/simple/Chapter24Test.java
index 9a657855..3e21c73e 100644
--- a/src/test/java/com/seaofnodes/simple/Chapter24Test.java
+++ b/src/test/java/com/seaofnodes/simple/Chapter24Test.java
@@ -22,14 +22,14 @@ public class Chapter24Test {
@Test public void testPrintingDeadFunction() throws Exception {
var code = new CodeGen("return 0;").parse();
var tfp = TypeFunPtr.TEST;
- var fun = new FunNode(null,tfp,"deadPrinterTarget");
+ var fun = new FunNode(null,tfp,"deadPrinterTarget",null);
org.junit.Assert.assertTrue(fun.isDead());
code.link(fun);
- var con = new ConstantNode(tfp);
+ var con = ConstantNode.raw(tfp);
var call = new CallNode(null,con,con,con);
con.toString();
call.name();
-
+ code.funcName(tfp.fidx());
org.junit.Assert.assertSame(fun,code._link(tfp));
// Optimizer lookup still performs its usual lazy cleanup.
org.junit.Assert.assertNull(code.link(tfp));
@@ -41,435 +41,110 @@ 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);
}
static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) {
CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os);
- SpillStats.record(code,"Chapter24",cpu,os);
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 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;
+return (10 < something_specific < 20)
+ ? 0 // Expected
+ : 1; // Error
""";
-
- TestC.runSF("stacked_r_1", src, null, "In Range", 0);
+ TestC.runSF( src, "stacked_r_1", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_1", 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_1", 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 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;
-""";
-
- TestC.runSF("stacked_r_2", src, null, "In Range", 0);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_2", 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_2", 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 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", "", 2);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_3", src, 0, 5, false);
+ EvalRisc5 R5 = TestRisc5.build( src, "stacked_r_3", 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_3", src, 0, 5, false);
+ EvalArm64 arm = TestArm64.build("stacked_r_3", src, 0, 3, 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);
+ TestC.runSF( src, "stacked_r_4", "", 0);
// Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_4", src,0, 2, 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("Out of Range",R5._stdout.toString());
// Evaluate on ARM emulator
- EvalArm64 arm = TestArm64.build("stacked_r_4", src, 0, 2, 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("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);
-
- // Evaluate on RISC5 emulator
- EvalRisc5 R5 = TestRisc5.build("stacked_r_5", src, 0, 7, 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);
- 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]);
@@ -483,21 +158,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]);
@@ -511,27 +177,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,2);
+ 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, 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]);
@@ -541,21 +200,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]);
@@ -572,120 +222,28 @@ 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);
// Compile once per target, then check false, true and negative inputs.
- EvalRisc5 R5 = TestRisc5.build("stacked_r_13", src2, 1, 0, false);
+ EvalRisc5 R5 = TestRisc5.build( src2, "stacked_r_13", 1, 0, false);
for( int arg : new int[]{-1,0,1} ) {
R5._pc = 0;
- R5.regs[riscv.A0] = arg;
+ R5.regs[riscv.A1] = arg;
assertEquals(0,R5.step(100));
assertEquals(arg==0 ? 1 : 0,R5.regs[riscv.A0]);
}
EvalArm64 arm = TestArm64.build("stacked_r_13", src2, 1, 0, false);
for( int arg : new int[]{-1,0,1} ) {
arm._pc = 0;
- arm.regs[0] = arg;
+ arm.regs[1] = arg;
assertEquals(0,arm.step(100));
assertEquals(arg==0 ? 1 : 0,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" ,23,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;
- };
-};
-~Scan s = new Scan{ buf = " q"; };
-return s.skip().x;
-""";
-
- try { new CodeGen(src).parse().opto().typeCheck(); fail(); }
- catch( Exception e ) { org.junit.Assert.assertTrue(e.getMessage().startsWith("Argument #0 isa")); }
- };
-
-
- @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" ,4,null);
- };
-
- @Test
public void testSCCP0() {
String src =
"""
@@ -700,7 +258,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
@@ -715,7 +273,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..354c6ec2
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/Chapter25Test.java
@@ -0,0 +1,459 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.isa.eval.EvalRisc5;
+
+import com.seaofnodes.isa.eval.EvalArm64;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
+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.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class Chapter25Test {
+
+ @Ignore
+ @Test
+ public void testJig() {
+ String src = "int x = arg/17; if( arg == 0 ) return arg + x; return arg + x;";
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("return (arg+3);", code.print());
+ assertEquals("3",Eval2.eval(code,0));
+ }
+
+ @Test public void testNeverReturnPointerRiscV() {
+ new CodeGen("""
+ struct _S { int x; };
+ !_S !s = new _S;
+ if(arg) while(1) { s.x += arg; }
+ return s;
+ """).driver(CodeGen.Phase.Encoding,"riscv","SystemV");
+ }
+
+ @Test
+ public void testExternData() throws IOException {
+ String src = """
+ i32 counter="C";
+ i8 small="C";
+ f64 fraction="C";
+ {int} bump="C";
+ val test_global={ ->
+ val before=counter;
+ counter=before;
+ counter=++counter+1;
+ counter+=1;
+ val mid=bump();
+ small++;
+ fraction=fraction+0.5;
+ return before*10000+mid*100+counter;
+ };
+ """;
+ TestC.runC(src,"extern_data","",-1);
+ }
+
+ @Test
+ public void testExternDataEncoding() {
+ for( String cpu : new String[]{"arm","riscv"} ) {
+ CodeGen code = new CheckedCodeGen("i32 counter=\"C\"; counter=counter+3; return counter;");
+ // Exercise signed low-12 relocation arithmetic, past the first page.
+ int addr = 0x3800;
+ code._externDataAddresses.put("counter",addr);
+ code.driver(cpu,"SystemV",true,false);
+ byte[] image = new byte[1<<20];
+ var bits = code._encoding._bits;
+ System.arraycopy(bits.buf(),0,image,0,bits.size());
+ if( cpu.equals("arm") ) {
+ EvalArm64 arm = new EvalArm64(image,1<<16);
+ arm.st4(addr,-7);
+ assertEquals(0,arm.step(1000));
+ assertEquals(-4,arm.regs[0]);
+ assertEquals(-4,arm.ld4s(addr));
+ } else {
+ EvalRisc5 r5 = new EvalRisc5(image,1<<16);
+ r5.st4(addr,-7);
+ assertEquals(0,r5.step(1000));
+ assertEquals(-4,r5.regs[10]);
+ assertEquals(-4,r5.ld4s(addr));
+ }
+ }
+ }
+
+ @Test
+ public void testExternDataImports() throws IOException {
+ Path dir = Files.createTempDirectory(Path.of("build/objs"),"extern_data_");
+ String globals = "i32 counter=\"C\"; val read={ -> counter; };";
+ CodeGen globalCode = new CodeGen(null,dir.toString(),null,"Globals",globals,126,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ var clz = globalCode.compunit()._clz;
+ int counter = clz.find("counter");
+ assertTrue(clz._fields[counter]._extern);
+ assertEquals(-1,clz.offset(counter));
+ assertEquals(clz.remove(counter).size(),clz.size());
+ // Load the serialized declaration and ideal graph from Globals.o.
+ String src = """
+ i32 counter="C";
+ val test_global={ ->
+ val before=Globals.counter;
+ Globals.counter=before+1;
+ counter+=2;
+ return before*100+Globals.read();
+ };
+ """;
+ Ary<String> paths = new Ary<>(new String[]{dir.toString()});
+ for( boolean imported : new boolean[]{true,false} ) {
+ Path root = dir.resolve(imported ? "binary" : "source");
+ Files.createDirectories(root.resolve("Main"));
+ if( !imported ) Files.writeString(root.resolve("Main/Globals.smp"),globals);
+ new CodeGen(root.toString(),root.toString(),imported ? paths : null,"Main",src,126,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ assertEquals("",TestC.gcc(root+"/Main.o","",TestC.C_DRIVERS_DIR+"extern_data_import.c",
+ (String)null,new Ary<>(new String[]{dir+"/Globals.o"}),root+"/program"+(TestC.OS.startsWith("Windows") ? ".exe" : "")));
+ }
+ }
+
+ @Test public void testPrintingConstantPool() throws Exception {
+ com.seaofnodes.simple.codegen.PrintRegTestSupport.checkConstantPool();
+ }
+
+ private static final String SYS_BLDDIR = "build/objs/lib_"+TestC.CPU_ABI;
+ private static final File SYS_FILE = new File(SYS_BLDDIR+"/sys.o");
+
+ @Test
+ public void testForwardConstructor() {
+ CodeGen code = new CodeGen("src/test/java/com/seaofnodes/simple/test_smp/forward_ctor",
+ "build/objs/forward_ctor_parse",null,
+ "m",null,123L,true,TypeInteger.BOT);
+ code.driver(CodeGen.Phase.TypeCheck);
+ }
+
+
+ @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
+ public void testCharacterEscapes() {
+ CodeGen code = new CodeGen("return '\\n'==10 && '\\t'==9 && '\\r'==13;")
+ .driver(CodeGen.Phase.TypeCheck);
+ assertEquals("1",Eval2.eval(code,0));
+ }
+
+ @Test
+ public void testFuzzerUnresolvedNumericLoop() {
+ CodeGen code = new CodeGen("""
+ arg=0;
+ while(arg+0)
+ while(-arg)
+ arg=0;
+ """).driver(CodeGen.Phase.TypeCheck);
+ assertEquals("return 0;",code.print());
+ }
+
+ @Test
+ public void testFuzzerBadLCA() {
+ new CodeGen("""
+ int v0=arg;
+ if(v0&&--v0?0:arg) {}
+ """).driver(CodeGen.Phase.LocalSched);
+ }
+
+ @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,true,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,true,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,true,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,true,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,true,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();
+ }
+
+
+ @Test
+ public void testSys() {
+ assertTrue("Missing "+SYS_FILE+"; run make tests_sys", SYS_FILE.exists());
+
+ // Can read the ELF files
+ CodeGen code1 = new CodeGen("return 0;");
+ ElfReader sys_elf = ElfReader.load(SYS_FILE, null);
+ sys_elf.loadPublicTypes(code1);
+
+ // Elf files are sane
+
+ // Sys depends on io, libc, char, collections, and array utilities.
+ assertEquals(10,sys_elf._deps.length);
+ assertSame("sys/aryu8" ,sys_elf._deps[0]);
+ assertSame("sys/char" ,sys_elf._deps[1]);
+ assertSame("sys/io" ,sys_elf._deps[2]);
+ assertSame("sys/ary" ,sys_elf._deps[3]);
+ assertSame("sys/aryi64",sys_elf._deps[4]);
+ assertSame("sys/adt/bitset",sys_elf._deps[5]);
+ assertSame("sys", sys_elf._deps[6]);
+ assertSame("sys/scan" ,sys_elf._deps[7]);
+ assertSame("sys/adt" ,sys_elf._deps[8]);
+ assertSame("sys/libc" ,sys_elf._deps[9]);
+ assertSame("class:sys" ,sys_elf._clz._name);
+ }
+
+ @Test
+ public void testConstantClassInitialization() throws IOException {
+ TestC.runSF("val x = 42; return 0;","constantClassInt","",0);
+ TestC.runSF("val f = { -> 7; }; return 0;","constantClassFunction","",0);
+ }
+
+ @Test
+ public void testHelloWorld() throws IOException {
+ String expected = "Hello, World!\n";
+ String prog = "return sys.io.p(\""+expected+"\") - "+expected.length()+";";
+ // tests_raw1 concurrently builds Chapter22Test's helloWorld executable.
+ TestC.run(prog,"helloWorldSys",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION, null, null, expected,0);
+ }
+
+ @Test
+ public void testHelloWorldDriver() throws Exception {
+ String out = "build/objs/helloWorldDriver/";
+ Simple.main(new String[]{"-L",SYS_BLDDIR,"-o",out,"--norun","docs/examples/A_helloWorld.smp"});
+ assertTrue(Files.isRegularFile(Path.of(out,"A_helloWorld.o")));
+ }
+
+ @Test
+ public void testHelloWorldDriverLibFile() throws Exception {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ PrintStream saved = System.out;
+ try( PrintStream capture = new PrintStream(output,true,StandardCharsets.UTF_8) ) {
+ System.setOut(capture);
+ Simple.main(new String[]{"-L",SYS_FILE.toString(),"-o","build/objs/helloWorldDriverLibFile/",
+ "docs/examples/A_helloWorld.smp"});
+ } finally {
+ System.setOut(saved);
+ }
+ assertEquals("Hello, World!\n",output.toString(StandardCharsets.UTF_8));
+ }
+
+ @Test
+ public void testHelloWorldNoInline() throws Exception {
+ String base = "helloWorldNoInline";
+ String expected = "Hello, World!\n";
+ String prog = "return sys.io.p_noInline(\""+expected+"\") - "+expected.length()+";";
+ CodeGen code = new CheckedCodeGen(null,"build/objs",new Ary<>(new String[]{SYS_BLDDIR}),
+ base,prog,123L,true,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 = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,"nm",obj);
+ assertTrue(syms, syms.contains(" U sys.io.p_noInline"));
+
+ TestC.linkExe(obj,null,null,new Ary<>(new String[]{SYS_FILE.toString()}),exe);
+ String rez = TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe);
+ assertEquals(expected,rez);
+ }
+
+ @Test
+ public void testRedirectedRead() throws IOException {
+ 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 testErrnoAccessor() throws IOException {
+ String src = "val status=sys.libc.close(-1); return status != -1 || sys.libc.errno()==0;";
+ TestC.run(src,"errnoAccessor",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,"",-1);
+ }
+
+ @Test
+ public void testBubbles() throws IOException {
+ String src = Files.readString( Path.of("docs/examples/BubbleSort.smp"));
+ String exe = TestC.compile(src,"BubbleSort",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("[-17, 2, 3, 999]\n",
+ TestC.exec(exe,"[3, 2,-17, 999 ]"));
+ assertEquals("[1, 2, 3, 4, 4, 5]\n",
+ TestC.exec(exe,"[4, 5, 3, 1, 4, 2]"));
+ assertEquals("[1, 2, 3, 4, 5]\n",
+ TestC.exec(exe,"[1, 2, 3, 4, 5]"));
+ assertEquals("[1, 2, 3, 4, 5, 6, 7, 8, 9]\n",
+ TestC.exec(exe,"[9, 8, 7, 6, 5, 4, 3, 2, 1]"));
+
+ String usage = "Usage: please provide a list of at least two integers to sort in the format \"[1, 2, 3, 4, 5]\"\n";
+ assertEquals(usage,TestC.exec(exe));
+ assertEquals(usage,TestC.exec(exe,""));
+ assertEquals(usage,TestC.exec(exe,"[1]"));
+ assertEquals(usage,TestC.exec(exe,"[4 5 3]"));
+ }
+
+ @Test
+ public void testCapitalize() throws IOException {
+ String src = Files.readString(Path.of("docs/examples/Capitalize.smp"));
+ String exe = TestC.compile(src,"Capitalize",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("Hello world\n",TestC.exec(exe,"hello world"));
+ assertEquals("Hello World\n",TestC.exec(exe,"Hello World"));
+ assertEquals("123 apples\n",TestC.exec(exe,"123 apples"));
+ assertEquals("Usage: please provide a string\n",TestC.exec(exe));
+ assertEquals("Usage: please provide a string\n",TestC.exec(exe,""));
+ assertEquals("Use quotes around multiple strings.\n",TestC.exec(exe,"hello","world"));
+ }
+
+ @Test
+ public void testDijkstra() throws IOException {
+ String src = Files.readString(Path.of("docs/examples/Dijkstra.smp"));
+ String matrix = "[0, 2, 0, 6, 0, 2, 0, 3, 8, 5, 0, 3, 0, 0, 7, 6, 8, 0, 0, 9, 0, 5, 7, 9, 0]";
+ String exe = TestC.compile(src,"Dijkstra",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("2\n",TestC.exec(exe,matrix,"0","1"));
+ String usage = "Usage: please provide three inputs: a serialized matrix, a source node and a destination node\n";
+ assertEquals("7\n",TestC.exec(exe,matrix,"0","4"));
+ assertEquals(usage,TestC.exec(exe));
+ assertEquals(usage,TestC.exec(exe,"","",""));
+ assertEquals(usage,TestC.exec(exe,"[1, 0, 3, 0, 5, 1]","1","2"));
+ assertEquals(usage,TestC.exec(exe,"[0, 0, 0, 0]","0","1"));
+ }
+
+ @Test
+ public void testFileIO() throws IOException {
+ String src = Files.readString(Path.of("docs/examples/FileIO.smp"));
+ String exe = TestC.compile(src,"FileIO",new Ary<>(new String[]{SYS_BLDDIR}),
+ TestC.CALL_CONVENTION,null,null,-1);
+ assertEquals("File I/O succeeded\n",TestC.exec(exe));
+ }
+}
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 98e0f18a..21e677ce 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,13 +134,21 @@ 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));
+ }
// ------------
// Evaluate until exit or timeout. Each outer loop step computes
// all data nodes under some new Control.
while( true ) {
- assert BB!=null;
+ if( BB==null ) return null;
traceCtrl(BB,trace);
if( trace!=null ) System.out.println(F.p());
@@ -170,6 +178,7 @@ public abstract class Eval2 {
case RegionNode r : break;
default: throw Utils.TODO();
}
+ if( BB==null ) return null;
assert !BB.isDead();
}
}
@@ -183,28 +192,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;
}
@@ -253,36 +271,42 @@ 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 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(2))==0 ? 0L : 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 -> 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 RoundF32Node rnd -> (double)(float)d(rnd.in(1));
case ToIntegerNode cvt -> (long)d(cvt.in(1));
case ToFloatNode toflt-> (double)x(toflt.in(1));
@@ -291,12 +315,6 @@ public abstract class Eval2 {
};
}
- 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;
- }
-
// Fetch without unboxing, searching up-Frame
static Object val( Node n ) { return F.get(n); }
// Fetch and unbox as primitive long
@@ -304,7 +322,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); }
@@ -316,8 +336,6 @@ public abstract class Eval2 {
case TypeFunPtr tfp -> tfp;
case TypeMem mem -> "MEM";
case TypeMemPtr tmp -> {
- // Unknown-caller inputs are type placeholders, not heap constants.
- if( !tmp.isConstant() ) yield tmp;
if( tmp._obj.isAry() ) { // Constant array ptr
Type elem = tmp._obj.field("[]")._t;
if( elem instanceof TypeConAry con ) {
@@ -332,16 +350,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();
@@ -351,10 +386,10 @@ 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.size());
+ long sz = (Long)val(alloc.in(1));
long x = offToIdx(sz, type);
int n = (int)x;
if( n!=x || n<0 )
@@ -371,6 +406,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++ )
@@ -378,15 +417,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;
@@ -445,12 +485,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);
@@ -475,9 +514,9 @@ public abstract class Eval2 {
case TypeRPC rpc -> sb.p(x.toString());
case TypeMem mem -> sb.p("$mem");
case TypeTuple tt -> {
- if( tt._types.length>1 && tt._types[0] instanceof TypeMemPtr )
+ if( tt._types.length>1 && tt._types[1] instanceof TypeMemPtr )
// Assume a NewNode
- yield _print( tt._types[0], x, sb, visit );
+ yield _print( tt._types[1], x, sb, visit );
throw Utils.TODO();
}
case Type tt -> {
diff --git a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
index b022bec9..68bbe918 100644
--- a/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
+++ b/src/test/java/com/seaofnodes/simple/FuzzerWrap.java
@@ -7,19 +7,37 @@ import org.junit.Test;
import static org.junit.Assert.assertTrue;
/**
- * Normal tests run chapter-local regression seeds. Open failures and exploratory
- * fuzzing are opt-in (run directly in the IDE or temporarily remove Ignore).
- * Seeds depend on this chapter's generator; do not copy them between chapters.
+ * To use the fuzzer run the <code>fuzzPeeps</code> methods.
+ * This can be done from IntelliJ Gui or remove the `@Ignore` annotation and start the test case from the command line
+ * via <code>mvn clean test -Dtest=com.seaofnodes.simple.Fuzzer08Test#fuzzPeeps</code>
*/
public class FuzzerWrap {
private static final long[] REGRESSION_SEEDS = {
+ 375135762521757909L, // bad LCA
+ -148471672577312953L, // bulk mem
+ -5037182906211190034L, // dead Guard control
+ -6359653295501938199L, // monotonicity, escape is dead
+ 6506797708065910879L, // required field without a user constructor
+ 650692970082394944L, // recursive bulk mem split
+ 8335100609598232836L, // ternary result killed by region cleanup
+ 2520958273643234516L, // nested guard merge
+ -3772838984504063325L, // fuzzer regression
+ -4628356252269023530L, // idepth cache version wrap
+ -4022199781524079249L, // fuzzer regression
+ 8824787517620928178L, // unknown-mode unary minus during SCCP
+ -673501011619761901L, // peephole killed self
+ -8212834489697770130L, // bad keep/unkeep in parser
+ 478567486335112458L, // dying escape node
+ -5114548365532699915L, // dominate flags dependencies
+ -4710931876754133070L, // Tests monotonic phi widening
};
private static final long[] OPEN_FAILING_SEEDS = {
};
- @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds(REGRESSION_SEEDS); }
+
+ @Test public void fuzzPeepsRegression () { fuzzPeepsSeeds( REGRESSION_SEEDS); }
@Test @Ignore public void fuzzPeepsOpenFailures() { fuzzPeepsSeeds(OPEN_FAILING_SEEDS); }
private static void fuzzPeepsSeeds(long... seeds) {
@@ -30,29 +48,31 @@ public class FuzzerWrap {
@Ignore
@Test
- public void fuzzPeepsLarge() {
+ public void fuzzPeepsRandom() {
+ Random R = new Random(System.currentTimeMillis());
var fuzzer = new Fuzzer();
- for (int i=0; i<1000000; i++)
- fuzzer.fuzzPeeps(i);
+ for (int i=0; i<100; i++)
+ fuzzer.fuzzPeeps(R.nextLong());
assertTrue(fuzzer.noExceptions());
}
- @Test @Ignore
- public void fuzzPeepsRandom() {
- Random R = new Random(System.currentTimeMillis());
+ @Ignore
+ @Test
+ public void fuzzPeepsLarge() {
var fuzzer = new Fuzzer();
- for (int i=0; i<100; i++)
- fuzzer.fuzzPeeps( R.nextLong());
+ for (int i=0; i<1000000; i++)
+ fuzzer.fuzzPeeps(i);
assertTrue(fuzzer.noExceptions());
}
- @Test
@Ignore
+ @Test
public void fuzzPeepTiming() {
var fuzzer = new Fuzzer();
int max_nid=0;
for (int i=0; i<1000000; i++)
max_nid = fuzzer.fuzzPeepTiming(i, max_nid);
}
+
}
diff --git a/src/test/java/com/seaofnodes/simple/LiteralTest.java b/src/test/java/com/seaofnodes/simple/LiteralTest.java
deleted file mode 100644
index 09ee30f5..00000000
--- a/src/test/java/com/seaofnodes/simple/LiteralTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-package com.seaofnodes.simple;
-
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.type.TypeConAryB;
-import com.seaofnodes.simple.type.TypeInteger;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-public class LiteralTest {
- private static final String[] ESCAPES = {"\\0", "\\n", "\\t", "\\r", "\\\\", "\\\"", "\\'"};
- private static final int[] VALUES = {0, 10, 9, 13, 92, 34, 39};
-
- @Test public void testStringEscapes() {
- for (int i = 0; i < ESCAPES.length; i++)
- assertString(ESCAPES[i], new byte[]{(byte)VALUES[i]});
- assertString("a\\\"b\\\\c\\0d", new byte[]{97, 34, 98, 92, 99, 0, 100});
- assertString("", new byte[]{});
- assertString("plain", new byte[]{112, 108, 97, 105, 110});
- }
-
- private static void assertString(String text, byte[] expected) {
- var code = new CodeGen("return \"" + text + "\";").parse().opto().typeCheck();
- TypeMemPtr ptr = (TypeMemPtr)code.expr()._type;
- TypeConAryB bytes = (TypeConAryB)ptr._obj.field("[]")._t;
- assertArrayEquals(text, expected, bytes._ary);
- }
-
- @Test public void testCharacterEscapes() {
- for (int i = 0; i < ESCAPES.length; i++)
- assertCharacter(ESCAPES[i], VALUES[i]);
- assertCharacter("A", 65);
- }
-
- private static void assertCharacter(String text, int expected) {
- var code = new CodeGen("return '" + text + "';").parse().opto().typeCheck();
- assertEquals(text, expected, ((TypeInteger)code.expr()._type).value());
- }
-
- @Test public void testMalformedStrings() {
- assertBadLiteral("return \"\\q\";", "Unknown string escape");
- assertBadLiteral("return \"abc", "Unclosed string");
- assertBadLiteral("return \"abc\\", "Unclosed string");
- // The quote closes the string at EOF; the missing token is the semicolon.
- assertBadLiteral("return \"plain\"", ";");
- }
-
- @Test public void testMalformedCharacters() {
- assertBadLiteral("return '\\q';", "Unknown character escape");
- assertBadLiteral("return '", "Unclosed character");
- assertBadLiteral("return '\\", "Unclosed character");
- assertBadLiteral("return '';", "Syntax error");
- assertBadLiteral("return 'ab';", "Syntax error");
- assertBadLiteral("return 'a", "Syntax error");
- }
-
- private static void assertBadLiteral(String src, String message) {
- try {
- new CodeGen(src).parse();
- fail("Accepted malformed literal: " + src);
- } catch (Parser.ParseException e) {
- assertTrue(e.getMessage(), e.getMessage().contains(message));
- }
- }
-}
diff --git a/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
index e97194af..9406f480 100644
--- a/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
+++ b/src/test/java/com/seaofnodes/simple/MemoryEncodingTest.java
@@ -1,18 +1,8 @@
package com.seaofnodes.simple;
-import com.seaofnodes.isa.eval.EvalRisc5;
-
-import com.seaofnodes.isa.eval.EvalArm64;
-
import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.RegAllocTestSupport;
import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.node.cpus.arm.arm;
-import com.seaofnodes.simple.node.cpus.riscv.riscv;
-import com.seaofnodes.simple.type.TypeMemPtr;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
+import java.util.BitSet;
import org.junit.Test;
import static org.junit.Assert.*;
@@ -65,117 +55,50 @@ public class MemoryEncodingTest {
return sum;
"""
};
- for( int p=0; p<sources.length; p++ ) {
- CodeGen code=new CodeGen(sources[p]).parse().opto();
- if( p==0 ) assertNull(code._stop.walk(n ->
- n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr &&
- ptr.in(0) instanceof NewNode nn &&
- nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null));
- for( int arg=0; arg<7; arg++ ) {
- long expected=switch(p) {
- case 0 -> 106*arg+(arg>=2 ? 2 : 0);
- case 1 -> (arg&1)==0 ? 140 : 60;
- case 2 -> 130;
- default -> 16;
- };
- assertEquals(Long.toString(expected),Eval2.eval(code,arg));
- }
- }
- }
-
- private static final String[] SOURCES = {
- Chapter16Test.CONSTRUCTOR_MEMORY,
- Chapter18Test.CALL_MEMORY,
- Chapter18Test.RECURSIVE_MEMORY,
- Chapter18Test.INLINE_MEMORY,
- "int[] !a = new int[7]; a[arg]=arg+3; return a[0]+1;",
- """
- int[] !a = new int[7];
- for( int i=0; i<7; i++ ) a[i]=i+arg;
- for( int i=0; i<6; i++ ) a[i+1]+=a[i];
- return a[6];
- """
- };
-
- private static long expected(int program, long arg) {
- return switch(program) {
- case 0 -> arg==0 ? 111249 : 222351+arg;
- case 1 -> arg==0 ? 10101325 : 20202322;
- case 2 -> (10*(arg+1)+arg*(arg+1)/2)*100+10+arg;
- case 3 -> 400944+arg*10001;
- case 4 -> arg==0 ? 4 : 1;
- case 5 -> 21+7*arg;
- default -> throw new AssertionError();
- };
- }
-
- @Test public void testEmulatedMemory() throws IOException {
- for( String target : new String[]{"riscv","arm"} )
- for( int p=0; p<SOURCES.length; p++ ) {
- CodeGen code = new RegAllocTestSupport.CheckedCodeGen(SOURCES[p]).driver(target,"SystemV",null);
- int entry=0;
- for( var n : code._start._outputs )
- if( n instanceof FunNode fun && "main".equals(fun._name) )
- entry=code._encoding._opStart[fun._nid];
+ // Exercise both unresolved Store aliases and nested value-Phi cleanup.
+ for( long seed : new long[]{0,9,126} )
+ for( int p=0; p<sources.length; p++ ) {
+ CodeGen code=new CodeGen(sources[p],seed,true).parse().opto();
+ if( p==0 ) assertNull(code._stop.walk(n ->
+ n instanceof LoadNode ld && ld.ptr() instanceof ProjNode ptr &&
+ ptr.in(0) instanceof NewNode nn &&
+ nn.size()._type==com.seaofnodes.simple.type.TypeInteger.constant(110) ? ld : null));
for( int arg=0; arg<7; arg++ ) {
- byte[] image = new byte[1<<20];
- byte[] bits = code._encoding.bits();
- System.arraycopy(bits,0,image,0,bits.length);
- long result;
- if( target.equals("riscv") ) {
- EvalRisc5 cpu = new EvalRisc5(image,1<<16);
- cpu._pc=entry;
- cpu.regs[riscv.A0]=arg;
- assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000));
- result=cpu.regs[riscv.A0];
- } else {
- EvalArm64 cpu = new EvalArm64(image,1<<16);
- cpu._pc=entry;
- cpu.regs[arm.X0]=arg;
- assertEquals(target+" program "+p+" arg "+arg,0,cpu.step(100000));
- result=cpu.regs[arm.X0];
- }
- assertEquals(target+" program "+p+" arg "+arg,expected(p,arg),result);
+ long expected=switch(p) {
+ case 0 -> 106*arg+(arg>=2 ? 2 : 0);
+ case 1 -> (arg&1)==0 ? 140 : 60;
+ case 2 -> 130;
+ default -> 16;
+ };
+ assertEquals(Long.toString(expected),Eval2.eval(code,arg));
}
}
}
- @Test public void testNeverMemory() {
- // A varying pointer prevents store sinking: the synthetic exit needs both slices.
- for( String target : new String[]{"x86_64_v2","riscv","arm"} ) {
- CodeGen code = new CodeGen("""
- struct S { int x; int y; };
- !S !a=new S; !S !b=new S; !S !p=a;
- if( arg ) while( 1 ) { p.x+=arg; p.y+=p.x; p=p==a ? b : a; }
- return a.x+a.y;
- """).driver(CodeGen.Phase.LoopTree);
- assertNotNull(code._stop.walk(n -> {
- if( n instanceof MemMergeNode mem &&
- mem.in(0) instanceof CProjNode exit && exit.in(0) instanceof NeverNode ) {
- var fields = ((TypeMemPtr)Parser.TYPES.get("S"))._obj._fields;
- assertTrue(mem.alias(fields[0]._alias) instanceof MemPhiNode);
- assertTrue(mem.alias(fields[1]._alias) instanceof MemPhiNode);
- return mem;
- }
- return null;
- }));
- code.instSelect(target,"SystemV").GCM().localSched().regAlloc();
- RegAllocTestSupport.checkRegisters(code);
- code.encode();
- }
+ @Test public void testKeepReadsBeforeWrites() {
+ var code = new CodeGen("""
+ struct S { int x; int y; };
+ !S !a = new S; !S !b = new S;
+ a.x=arg; b.x=arg+1;
+ !S !p=a; !S !q=b;
+ if (arg<0) { p=b; q=a; }
+ int v;
+ if (arg>1) { v=p.x; p.x=41; p.y=5; }
+ else { v=q.x; q.x=42; }
+ return v;
+ """).parse().opto();
+ StopNode stop = code._stop;
+ assertEquals(2,countMemoryNodes(stop,LoadNode.class,new BitSet()));
+ assertEquals("-1",Eval2.eval(code,-1));
+ assertEquals("1",Eval2.eval(code,0));
+ assertEquals("3",Eval2.eval(code,3));
}
- @Test public void testNativeMemory() throws IOException {
- Files.createDirectories(Path.of("build/objs"));
- for( int p=0; p<SOURCES.length; p++ ) {
- String file="build/objs/memory"+p;
- StringBuilder c = new StringBuilder("extern long long memory(long long);\nint main() {\n");
- for( int arg=0; arg<7; arg++ )
- c.append("if(memory(").append(arg).append(")!=").append(expected(p,arg)).append("LL) return ").append(arg+1).append(";\n");
- c.append("return 0;\n}\n");
- Files.writeString(Path.of(file+".c"),c);
- TestC.run("val memory = { int arg -> "+SOURCES[p]+" };",
- TestC.CALL_CONVENTION,null,"",file+".c",file,"S","",-1);
- }
+ private static int countMemoryNodes(Node n, Class<?> kind, BitSet seen) {
+ if (n==null || seen.get(n._nid)) return 0;
+ seen.set(n._nid);
+ int cnt=kind.isInstance(n) ? 1 : 0;
+ for (Node def : n._inputs) cnt+=countMemoryNodes(def,kind,seen);
+ return cnt;
}
}
diff --git a/src/test/java/com/seaofnodes/simple/MergeSortTest.java b/src/test/java/com/seaofnodes/simple/MergeSortTest.java
index 453bd35c..f5d3d146 100644
--- a/src/test/java/com/seaofnodes/simple/MergeSortTest.java
+++ b/src/test/java/com/seaofnodes/simple/MergeSortTest.java
@@ -2,12 +2,10 @@ package com.seaofnodes.simple;
import com.seaofnodes.isa.eval.EvalRisc5;
-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;
@@ -17,52 +15,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, 34);
+ 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/MutabilityTest.java b/src/test/java/com/seaofnodes/simple/MutabilityTest.java
index 7ae80314..60e4c5aa 100644
--- a/src/test/java/com/seaofnodes/simple/MutabilityTest.java
+++ b/src/test/java/com/seaofnodes/simple/MutabilityTest.java
@@ -22,20 +22,20 @@ public class MutabilityTest {
}
@Test public void testIndependentBindingsAndViews() {
- ok("struct P { int x; }; !P ~p=new P; p.x=7; return p.x;", "7");
- ok("struct P { int x; }; ~P !p=new P{x=3;}; p=new P{x=7;}; return p.x;", "7");
- bad("struct P { int x; }; !P ~p=new P; p=new P; return 0;");
- bad("struct P { int x; }; ~P !p=new P; p.x=7; return 0;");
+ ok("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); p.x=7; return p.x;", "7");
+ ok("struct P { int x; new P = { int v -> x=v; }; }; ~P !p=new P(3); p=new P(7); return p.x;", "7");
+ bad("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); p=new P(0); return 0;");
+ bad("struct P { int x; new P = { int v -> x=v; }; }; ~P !p=new P(0); p.x=7; return 0;");
bad("int ~x=3; x=7; return x;");
ok("int !x=3; x=7; return x;", "7");
}
@Test public void testDeepViewAndAliases() {
- ok("struct P { int x; }; !P ~p=new P; ~P ~r=p; p.x=7; return r.x;", "7");
- bad("struct P { int x; }; struct Box { !P p; }; !P q=new P; Box b=new Box{p=q;}; b.p.x=7; return 0;");
- bad("struct P { int x; }; P p=new P; !P q=p; q.x=7; return 0;");
- bad("struct P { P? next; int x; }; !P p=new P{next=new P;}; p.next.x=7; return 0;");
- bad("struct P { !P? next; int x; }; P p=new P{next=new P;}; p.next.x=7; return 0;");
+ ok("struct P { int x; new P = { int v -> x=v; }; }; !P ~p=new P(0); ~P ~r=p; p.x=7; return r.x;", "7");
+ bad("struct P { int x; new P = { int v -> x=v; }; }; struct Box { !P p; new Box = { !P v -> p=v; }; }; !P p=new P(0); Box b=new Box(p); b.p.x=7; return 0;");
+ bad("struct P { int x; new P = { int v -> x=v; }; }; P p=new P(0); !P q=p; q.x=7; return 0;");
+ bad("struct P { P? next; int x; new P = { P? v -> next=v; }; }; !P p=new P(new P(null)); p.next.x=7; return 0;");
+ bad("struct P { !P? next; int x; new P = { !P? v -> next=v; }; }; P p=new P(new P(null)); p.next.x=7; return 0;");
}
@Test public void testInferencePreservesAccess() {
@@ -53,18 +53,12 @@ public class MutabilityTest {
bad("struct P { int x; }; val f={ P?[] a -> return a#; }; !P?[] a=new !P?[1]; return f(a);");
}
- @Test public void testConstructorScope() {
- ok("struct P { int ~x; }; P p=new P{if(arg) x=3; else x=7;}; return p.x;", "7");
- bad("int ~outer=1; struct P { int x; }; P p=new P{outer=7;}; return outer;");
- bad("struct P { int x; }; P p=new P{int ~local=1; local=7;}; return p.x;");
- }
-
@Test public void testArrayLayers() {
bad("struct P { int x; }; !P?[] rw=new !P?[1]; P?[] ro=rw; rw[0]=new P; ro[0].x=7; return 0;");
ok("u8[] a=new u8[2]; a[0]=7; return a[0];", "7");
- ok("struct P { int x; }; P?[] a=new P?[1]; a[0]=new P{x=7;}; return a[0].x;", "7");
- bad("struct P { int x; }; P?[] a=new P?[1]; a[0]=new P; a[0].x=7; return 0;");
- ok("struct P { int x; }; !P?[] a=new !P?[1]; a[0]=new P; a[0].x=7; return a[0].x;", "7");
+ ok("struct P { int x; new P = { int v -> x=v; }; }; P?[] a=new P?[1]; a[0]=new P(7); return a[0].x;", "7");
+ bad("struct P { int x; new P = { int v -> x=v; }; }; P?[] a=new P?[1]; a[0]=new P(0); a[0].x=7; return 0;");
+ ok("struct P { int x; new P = { int v -> x=v; }; }; !P?[] a=new !P?[1]; a[0]=new P(0); a[0].x=7; return a[0].x;", "7");
bad("u8[~] a=new u8[1]; a[0]=7; return 0;");
ok("u8[~]?[] rows=new u8[~]?[1]; rows[0]=new u8[1]; return rows#;", "1");
bad("u8[~]?[] rows=new u8[~]?[1]; rows[0]=new u8[1]; rows[0][0]=7; return 0;");
diff --git a/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java b/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
new file mode 100644
index 00000000..f3f65183
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/NativeExecutionTest.java
@@ -0,0 +1,69 @@
+package com.seaofnodes.simple;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+public class NativeExecutionTest {
+ // A subprocess with deterministic stdout, stderr, and process status.
+ public static class Child {
+ public static void main(String[] args) {
+ System.out.println("before exit");
+ System.err.println("child diagnostic");
+ System.exit(Integer.parseInt(args[0]));
+ }
+ }
+
+ private static String child(int exit) throws IOException {
+ String java = Path.of(System.getProperty("java.home"),"bin","java").toString();
+ return TestC.exec(java,"-cp",System.getProperty("java.class.path"),
+ Child.class.getName(),Integer.toString(exit));
+ }
+
+ private static void checkFailure(int exit) throws IOException {
+ try {
+ child(exit);
+ fail("Accepted process exit "+exit);
+ } catch( IOException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("exit code: "+exit));
+ assertTrue(e.getMessage(),e.getMessage().contains("before exit"));
+ assertTrue(e.getMessage(),e.getMessage().contains("child diagnostic"));
+ }
+ }
+
+ @Test public void testNonzeroExit() throws IOException { checkFailure(7); }
+
+ @Test public void testFullWindowsExitCode() throws IOException {
+ assumeTrue(TestC.OS.startsWith("Windows"));
+ checkFailure(256); // Narrowing to a byte incorrectly reports success.
+ checkFailure(2816); // Observed Cygwin status for the Hello World crash.
+ }
+
+ @Test public void testSuccessfulOutput() throws IOException {
+ assertEquals("before exit"+System.lineSeparator(),child(0));
+ }
+
+ @Test public void testHelloWorldArtifacts() throws IOException {
+ new Chapter22Test().testHelloWorld();
+ new Chapter25Test().testHelloWorld();
+ // Model a parallel run paused between Chapter 22's link and execution.
+ // Chapter 25 must not replace the executable Chapter 22 is about to run.
+ String exe = "build/objs/helloWorld"+(TestC.OS.startsWith("Windows") ? ".exe" : "");
+ assertEquals("Hello, World!",TestC.exec(exe));
+ }
+
+ @Test public void testDriverNonzeroExit() throws Exception {
+ Path dir = Files.createTempDirectory(Path.of("build/objs"),"driver-exit-");
+ Path src = dir.resolve("fail.smp");
+ Files.writeString(src,"return 7;");
+ try {
+ Simple.main(new String[]{src.toString()});
+ fail("Driver accepted a failed executable");
+ } catch( IOException e ) {
+ assertTrue(e.getMessage(),e.getMessage().contains("exit code: 7"));
+ }
+ }
+}
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 3b19857a..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 18804ebd..b7e10122 100644
--- a/src/test/java/com/seaofnodes/simple/Simple.java
+++ b/src/test/java/com/seaofnodes/simple/Simple.java
@@ -1,66 +1,65 @@
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_PASS_NAME = 1<<30;
- static final int DUMP_AFTER_ALL = 0xff | DUMP_PASS_NAME;
-
- 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
- -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
+ --dump - dump final intermediate representation only
"""
);
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);
@@ -73,71 +72,34 @@ Options:
System.out.println(TestC.CALL_CONVENTION);
}
- static void dump(CodeGen code, int dump, int pass) {
- if ((dump & pass) != 0) {
- 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 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++ ) {
@@ -149,23 +111,32 @@ 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 "-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 "-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;
@@ -179,16 +150,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 {
@@ -196,70 +219,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, true, 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, "", TestC.linkObjs(externPaths), exe);
System.out.print(result);
}
}
diff --git a/src/test/java/com/seaofnodes/simple/SpillStats.java b/src/test/java/com/seaofnodes/simple/SpillStats.java
index ad044a52..d43f1a36 100644
--- a/src/test/java/com/seaofnodes/simple/SpillStats.java
+++ b/src/test/java/com/seaofnodes/simple/SpillStats.java
@@ -1,13 +1,15 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
+import com.seaofnodes.simple.type.TypeInteger;
+import java.nio.file.*;
import java.util.Map;
import java.util.TreeMap;
import org.junit.runner.*;
import org.junit.runner.notification.*;
-// Run existing tests and sum actual allocations, including ones without goldens.
-// Helpers call record after allocation; ordinary JUnit runs collect nothing.
+// Earlier cohorts freeze source/target membership; Chapter 25 retains native checks.
public class SpillStats extends RunListener {
private static SpillStats ACTIVE;
private Description _test;
@@ -15,30 +17,25 @@ public class SpillStats extends RunListener {
private final Map<String,long[]> _totals = new TreeMap<>();
@Override public void testStarted(Description test) { _test = test; }
- public static void record(CodeGen code, String cohort, String cpu, String abi) {
- if( ACTIVE==null ) return;
- String test = ACTIVE._test.getTestClass().getSimpleName()+"."+ACTIVE._test.getMethodName();
+ @Override public void testFinished(Description test) { _test = null; }
+
+ public static void record25(CodeGen code) {
+ if( ACTIVE==null || ACTIVE._test==null || ACTIVE._test.getTestClass()!=Chapter25Test.class ) return;
+ ACTIVE.record(code,"Chapter25",ACTIVE._test.getMethodName(),code._mach.getClass().getSimpleName(),code._callingConv);
+ }
+
+ private void record(CodeGen code, String cohort, String test, String cpu, String abi) {
for( String key : new String[]{cohort+","+cpu+","+abi,cohort+",ALL,ALL"} ) {
- long[] sum = ACTIVE._totals.computeIfAbsent(key,k -> new long[3]);
+ long[] sum = _totals.computeIfAbsent(key,k -> new long[3]);
sum[0]++; sum[1] += code._regAlloc._spills; sum[2] += code._regAlloc._spillScaled;
}
System.out.println("allocation,"+cohort+","+test+","+cpu+","+abi+","+
code._regAlloc._spills+","+code._regAlloc._spillScaled);
}
- // The same native helpers serve old and new program cohorts.
- public static void recordNative(CodeGen code, String cpu, String abi) {
- String cohort = ACTIVE!=null && (ACTIVE._test.getTestClass()==Chapter24Test.class || ACTIVE._test.getTestClass()==Chapter24AllocTest.class)
- ? "Chapter24" : ACTIVE!=null && ACTIVE._test.getTestClass()==Chapter23Test.class
- ? "Chapter23" : ACTIVE!=null && ACTIVE._test.getTestClass()==Chapter22Test.class
- ? "Chapter22" : "Chapter21";
- record(code,cohort,cpu,abi);
- }
-
- // Keep running every CPU and native result check when measuring quality.
- // A changed golden still makes spill-stats fail at the end.
+ // Keep native result checks running during a quality comparison.
public static void checkSpills(int expected, int actual) {
- if( expected==-1 ) return;
+ if( expected==-1 || CodeGen.iterSeedOverridden() ) return;
try { org.junit.Assert.assertEquals("Expect spills:",expected,actual,Math.max(1,expected>>3)); }
catch( AssertionError error ) {
if( ACTIVE==null ) throw error;
@@ -47,27 +44,49 @@ public class SpillStats extends RunListener {
}
}
- public static void main(String[] args) throws ClassNotFoundException {
- if( args.length==0 ) args = new String[]{"Chapter20Test","Chapter21Test","Chapter21AllocTest","Chapter22Test","Chapter23Test","Chapter23AllocTest","Chapter24Test","Chapter24AllocTest","BrainFuckTest","MergeSortTest"};
- Class<?>[] tests = new Class<?>[args.length];
- for( int i=0; i<args.length; i++ )
- tests[i] = Class.forName("com.seaofnodes.simple."+args[i]);
+ public static void main(String[] args) throws Exception {
ACTIVE = new SpillStats();
+ Path dir = Path.of("src/test/java/com/seaofnodes/simple/spill");
+ int failures=0, attempts=0;
+ for( String line : Files.readAllLines(dir.resolve("cohorts.tsv")) ) {
+ if( line.startsWith("#") || line.isBlank() ) continue;
+ attempts++;
+ String[] row = line.split("\t");
+ try {
+ String src = Files.readString(dir.resolve(row[5]));
+ CodeGen code = new CheckedCodeGen(null,null,null,null,src,123L,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.RegAlloc,row[2],row[3]);
+ ACTIVE.record(code,row[0],row[1],row[2],row[3]);
+ } catch( Throwable error ) {
+ failures++;
+ System.err.println(line);
+ error.printStackTrace();
+ }
+ }
+ // Include the library's allocation even when make's sys.o is up to date.
+ try {
+ CodeGen code = new CheckedCodeGen("src/main/smp","build/spill-sys",null,"sys",
+ Files.readString(Path.of("src/main/smp/sys.smp")),456L,true,TypeInteger.BOT)
+ .driver(CodeGen.Phase.Encoding,TestC.CPU_PORT,TestC.CALL_CONVENTION);
+ ACTIVE.record(code,"Chapter25","sys",TestC.CPU_PORT,TestC.CALL_CONVENTION);
+ } catch( Throwable error ) {
+ failures++;
+ error.printStackTrace();
+ }
JUnitCore junit = new JUnitCore();
junit.addListener(ACTIVE);
- Result result = junit.run(tests);
- boolean passed = result.wasSuccessful() && ACTIVE._qualityFailures==0;
- if( !result.wasSuccessful() ) System.out.println("FAILED test run: totals are not a complete comparison.");
- else if( !passed ) System.out.println("Spill expectations differ; allocation and runtime checks passed.");
+ Result result = junit.run(Chapter25Test.class);
+ for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace());
+ failures += result.getFailureCount();
+ if( failures!=0 ) System.out.println("FAILED: totals are not a complete comparison.");
System.out.println("cohort,cpu,abi,allocations,splits,scaled");
for( var row : ACTIVE._totals.entrySet() ) {
long[] sum = row.getValue();
System.out.println(row.getKey()+","+sum[0]+","+sum[1]+","+sum[2]);
}
- for( Failure failure : result.getFailures() ) System.err.println(failure.getTrace());
- System.out.println("Tests: "+result.getRunCount()+", failures: "+result.getFailureCount());
- System.out.println("Spill golden failures: "+ACTIVE._qualityFailures);
+ System.out.println("Historical allocations: "+attempts+"; native tests: "+result.getRunCount()+"; failures: "+failures+"; spill golden failures: "+ACTIVE._qualityFailures);
+ failures += ACTIVE._qualityFailures;
ACTIVE = null;
- if( !passed ) System.exit(1);
+ if( failures!=0 ) System.exit(1);
}
-}
+} \ No newline at end of file
diff --git a/src/test/java/com/seaofnodes/simple/StaticDataTest.java b/src/test/java/com/seaofnodes/simple/StaticDataTest.java
new file mode 100644
index 00000000..070b7a9e
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/StaticDataTest.java
@@ -0,0 +1,112 @@
+package com.seaofnodes.simple;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.StaticData;
+import com.seaofnodes.simple.type.TypeStruct;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.isa.eval.EvalArm64;
+import com.seaofnodes.isa.eval.EvalRisc5;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class StaticDataTest {
+ private static final String SRC="val n=42; val f={int x -> x+n;}; return 0;";
+
+ private static StaticData.ObjectData root(CodeGen code) {
+ return code._encoding._data.entries().stream()
+ .filter(obj -> obj.symbol.endsWith("Test.$class")).findFirst().orElseThrow();
+ }
+
+ // Read the image before <clinit>, then call the function through its data slot.
+ @Test public void testEmulatorImage() {
+ for( String cpu : new String[]{"arm","riscv"} ) {
+ CodeGen code=new CodeGen(SRC).driver(cpu,"SystemV",true,false);
+ var e=code._encoding;
+ var obj=root(code);
+ TypeStruct ts=(TypeStruct)obj.type;
+ int cp=(e._bits.size()+15)&-16, sd=(cp+e._cpool.size()+15)&-16;
+ byte[] image=new byte[1<<20];
+ System.arraycopy(e._bits.buf(),0,image,0,e._bits.size());
+ System.arraycopy(e._cpool.buf(),0,image,cp,e._cpool.size());
+ System.arraycopy(e._sdata.buf(),0,image,sd,e._sdata.size());
+ int n=sd+obj.offset+ts.offset(ts.find("n"));
+ int f=sd+obj.offset+ts.offset(ts.find("f"));
+ if( cpu.equals("arm") ) {
+ var arm=new EvalArm64(image,1<<16);
+ assertEquals(42,arm.ld4s(n));
+ arm._pc=arm.ld4s(f);
+ arm.regs[0]=7;
+ assertEquals(0,arm.step(1000));
+ assertEquals(49,arm.regs[0]);
+ } else {
+ var r5=new EvalRisc5(image,1<<16);
+ assertEquals(42,r5.ld4s(n));
+ r5._pc=r5.ld4s(f);
+ r5.regs[10]=7;
+ assertEquals(0,r5.step(1000));
+ assertEquals(49,r5.regs[10]);
+ }
+ }
+ }
+
+ @Test public void testNativeImage() throws Exception {
+ Path dir=Path.of("build/objs/static-data");
+ Files.createDirectories(dir);
+ CodeGen code=new CodeGen(null,dir.toString(),null,"Test",SRC,123L,true,
+ TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ TypeStruct ts=(TypeStruct)root(code).type;
+ Path driver=dir.resolve("driver.c");
+ Files.writeString(driver,"""
+ #include <stdint.h>
+ #include <string.h>
+ extern unsigned char data[] __asm__("Test.$class");
+ typedef int64_t (__attribute__((CALL_CONV)) *fun)(int64_t);
+ int main(void) {
+ int64_t n; uint32_t f;
+ memcpy(&n,data+%d,sizeof(n));
+ memcpy(&f,data+%d,sizeof(f));
+ return n!=42 || ((fun)(uintptr_t)f)(7)!=49;
+ }
+ """.formatted(ts.offset(ts.find("n")),ts.offset(ts.find("f"))));
+ String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ TestC.linkExe(dir.resolve("Test.o").toString(),
+ TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",
+ driver.toString(),null,exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+
+ // Two separately compiled users must reference the same mutable class object.
+ @Test public void testSharedClass() throws Exception {
+ Path dir=Path.of("build/objs/static-data-shared");
+ Files.createDirectories(dir);
+ Path lib=dir.resolve("lib");
+ Files.createDirectories(lib);
+ var paths=new Ary<>(new String[]{lib.toString()});
+ String[] names={"Globals","A","B"};
+ String[] sources={"int count=0;",
+ "val a={ -> Globals; };", "val b={ -> Globals; };"};
+ for( int i=0; i<names.length; i++ )
+ new CodeGen(null,(i==0?lib:dir).toString(),i==0?null:paths,names[i],sources[i],123L,true,TypeInteger.BOT)
+ .driver(TestC.CPU_PORT,TestC.CALL_CONVENTION,false,false);
+ Path driver=dir.resolve("driver.c");
+ Files.writeString(driver,"""
+ #include <stdint.h>
+ __attribute__((CALL_CONV)) extern int64_t *a(void),*b(void);
+ int main(void) {
+ int64_t *x=a(),*y=b();
+ if(x!=y) return 1;
+ *x=37;
+ return *y!=37;
+ }
+ """);
+ String exe=dir.resolve(TestC.OS.startsWith("Windows")?"test.exe":"test").toString();
+ TestC.linkExe(lib.resolve("Globals.o").toString(),
+ TestC.CALL_CONVENTION.equals("win64")?"ms_abi":"sysv_abi",
+ driver.toString(),new Ary<>(new String[]{dir+"/A.o",dir+"/B.o"}),exe);
+ assertEquals("",TestC.exec(TestC.TEST_TIMEOUT_SECONDS,exe));
+ }
+}
diff --git a/src/test/java/com/seaofnodes/simple/TestArm64.java b/src/test/java/com/seaofnodes/simple/TestArm64.java
index 086bf9c7..f1089cf0 100644
--- a/src/test/java/com/seaofnodes/simple/TestArm64.java
+++ b/src/test/java/com/seaofnodes/simple/TestArm64.java
@@ -3,13 +3,10 @@ package com.seaofnodes.simple;
import com.seaofnodes.isa.eval.EvalArm64;
import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
-
-
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;
@@ -17,35 +14,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 CheckedCodeGen(src).driver("arm", "SystemV",null);
- SpillStats.recordNative(code,"arm","SystemV");
- if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
+ CodeGen code = new CheckedCodeGen(src).driver("arm", "SystemV",true,false);
// Allocation quality not degraded
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ int delta = spills>>3;
+ if( delta == 0 ) delta =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)
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 3b3aabc0..b08be824 100644
--- a/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/src/test/java/com/seaofnodes/simple/TestC.java
@@ -5,6 +5,8 @@ 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;
@@ -14,93 +16,171 @@ import static org.junit.Assert.*;
// Call a C driver program, which calls a Simple-generated .o, and self-checks.
// Expects GCC from the default CLI, compiles C, compiles Simple, links and runs.
public abstract class TestC {
+ public static final int TEST_TIMEOUT_SECONDS = 60;
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 final String CPU_ABI = CPU_PORT+"_"+CALL_CONVENTION;
+ public static final String RELEASE_SYS_DIR = "build/release/"+CPU_ABI;
- 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 String runtimeObject() {
+ String home = System.getenv("SIMPLE_HOME");
+ String root = home==null || home.isEmpty() ? "build/release" : home;
+ return Path.of(root,CPU_ABI,"simple_crt.o").toString();
}
- // 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;
+ public static final String C_DRIVERS_DIR = "src/test/java/com/seaofnodes/simple/progs/";
- // 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
+ * - 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);
}
- // 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.
+ * - 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);
}
- public static void run(String dir, String file, String expected, int spills ) throws IOException {
- run(dir, file,null, expected, spills, false);
+ /**
+ * 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);
}
- // 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 runArgs( String src, String base, Ary<String> externPaths, String simple_conv,
+ String expected, int spills, String... programArgs ) throws IOException {
+ run0(src,base,externPaths,simple_conv,null,null,null,expected,spills,programArgs);
}
- 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 base, Ary<String> externPaths, String simple_conv, String c_conv, String cfile, String stdin, String expected, int spills ) throws IOException {
+ run0(src,base,externPaths,simple_conv,c_conv,cfile,stdin,expected,spills,new String[0]);
}
+ private static void run0( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv,
+ String cfile, String stdin, String expected, int spills, String[] programArgs ) throws IOException {
+ String exe = compile(src,base,externPaths,simple_conv,c_conv,cfile,spills);
- 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;
+ String[] execArgs = new String[programArgs.length+1];
+ execArgs[0] = exe;
+ System.arraycopy(programArgs,0,execArgs,1,programArgs.length);
+ String result = execStdin(stdin, 0, execArgs );
+ assertEquals(expected,result);
+ }
+
+ public static String compile( String src, String base, Ary<String> externPaths, String simple_conv, String c_conv,
+ String cfile, 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 CheckedCodeGen(src, arg).driver( CPU_PORT, simple_conv, obj);
- SpillStats.recordNative(code,CPU_PORT,simple_conv);
+ CodeGen code = new CheckedCodeGen(null,"build/objs",externPaths,base,src,126L,true,TypeInteger.BOT);
+ code.driver( CPU_PORT, simple_conv, false, cfile==null );
- String result = gcc(obj, c_conv, cfile, false, exe );
- assertEquals(expected,result);
+ linkExe(obj, c_conv, cfile, linkObjs(externPaths), exe);
// Allocation quality not degraded
SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ return 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 main, double ignore, String... objs ) throws IOException {
+ String BLDDIR = "build/objs/test0/";
+ String exe = BLDDIR+main+(OS.startsWith("Windows") ? ".exe" : "");
+ linkExe(objs[0],null,null,null,exe);
+ // Module tests intentionally use the program exit code as their result.
+ ExecResult result = execute(null,0,exe);
+ return result.exit==0 ? result.out : "exec exit code: "+result.exit;
+ }
+
+ // 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 {
+ linkExe(obj,c_conv,cfile,linkObjs,args[0]);
+ return execStdin(stdin,0,args);
+ }
+ public static void linkExe( String obj, String c_conv, String cfile, Ary<String> linkObjs, String exe ) 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
// high bits are zero - and Simple code can be emitted treating
// pointers as 4bytes.
var params = new Ary<>(String.class);
params.add("gcc");
+ params.add(OS.startsWith("Windows") ? "-Wl,--image-base,0x400000" : "-no-pie");
if( cfile!=null ) params.add(cfile); // Associated C driver, usually has a `main`
+ params.add(obj);
+ // Standalone Simple programs export `simple_main`; the small C
+ // runtime owns the native main symbol and converts argc/argv into a
+ // normal Simple array before making the ABI-qualified call.
+ if( cfile==null ) params.add(runtimeObject());
+ 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",
+ exe,
});
// Calling convention for C calls, if any
if( cfile!=null ) {
@@ -109,29 +189,85 @@ public abstract class TestC {
}
// Run GCC to link (optionally compile C driver code)
- Process gcc = new ProcessBuilder(params.asAry()).redirectErrorStream(true).start();
- int exit;
- try {
- boolean normal = gcc.waitFor(2, TimeUnit.SECONDS);
- exit = normal ? gcc.exitValue() : -1; // no exit???
- } catch( InterruptedException e ) {
- throw new IOException("interrupted");
- }
- String result = new String(gcc.getInputStream().readAllBytes());
- if( exit!=0 ) {
- System.err.println("gcc error code: "+exit);
- System.err.println(result);
- }
- assertEquals( 0, exit );
- //assertTrue(result.isEmpty()); // No data in error stream
+ exec(TEST_TIMEOUT_SECONDS,params.asAry());
+ }
+
+ public static String exec( String... args ) throws IOException {
+ return execStdin(null,0,args);
+ }
+
+ // Tests can bound execution without imposing a deadline on the CLI driver.
+ public static String exec( int seconds, String... args ) throws IOException {
+ return execStdin(null,seconds,args);
+ }
+ private static String execStdin( String stdin, int seconds, String... args ) throws IOException {
+ ExecResult result = execute(stdin,seconds,args);
+ if( result.exit!=0 )
+ throw new IOException(String.join(" ",args)+"\nexec exit code: "+result.exit+
+ "\nstdout:\n"+result.out+"\nstderr:\n"+result.err);
+ return result.out;
+ }
+
+ private record ExecResult(int exit, String out, String err) {}
+
+ private static ExecResult execute( String stdin, int seconds, String... args ) throws IOException {
// Execute results
ProcessBuilder smp = new ProcessBuilder(args);
- if( stdin ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
- Process p = smp.start();
- try { exit = p.waitFor(); } catch( InterruptedException e ) { throw new IOException("interrupted"); }
- result = new String(p.getInputStream().readAllBytes());
- assertEquals("Program exit status",0,exit);
- return result;
+ Path stdinFile = null;
+ Path stdoutFile = Files.createTempFile("simple-stdout",".txt");
+ Path stderrFile = Files.createTempFile("simple-stderr",".txt");
+ Process p = null;
+ try {
+ smp.redirectOutput(stdoutFile.toFile());
+ smp.redirectError(stderrFile.toFile());
+ if( stdin!=null && stdin.isEmpty() ) smp.redirectInput(ProcessBuilder.Redirect.INHERIT);
+ else if( stdin!=null ) {
+ stdinFile = Files.createTempFile("simple-stdin",".txt");
+ Files.writeString(stdinFile,stdin);
+ smp.redirectInput(stdinFile.toFile());
+ }
+ p = smp.start();
+ if( stdin==null )
+ p.getOutputStream().close();
+ if( seconds>0 && !p.waitFor(seconds,TimeUnit.SECONDS) ) {
+ String process = "pid="+p.pid()+", cpu="+
+ p.info().totalCpuDuration().map(Object::toString).orElse("unknown");
+ p.destroyForcibly();
+ boolean stopped = p.waitFor(5,TimeUnit.SECONDS);
+ throw new IOException(String.join(" ",args)+"\ntimed out after "+seconds+" seconds ("+process+")"+
+ (stopped ? "" : "; process did not terminate")+
+ "\nstdout:\n"+Files.readString(stdoutFile)+
+ "\nstderr:\n"+Files.readString(stderrFile));
+ }
+ // Windows statuses have 32 bits; narrowing can turn a failure into 0.
+ int exit = seconds>0 ? p.exitValue() : p.waitFor();
+ return new ExecResult(exit,Files.readString(stdoutFile),Files.readString(stderrFile));
+ } catch( InterruptedException e ) {
+ if( p!=null ) p.destroyForcibly();
+ Thread.currentThread().interrupt();
+ throw new IOException("interrupted",e);
+ } finally {
+ if( stdinFile!=null ) Files.deleteIfExists(stdinFile);
+ Files.deleteIfExists(stdoutFile);
+ Files.deleteIfExists(stderrFile);
+ }
+ }
+
+ 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 f55e7d0b..10c75d3f 100644
--- a/src/test/java/com/seaofnodes/simple/TestRisc5.java
+++ b/src/test/java/com/seaofnodes/simple/TestRisc5.java
@@ -4,54 +4,52 @@ import com.seaofnodes.isa.eval.EvalRisc5;
import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen;
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 CheckedCodeGen(src).driver("riscv", "SystemV",null);
- SpillStats.recordNative(code,"riscv","SystemV");
- if( print ) { code.print_as_hex(); System.out.print(code.asm()); }
+ CodeGen code = new CheckedCodeGen(src).driver("riscv", "SystemV",true,false);
// Allocation quality not degraded
- SpillStats.checkSpills(spills,code._regAlloc._spillScaled);
+ int delta = spills>>3;
+ if( delta==0 ) delta = 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 fd3fea9d..f6099607 100644
--- a/src/test/java/com/seaofnodes/simple/TypeTest.java
+++ b/src/test/java/com/seaofnodes/simple/TypeTest.java
@@ -27,11 +27,11 @@ public class TypeTest {
@Test
public void testCyclicLeafKinds() {
TypeStruct bottom = TypeStruct.make("B14LeafKinds",false,
- Field.make("x",Type.BOTTOM,2,false, false));
+ Field.make("x",Type.BOTTOM,2,false));
TypeStruct one = TypeStruct.make("B14LeafKinds",false,
- Field.make("x",TypeFloat.constant(1.0),2,false, false));
+ Field.make("x",TypeFloat.constant(1.0),2,false));
TypeStruct two = TypeStruct.make("B14LeafKinds",false,
- Field.make("x",TypeFloat.constant(2.0),2,false, false));
+ Field.make("x",TypeFloat.constant(2.0),2,false));
// Meeting creates a new struct while cyclic equality is active. Its
// float leaf must compare unequal to the previously interned BOTTOM.
@@ -39,29 +39,25 @@ public class TypeTest {
assertSame(TypeFloat.F32,meet._fields[0]._t);
assertSame(meet,two.meet(one));
assertSame(meet,TypeStruct.make("B14LeafKinds",false,
- Field.make("x",TypeFloat.F32,2,false, false)));
+ Field.make("x",TypeFloat.F32,2,false)));
assertSame(bottom,meet.meet(bottom));
assertSame(meet.dual(),one.dual().join(two.dual()));
}
- // Test basic properties and GLB
+ // Test basic type properties
@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) );
- TypeStruct x1ro = (TypeStruct)s1.makeRO();
+ Field.make("a", TypeInteger.BOT,-3, false),
+ Field.make("b", TypeInteger.BOT,-4, false) );
TypeMemPtr p1 = TypeMemPtr.make(s1);
- 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));
TypeMem m1 = TypeMem.make(2,TypeNil.NIL);
TypeMem m2 = TypeMem.make(3,TypeInteger.U16);
@@ -73,13 +69,12 @@ 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(m4.dual(), m4.glb(false).dual());
+ Assert.assertEquals(TypeMem.make(2,Type.XNIL,true,true,true, XInt.FULL, XInt.FULL), m1.dual());
TypeMemPtr ptr1 = TypeMemPtr.make(s1);
Assert.assertEquals(s1, ptr1._obj);
@@ -94,15 +89,8 @@ public class TypeTest {
Assert.assertEquals(s2, ptr2nil._obj);
Assert.assertNotEquals(ptr1, ptr2);
- Type p1glb = ptr1.glb(false);
- Assert.assertNotEquals(ptr1, p1glb );
- Assert.assertTrue(((TypeMemPtr)p1glb)._ro);
- Assert.assertTrue(ptr1nil.isa(p1glb));
-
Assert.assertEquals(ptr1, ptr1.dual().dual());
- Assert.assertTrue(p1glb.makeRO().isa( ptr1.dual().glb(false)));
Assert.assertEquals(TypeMemPtr.makeNullable(TypeStruct.BOT).withAccess(false), ptr1.meet(ptr2nil));
- Assert.assertEquals(ptr1nil, ptr1.meet(TypeNil.NIL));
TypeMemPtr TOP = TypeMemPtr.TOP;
TypeMemPtr BOT = TypeMemPtr.makeNullable(TypeStruct.BOT);
@@ -120,7 +108,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();
@@ -128,9 +116,6 @@ public class TypeTest {
Assert.assertTrue(s1ro.isFinal());
Assert.assertFalse(S1.isConstant());
- Type s1glb = S1.field("s2")._t.glb(false);
- Type s1glb2 = S1.field("s2")._t.glb(false);
- Assert.assertSame(s1glb,s1glb2);
}
// Test theoretical properties.
@@ -163,7 +148,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);
}
}
@@ -189,8 +174,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 ) {
@@ -198,31 +183,23 @@ 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);
- }
-
- @Test
- public void testGLB() {
- Type[] ts = Type.gather();
- for( Type t0 : ts )
- if( !(t0 instanceof Field || t0 instanceof TypeStruct || t0 instanceof TypeTuple || t0 instanceof TypeConAry ) )
- Assert.assertTrue(t0.isa(t0.glb(false)));
+ assertSame(TypeStruct.SFLT1,d1);
}
@Test
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
@@ -230,6 +207,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/codegen/PrintRegTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java
index b9b49c9f..b255bcf7 100644
--- a/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java
+++ b/src/test/java/com/seaofnodes/simple/codegen/PrintRegTestSupport.java
@@ -7,9 +7,9 @@ public class PrintRegTestSupport {
var code = new CodeGen("return 0;").parse();
var alloc = new RegAlloc(code);
var n = new ConstantNode(TypeInteger.ZERO);
- var a = new LRG((short)1);
- var b = new LRG((short)2);
- var c = new LRG((short)3);
+ var a = new LRG((short)1,null);
+ var b = new LRG((short)2,null);
+ var c = new LRG((short)3,null);
a._reg = -1;
c._leader = b;
b._leader = a;
@@ -23,4 +23,54 @@ public class PrintRegTestSupport {
assertSame(b,c._leader);
assertSame(a,b._leader);
}
+ public static void checkConstantPool() throws Exception {
+ var code = new CodeGen("return 0;").parse();
+ code._phase = CodeGen.Phase.Encoding;
+ var enc = new Encoding(code);
+ var t = com.seaofnodes.simple.type.TypeStruct.make("PrintPool",false,
+ com.seaofnodes.simple.type.Field.make("x",TypeInteger.ZERO,2,true));
+ var n = new ConstantNode(TypeInteger.ZERO);
+ enc.largeConstant(n,t,0,2);
+ int size = t.size(); // Encoding lays out data before printing the pool.
+ for( int i=0; i<size; i++ ) enc._cpool.write(0);
+ // Layout queries are forbidden during printing, even if they would
+ // happen to return a cached answer without changing the type.
+ boolean[] printing = {false};
+ var scalar = new com.seaofnodes.simple.type.Type(com.seaofnodes.simple.type.Type.BOTTOM._type) {
+ @Override public int alignment() {
+ assertFalse("alignment queried by printer",printing[0]);
+ return 0;
+ }
+ @Override public int size() {
+ assertFalse("size queried by printer",printing[0]);
+ return 1;
+ }
+ @Override public String str() { return "LayoutProbe"; }
+ };
+ var scalarNode = new ConstantNode(TypeInteger.TRUE);
+ enc._bigCons.put(scalarNode,new Encoding.Relo(scalarNode,scalar,(byte)0,(byte)2));
+ enc._cpool.write(0);
+ printing[0] = true;
+ var offsets = com.seaofnodes.simple.type.TypeStruct.class.getDeclaredField("_offs");
+ offsets.setAccessible(true);
+ offsets.set(t,null); // Printer must not lazily rebuild the cached layout.
+ var field = com.seaofnodes.simple.type.Type.class.getDeclaredField("VISIT");
+ field.setAccessible(true);
+ var visit = (java.util.Map<Object,Object>)field.get(null);
+ var marker = new Object();
+ visit.put(marker,t);
+ var method = com.seaofnodes.simple.print.ASMPrinter.class.getDeclaredMethod(
+ "printConstantPool",int.class,com.seaofnodes.simple.util.SB.class,
+ com.seaofnodes.simple.util.BAOS.class,java.util.HashMap.class,boolean.class,String.class);
+ method.setAccessible(true);
+ try {
+ var sb = new com.seaofnodes.simple.util.SB();
+ method.invoke(null,0,sb,enc._cpool,enc._bigCons,true,"Constant Pool");
+ assertTrue(sb.toString().contains("PrintPool"));
+ assertTrue(sb.toString().contains("LayoutProbe"));
+ assertNull(offsets.get(t));
+ assertSame(t,visit.get(marker));
+ assertEquals(1,visit.size());
+ } finally { visit.clear(); }
+ }
}
diff --git a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
index 1ffdb3d0..93380c87 100644
--- a/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
+++ b/src/test/java/com/seaofnodes/simple/codegen/RegAllocTestSupport.java
@@ -33,8 +33,8 @@ public class RegAllocTestSupport {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(new RegMask(3L),null,null,false,code._start);
- LRG lrg = alloc.newLRG(def);
- LRG neighbor = new LRG((short)99);
+ LRG lrg = alloc.newLRG(def,null);
+ LRG neighbor = new LRG((short)99,null);
neighbor._mask = A;
lrg.addNeighbor(neighbor); // Uncolored, but its only possible color is A.
var bias = IFG.class.getDeclaredMethod("biasColorNeighbors",RegAlloc.class,Node.class,RegMask.class);
@@ -59,7 +59,7 @@ public class RegAllocTestSupport {
public static void union() {
CodeGen code = graph();
Op use = new Op(null,B,null,false,code._start);
- LRG a = new LRG((short)1), b = new LRG((short)2);
+ LRG a = new LRG((short)1,null), b = new LRG((short)2,null);
a._mask = new RegMask(3L);
b._mask = new RegMask(6L);
b.machUse(use,(short)2,true);
@@ -113,7 +113,7 @@ public class RegAllocTestSupport {
@Override public boolean commutes() { return true; }
};
RegionNode region = new RegionNode(null,code._start,code._start);
- new PhiNode("sum",TypeInteger.BOT,region,add,a);
+ new PhiNode("sum",region,add,a);
code._cfg.add(region);
assertTrue(BuildLRG.run(0,new RegAlloc(code)));
}
@@ -131,7 +131,7 @@ public class RegAllocTestSupport {
RegAlloc alloc = new RegAlloc(code);
RegionNode block = new RegionNode(null,code._start,code._start);
Op def = new Op(new RegMask(7L),null,null,false,block);
- LRG lrg = alloc.newLRG(def);
+ LRG lrg = alloc.newLRG(def,null);
lrg._machDef = def;
lrg._mask = new RegMask(0L);
Op ab = new Op(null,new RegMask(3L),null,false,block,def);
@@ -163,10 +163,14 @@ public class RegAllocTestSupport {
// Check the scheduled graph before encoding adds untyped branches or rewrites tail calls.
public static class CheckedCodeGen extends CodeGen {
public CheckedCodeGen(String src) { super(src); }
- public CheckedCodeGen(String src, TypeInteger arg) { super(src,arg); }
+ public CheckedCodeGen(String modDir, String buildDir, com.seaofnodes.simple.util.Ary<String> externPaths,
+ String srcName, String src, long seed, boolean reset, TypeInteger arg) {
+ super(modDir,buildDir,externPaths,srcName,src,seed,reset,arg);
+ }
@Override public CodeGen regAlloc() {
super.regAlloc();
checkRegisters(this);
+ com.seaofnodes.simple.SpillStats.record25(this);
return this;
}
}
@@ -180,7 +184,7 @@ public class RegAllocTestSupport {
Op def = new Op(A,null,null,false,code._start);
Node top = new ConstantNode(com.seaofnodes.simple.type.Type.TOP);
RegionNode region = new RegionNode(null,code._start,code._start);
- PhiNode phi = new PhiNode("value",TypeInteger.BOT,region,slot==1 ? top : def,slot==2 ? top : def);
+ PhiNode phi = new PhiNode("value",region,slot==1 ? top : def,slot==2 ? top : def);
phi._type = TypeInteger.BOT;
new Op(null,A,null,false,region,phi);
code._cfg.add(region);
@@ -220,14 +224,14 @@ public class RegAllocTestSupport {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(A,null,null,false,code._start);
- alloc.newLRG(def)._reg=0;
- SplitNode hi = code._mach.split("test",(byte)0,null);
+ alloc.newLRG(def,null)._reg=0;
+ SplitNode hi = code._mach.split(null,"test",(byte)0);
hi.setDef(0,code._start); hi.setDef(1,def);
- alloc.newLRG(hi)._reg=1;
+ alloc.newLRG(hi,null)._reg=1;
new Op(null,null,killed ? A : null,false,code._start);
- SplitNode lo = code._mach.split("test",(byte)0,null);
+ SplitNode lo = code._mach.split(null,"test",(byte)0);
lo.setDef(0,code._start); lo.setDef(1,hi);
- alloc.newLRG(lo)._reg=0;
+ alloc.newLRG(lo,null)._reg=0;
Op use = new Op(null,A,null,false,code._start,lo);
var post = RegAlloc.class.getDeclaredMethod("postColor");
post.setAccessible(true); post.invoke(alloc);
@@ -236,10 +240,10 @@ public class RegAllocTestSupport {
CodeGen pre = graph();
RegAlloc uncolored = new RegAlloc(pre);
Op def0 = new Op(A,null,null,false,pre._start);
- uncolored.newLRG(def0)._mask=A;
+ uncolored.newLRG(def0,null)._mask=A;
Op fixed = new Op(A,null,null,false,pre._start);
- uncolored.newLRG(fixed)._mask=A;
- SplitNode copy = pre._mach.split("test",(byte)0,null);
+ uncolored.newLRG(fixed,null)._mask=A;
+ SplitNode copy = pre._mach.split(null,"test",(byte)0);
copy.setDef(0,pre._start); copy.setDef(1,def0);
assertFalse("A fixed definition clobbers even before coloring",uncolored.sameBlockNoClobber(copy));
}
@@ -249,13 +253,14 @@ public class RegAllocTestSupport {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
Op init = new Op(A,null,null,false,code._start);
- LoopNode loop = new LoopNode(null,code._start);
- PhiNode phi = new PhiNode("loop",TypeInteger.BOT,loop,init,null);
+ LoopNode loop = new LoopNode(null,null,code._start);
+ PhiNode phi = new PhiNode("loop",loop,init,null);
+ phi._type = TypeInteger.BOT;
Op back = new Op(A,A,null,false,loop,phi);
phi.setDef(2,back);
Op use = new Op(null,A,null,false,loop,phi);
loop.setDef(2,loop);
- LRG lrg = alloc.newLRG(phi);
+ LRG lrg = alloc.newLRG(phi,null);
lrg._mask = A;
alloc.union(lrg,init); alloc.union(lrg,back);
lrg.selfConflict(back);
@@ -278,10 +283,10 @@ public class RegAllocTestSupport {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(A,null,null,false,code._start);
- alloc.newLRG(def)._reg=0;
- SplitNode copy = code._mach.split("test",(byte)0,null);
+ alloc.newLRG(def,null)._reg=0;
+ SplitNode copy = code._mach.split(null,"test",(byte)0);
copy.setDef(0,code._start); copy.setDef(1,def);
- alloc.newLRG(copy)._reg=1;
+ alloc.newLRG(copy,null)._reg=1;
if( kind==3 ) new Op(null,null,A,false,code._start);
int twoAddress = kind==2 ? 1 : 0;
RegMask mask = kind==1 ? B : kind==5 ? null : new RegMask(3L);
@@ -302,15 +307,15 @@ public class RegAllocTestSupport {
CodeGen code = graph();
RegAlloc alloc = new RegAlloc(code);
Op def = new Op(new RegMask(3L),null,null,false,code._start);
- SplitNode copy = code._mach.split("test",(byte)0,null);
+ SplitNode copy = code._mach.split(null,"test",(byte)0);
copy.setDef(0,code._start); copy.setDef(1,def);
Op use = new Op(null,new RegMask(3L),null,false,code._start,copy);
- LRG from = alloc.newLRG(def), to = alloc.newLRG(copy);
+ LRG from = alloc.newLRG(def,null), to = alloc.newLRG(copy,null);
from._mask = kind==1 ? A : new RegMask(3L);
to._mask = kind==1 ? B : new RegMask(3L);
from.machDef(def,false); from.machUse(copy,(short)1,false);
to.machDef(copy,false); to.machUse(use,(short)1,false);
- LRG left = new LRG((short)100), right = new LRG((short)101);
+ LRG left = new LRG((short)100,null), right = new LRG((short)101,null);
if( kind==2 ) { from.addNeighbor(to); to.addNeighbor(from); }
if( kind>=3 ) {
from.addNeighbor(left); left.addNeighbor(from);
diff --git a/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java b/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java
deleted file mode 100644
index 0914d916..00000000
--- a/src/test/java/com/seaofnodes/simple/codegen/X86EncodingTest.java
+++ /dev/null
@@ -1,123 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.Node;
-import com.seaofnodes.simple.node.cpus.x86_64_v2.CmpMemX86;
-import com.seaofnodes.simple.node.cpus.x86_64_v2.NotX86;
-import com.seaofnodes.simple.node.cpus.x86_64_v2.MulIX86;
-import org.junit.Test;
-import static org.junit.Assert.*;
-
-public class X86EncodingTest {
-
- @Test
- public void testLogicalNotRegisters() {
- CodeGen code = new CodeGen("return !arg;")
- .driver(CodeGen.Phase.Select, "x86_64_v2", "SystemV");
- var not = code._stop.walk(n -> n instanceof NotX86 ? (NotX86)n : null);
- assertNotNull("Must exercise logical not", not);
- int[][] registers = {{0,0}, {1,1}, {7,7}, {9,9}, {1,9}, {9,1}, {6,0}, {15,8}};
- // TEST src,src; SETZ dstb; MOVZX dst32,dstb. Include overlapping
- // registers, low-byte registers requiring REX, and extended registers.
- String[] expected = {
- "4885c00f94c00fb6c0", "4885c90f94c10fb6c9",
- "4885ff400f94c7400fb6ff", "4d85c9410f94c1450fb6c9",
- "4d85c90f94c10fb6c9", "4885c9410f94c1450fb6c9",
- "4885c0400f94c6400fb6f6", "4d85c0410f94c7450fb6ff"
- };
- for (int i = 0; i < registers.length; i++) {
- int dst = registers[i][0], src = registers[i][1];
- var enc = new FixedRegisterEncoding(code, not, dst, not.in(1), src);
- not.encoding(enc);
- assertArrayEquals("Logical not dst=" + dst + ", src=" + src,
- java.util.HexFormat.of().parseHex(expected[i]), enc._bits.toByteArray());
- }
- }
-
- @Test
- public void testLogicalNotExecution() throws java.io.IOException {
- var dir = java.nio.file.Path.of("build/objs");
- java.nio.file.Files.createDirectories(dir);
- java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.smp"),
- "val logicalNot = { int x -> return !x; };");
- java.nio.file.Files.writeString(dir.resolve("x86LogicalNot.c"), """
- #include <limits.h>
- extern long long logicalNot(long long);
- int main(void) {
- long long inputs[] = {0, 1, -1, LLONG_MIN, LLONG_MAX, 1LL << 32};
- for (unsigned i = 0; i < sizeof(inputs)/sizeof(inputs[0]); i++)
- if (logicalNot(inputs[i]) != (inputs[i] == 0)) return 1;
- return 0;
- }
- """);
- com.seaofnodes.simple.TestC.run("build/objs", "x86LogicalNot", "", -1);
- }
-
- @Test
- public void testMultiplyImmediateRegisters() {
- // Destination, source, expected REX and ModRM. Cross the register-8
- // boundary in both directions, with low/low and high/high controls.
- int[][] cases = {{1, 9, 0x49, 0xC9}, {9, 1, 0x4C, 0xC9},
- {1, 2, 0x48, 0xCA}, {9, 10, 0x4D, 0xCA}};
- for (int immediate : new int[]{11, -11, 123456789, -123456789}) {
- CodeGen code = new CodeGen("return arg * " + immediate + ";")
- .driver(CodeGen.Phase.Select, "x86_64_v2", "SystemV");
- var mul = code._stop.walk(n -> n instanceof MulIX86 ? n : null);
- assertNotNull("Must exercise immediate multiply", mul);
- for (int[] regs : cases) {
- var enc = new FixedRegisterEncoding(
- code, mul, regs[0], mul.in(1), regs[1]);
- ((MulIX86)mul).encoding(enc);
- boolean small = immediate == 11 || immediate == -11;
- assertEquals("REX for dst=" + regs[0] + ", src=" + regs[1], regs[2], enc.read1(0));
- assertEquals(small ? 0x6B : 0x69, enc.read1(1));
- assertEquals(regs[3], enc.read1(2));
- assertEquals(small ? 4 : 7, enc._bits.size());
- assertEquals(immediate, small ? (byte)enc.read1(3) : enc.read4(3));
- }
- }
- }
- @Test
- public void testMemoryCompare64Immediate32() {
- for (int immediate : new int[]{123456789, -123456789}) {
- String src = "int[] !a = new int[2]; a[0] = arg; return a[arg & 1] == " + immediate + ";";
- CodeGen code = new CodeGen(src).driver(CodeGen.Phase.Encoding, "x86_64_v2", "SystemV");
- var cmp = code._stop.walk(n -> n instanceof CmpMemX86 ? n : null);
- assertNotNull("Must exercise a memory comparison", cmp);
- var enc = code._encoding;
- int start = enc._opStart[cmp._nid];
-
- // REX.W, 81 /7, SIB with scale 8, disp8, and a sign-extended imm32.
- assertEquals(0x48, enc.read1(start) & 0xF8);
- assertEquals(0x81, enc.read1(start + 1));
- assertEquals(0x7C, enc.read1(start + 2));
- assertEquals(0xC0, enc.read1(start + 3) & 0xC0);
- assertEquals(8, enc.read1(start + 4));
- assertEquals(immediate, enc.read4(start + 5));
- assertEquals("CMP r/m64 uses four immediate bytes", 9, enc._opLen[cmp._nid]);
-
- // SETE must immediately follow the CPU's nine-byte CMP instruction.
- assertEquals(0x0F, enc.read1(start + 9));
- assertEquals(0x94, enc.read1(start + 10));
- }
- }
-
- /** Exercise instruction encoders with explicit registers, independent of allocator choices. */
- private static class FixedRegisterEncoding extends Encoding {
- private final Node _dst, _src;
- private final short _dstReg, _srcReg;
-
- FixedRegisterEncoding(CodeGen code, Node dst, int dstReg, Node src, int srcReg) {
- super(code);
- _dst = dst;
- _src = src;
- _dstReg = (short)dstReg;
- _srcReg = (short)srcReg;
- }
-
- @Override public short reg(Node n) {
- if (n == _dst) return _dstReg;
- if (n == _src) return _srcReg;
- throw new AssertionError("Unexpected register lookup");
- }
- }
-}
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 20aac367..00000000
--- a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java
+++ /dev/null
@@ -1,380 +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.size());
- 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] = n;
- } 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 -> 0D;
- default -> null;
- };
- }
- return new Object[]{new Obj(type,body),null};
- }
-
- 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 RoundF32Node rnd -> (double)(float)vald(rnd.in(1));
- case ToIntegerNode cvt -> (long)vald(cvt.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 b29eb2c3..ec9e3910 100644
--- a/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
+++ b/src/test/java/com/seaofnodes/simple/fuzzer/Fuzzer.java
@@ -85,7 +85,7 @@ public class Fuzzer {
code1 = FuzzerUtils.parse(script, 123, true);
} catch (RuntimeException e1) {
try {
- FuzzerUtils.parse(script, 456, false);
+ FuzzerUtils.parse(script, 456, true);
} catch (RuntimeException e2) {
if (FuzzerUtils.isExceptionFromSameCause(e1, e2)) {
if (!valid || e1.getClass() == Parser.ParseException.class) return;
@@ -163,26 +163,30 @@ public class Fuzzer {
check(sb.toString(), valid, seed);
}
-
/**
- * Check that no exceptions happened.
- * @return true if no exception happened
+ * Run one seed as a regression test. Unlike the exploratory fuzzer path,
+ * this deliberately does not reduce on failure; old bad seeds should fail
+ * fast with the seed visible in the JUnit assertion.
*/
- public boolean noExceptions() {
- return exceptions.isEmpty();
- }
-
- /** Run a fixed chapter-local seed without reducing or swallowing failures. */
public void fuzzPeepsRegression(long seed) {
var rand = new Random(seed);
var sb = new StringBuilder();
var valid = new ScriptGenerator(rand, sb, true).genProgram();
try {
runCheck(sb.toString(), valid);
- } catch (Throwable e) {
- AssertionError ae = new AssertionError("Fuzzer regression seed failed: " + seed);
+ } catch( Throwable e ) {
+ AssertionError ae = new AssertionError("Fuzzer regression seed failed: "+seed);
ae.initCause(e);
throw ae;
}
}
+
+
+ /**
+ * Check that no exceptions happened.
+ * @return true if no exception happened
+ */
+ public boolean noExceptions() {
+ return exceptions.isEmpty();
+ }
}
diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java b/src/test/java/com/seaofnodes/simple/fuzzer/FuzzerUtils.java
index 45558541..9fe2e405 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 and optimize using the given worklist seed
*/
- public static CodeGen parse(String script, long workListSeed, boolean reset) {
+ public static CodeGen parse(String script, long workListSeed, boolean resetTypes ) {
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, resetTypes);
return code.driver(CodeGen.Phase.LocalSched);
} finally {
System.setErr(err);
diff --git a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
index 20815d5d..c5e94047 100644
--- a/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
+++ b/src/test/java/com/seaofnodes/simple/fuzzer/ScriptGenerator.java
@@ -83,18 +83,11 @@ public class ScriptGenerator {
}
}
- private static final List<TypeInt> INTTYPES = new ArrayList<>();
- static {
- for( var e : Parser.TYPES.entrySet() ) {
- if( e.getValue() instanceof TypeInteger ) {
- INTTYPES.add(new TypeInt(e.getKey()));
- }
- }
- }
-
- private static final Type TYPE_INT = INTTYPES.stream().filter(t->t.name.equals("int")).findAny().get();
- private static final Type TYPE_BOOL = INTTYPES.stream().filter(t->t.name.equals("bool")).findAny().get();
+ private static final TypeInt TYPE_INT = new TypeInt("int");
+ private static final TypeInt TYPE_BOOL = new TypeInt("bool");
+ private static final TypeInt TYPE_U8 = new TypeInt("u8");
+ private static final List<TypeInt> INTTYPES = new ArrayList<>(){{add(TYPE_INT); add(TYPE_BOOL); add(TYPE_U8); }};
private static class Variable {
final String name;
@@ -937,4 +930,4 @@ public class ScriptGenerator {
else sb.append(v.name);
}
-} \ No newline at end of file
+}
diff --git a/src/test/java/com/seaofnodes/simple/progs/extern_data.c b/src/test/java/com/seaofnodes/simple/progs/extern_data.c
new file mode 100644
index 00000000..9325f65e
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/progs/extern_data.c
@@ -0,0 +1,22 @@
+#include <stdint.h>
+#include <stdio.h>
+
+int32_t counter=7;
+int8_t small=-3;
+double fraction=1.25;
+
+int64_t bump(void) { counter+=5; return counter; }
+__attribute__((CALL_CONV)) extern int64_t test_global(void);
+
+int main(void) {
+ int64_t result=test_global();
+ if(result!=71515 || counter!=15 || small!=-2 || fraction!=1.75) {
+ printf("result=%lld counter=%d small=%d fraction=%g\n",
+ (long long)result,counter,small,fraction);
+ return 1;
+ }
+ // A second call must observe C's new values, not a Simple initialization copy.
+ counter=-2;
+ result=test_global();
+ return result!=-19394 || counter!=6 || small!=-1 || fraction!=2.25;
+}
diff --git a/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c b/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c
new file mode 100644
index 00000000..f65e5c84
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/progs/extern_data_import.c
@@ -0,0 +1,14 @@
+#include <stdint.h>
+#include <stdio.h>
+
+int32_t counter=7;
+__attribute__((CALL_CONV)) extern int64_t test_global(void);
+
+int main(void) {
+ int64_t result=test_global();
+ if(result!=710 || counter!=10) {
+ printf("result=%lld counter=%d\n",(long long)result,counter);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c b/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c
new file mode 100644
index 00000000..cf9390d8
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c
@@ -0,0 +1,25 @@
+#include <stdint.h>
+#include <math.h>
+#include <stdio.h>
+
+extern int64_t float_to_int(double);
+extern int64_t float_to_u8(double);
+
+int main(void) {
+ double values[] = {0.0,-0.0,6.92,-6.92,0.99,-0.99,255.75,-257.75,
+ 0x1p63,0x1.fffffffffffffp62,-0x1p63,-0x1.0000000000001p63,
+ INFINITY,-INFINITY,NAN,-NAN};
+ for( unsigned i=0; i<sizeof(values)/sizeof(values[0]); i++ ) {
+ double x=values[i];
+ int64_t expected=isnan(x) ? 0 : x>=0x1p63 ? INT64_MAX
+ : x<=-0x1p63 ? INT64_MIN : (int64_t)x;
+ int64_t actual=float_to_int(x), narrow=float_to_u8(x);
+ if( actual!=expected || narrow!=(expected&255) ) {
+ fprintf(stderr,"conversion of %g: got %lld / %lld, expected %lld / %lld\n",
+ x,(long long)actual,(long long)narrow,
+ (long long)expected,(long long)(expected&255));
+ return 1;
+ }
+ }
+ return 0;
+}
diff --git a/src/test/java/com/seaofnodes/simple/progs/funptr.c b/src/test/java/com/seaofnodes/simple/progs/funptr.c
deleted file mode 100644
index 2abe6e74..00000000
--- a/src/test/java/com/seaofnodes/simple/progs/funptr.c
+++ /dev/null
@@ -1,15 +0,0 @@
-#include <assert.h>
-#include <stdint.h>
-
-typedef int64_t (*Function)(void);
-typedef int64_t (*Printer)(void *, void *);
-extern Function factory(int64_t);
-extern Printer printer(void);
-
-int main(void) {
- assert(factory(1)() == 42);
- assert(factory(0)() == 43);
- struct { uint32_t length; char data[2]; } text = {2, {'o','k'}};
- printer()(0, &text); // Unused class receiver, then the byte array.
- return 0;
-}
diff --git a/src/test/java/com/seaofnodes/simple/progs/jig.smp b/src/test/java/com/seaofnodes/simple/progs/jig.smp
deleted file mode 100644
index 29987d8e..00000000
--- a/src/test/java/com/seaofnodes/simple/progs/jig.smp
+++ /dev/null
@@ -1,16 +0,0 @@
-// -*- mode: java; -*-
-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;
-};
-
-_fib(10);
diff --git a/src/test/java/com/seaofnodes/simple/progs/person21.c b/src/test/java/com/seaofnodes/simple/progs/person21.c
deleted file mode 100644
index df95e1ca..00000000
--- a/src/test/java/com/seaofnodes/simple/progs/person21.c
+++ /dev/null
@@ -1,34 +0,0 @@
-#include <stdio.h>
-#include <stdbool.h>
-#include <stdlib.h>
-
-typedef struct Person {
- int age;
-} Person;
-
-typedef struct PersonArray {
- unsigned int length;
- int pad;
- Person* data[];
-} PersonArray;
-
-__attribute__ ((CALL_CONV))
-extern void fcn(PersonArray* people, int index);
-
-int main() {
- Person base[5] = {{25},{1},{18},{45},{60}};
- unsigned char buf[8+8*5];
- PersonArray* people_array = (PersonArray*)buf;
- people_array->length=5;
- for (int i=0; i<people_array->length; i++) people_array->data[i] = &base[i];
-
- fcn(people_array, 1);
- fcn(people_array, 1);
- fcn(people_array, 1);
- fcn(people_array, 1);
- fcn(people_array, 1);
-
- printf("%d\n", people_array->data[1]->age); // expected: 6
-
- return 0;
-} \ No newline at end of file
diff --git a/src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp b/src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp
new file mode 100644
index 00000000..71af2b7a
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/BrainFuckTest-testBrainFuck.smp
@@ -0,0 +1,46 @@
+// -*- mode: java; -*-
+val brain_fuck = { ->
+ var !program = "++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.";
+ int d = 0;
+ u8[] !output = new u8[0];
+ u8[] !data = new u8[100];
+
+ for( int pc = 0; pc < program#; pc++ ) {
+ var command = program[pc];
+ if (command == 62) {
+ d++;
+ } else if (command == 60) {
+ d--;
+ } else if (command == 43) {
+ data[d]++;
+ } else if (command == 45) {
+ data[d]--;
+ } else if (command == 46) {
+ // 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) {
+ data[d] = 42;
+ } else if (command == 91) {
+ if (data[d] == 0) {
+ for( int d = 1; d > 0; ) {
+ command = program[++pc];
+ if (command == 91) d++;
+ if (command == 93) d--;
+ }
+ }
+ } else if (command == 93) {
+ if (data[d]) {
+ for( int d = 1; d > 0; ) {
+ command = program[--pc];
+ if (command == 93) d++;
+ if (command == 91) d--;
+ }
+ }
+ }
+ }
+ return output;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp
new file mode 100644
index 00000000..d85e39e5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc0.smp
@@ -0,0 +1 @@
+return new u8[arg];
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp
new file mode 100644
index 00000000..864bcdc8
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testAlloc2.smp
@@ -0,0 +1 @@
+int[] !xs = new int[3]; xs[arg]=1; return xs[arg&1];
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp
new file mode 100644
index 00000000..7f3bd007
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testArray1.smp
@@ -0,0 +1,8 @@
+int[] !ary = new int[arg];
+// Fill [0,1,2,3,4,...]
+for( int i=0; i<ary#; i++ )
+ ary[i] = i;
+// Fill [0,1,3,6,10,...]
+for( int i=0; i<ary#-1; i++ )
+ ary[i+1] += ary[i];
+return ary[1] * 1000 + ary[3]; // 1 * 1000 + 6
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp
new file mode 100644
index 00000000..671e5b74
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBasic1.smp
@@ -0,0 +1 @@
+return arg | 2;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp
new file mode 100644
index 00000000..4e5ab81d
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testBrainfuck.smp
@@ -0,0 +1,43 @@
+u8[] !program = new u8[106];program[0] = 43;program[1] = 43;program[2] = 43;program[3] = 43;program[4] = 43;program[5] = 43;program[6] = 43;program[7] = 43;program[8] = 91;program[9] = 62;program[10] = 43;program[11] = 43;program[12] = 43;program[13] = 43;program[14] = 91;program[15] = 62;program[16] = 43;program[17] = 43;program[18] = 62;program[19] = 43;program[20] = 43;program[21] = 43;program[22] = 62;program[23] = 43;program[24] = 43;program[25] = 43;program[26] = 62;program[27] = 43;program[28] = 60;program[29] = 60;program[30] = 60;program[31] = 60;program[32] = 45;program[33] = 93;program[34] = 62;program[35] = 43;program[36] = 62;program[37] = 43;program[38] = 62;program[39] = 45;program[40] = 62;program[41] = 62;program[42] = 43;program[43] = 91;program[44] = 60;program[45] = 93;program[46] = 60;program[47] = 45;program[48] = 93;program[49] = 62;program[50] = 62;program[51] = 46;program[52] = 62;program[53] = 45;program[54] = 45;program[55] = 45;program[56] = 46;program[57] = 43;program[58] = 43;program[59] = 43;program[60] = 43;program[61] = 43;program[62] = 43;program[63] = 43;program[64] = 46;program[65] = 46;program[66] = 43;program[67] = 43;program[68] = 43;program[69] = 46;program[70] = 62;program[71] = 62;program[72] = 46;program[73] = 60;program[74] = 45;program[75] = 46;program[76] = 60;program[77] = 46;program[78] = 43;program[79] = 43;program[80] = 43;program[81] = 46;program[82] = 45;program[83] = 45;program[84] = 45;program[85] = 45;program[86] = 45;program[87] = 45;program[88] = 46;program[89] = 45;program[90] = 45;program[91] = 45;program[92] = 45;program[93] = 45;program[94] = 45;program[95] = 45;program[96] = 45;program[97] = 46;program[98] = 62;program[99] = 62;program[100] = 43;program[101] = 46;program[102] = 62;program[103] = 43;program[104] = 43;program[105] = 46;
+int d = 0;
+u8[] !output = new u8[0];
+u8[] !data = new u8[100];
+
+for( int pc = 0; pc < program#; pc++ ) {
+ var command = program[pc];
+ if (command == 62) {
+ d++;
+ } else if (command == 60) {
+ d--;
+ } else if (command == 43) {
+ data[d]++;
+ } else if (command == 45) {
+ data[d]--;
+ } else if (command == 46) {
+ // 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) {
+ data[d] = 42;
+ } else if (command == 91) {
+ if (data[d] == 0) {
+ for( int d = 1; d > 0; ) {
+ command = program[++pc];
+ if (command == 91) d++;
+ if (command == 93) d--;
+ }
+ }
+ } else if (command == 93) {
+ if (data[d]) {
+ for( int d = 1; d > 0; ) {
+ command = program[--pc];
+ if (command == 93) d++;
+ if (command == 91) d--;
+ }
+ }
+ }
+}
+return output;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp
new file mode 100644
index 00000000..d1368767
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testCast.smp
@@ -0,0 +1 @@
+struct _Bar { int x; }; var b = arg ? new _Bar; return b ? b.x++ + b.x++ : -1;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp
new file mode 100644
index 00000000..f630e959
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags1.smp
@@ -0,0 +1,5 @@
+bool b1 = arg == 1;
+bool b2 = arg == 2;
+if (b2) if (b1) return 1;
+if (b1) return 2;
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp
new file mode 100644
index 00000000..70bfc226
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFlags2.smp
@@ -0,0 +1,6 @@
+bool b1 = arg == 1;
+while (arg > 0) {
+ arg--;
+ if (b1) arg--;
+}
+return arg;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp
new file mode 100644
index 00000000..caa11f12
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testFltArg.smp
@@ -0,0 +1 @@
+return {int i, flt f, int j->return i+f+j;};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp
new file mode 100644
index 00000000..417eba72
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testMergeSort.smp
@@ -0,0 +1,46 @@
+// based on the top-down version from https://en.wikipedia.org/wiki/Merge_sort
+
+val _merge_sort = { int[] a, int[] b, int n ->
+ _copy_array(a, 0, n, b);
+ _split_merge(a, 0, n, 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[] !a = new int[arg];
+int[] !b = new int[a#];
+
+for (int i = 0; i < a#; i++)
+ a[i] = a# - i;
+
+_merge_sort(a, b, a#);
+
+return a;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp
new file mode 100644
index 00000000..fd76478f
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonFloat.smp
@@ -0,0 +1,11 @@
+// Newtons approximation to the square root
+val _sqrt = { flt x ->
+ flt guess = x;
+ while( 1 ) {
+ flt next = (x/guess + guess)/2;
+ if( next == guess ) return guess;
+ guess = next;
+ }
+};
+flt farg = arg;
+return _sqrt(farg) + _sqrt(farg+2.0);
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp
new file mode 100644
index 00000000..8912dcd9
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testNewtonInteger.smp
@@ -0,0 +1,10 @@
+// Newtons approximation to the square root
+val _sqrt = { int x ->
+ int guess = x;
+ while( 1 ) {
+ int next = (x/guess + guess)/2;
+ if( next == guess ) return guess;
+ guess = next;
+ }
+};
+return _sqrt(arg) + _sqrt(arg+2);
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp
new file mode 100644
index 00000000..951cdc32
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter20Test-testString.smp
@@ -0,0 +1,35 @@
+struct _String {
+ u8[] cs;
+ new _String={ u8[] b -> cs=b; };
+ int _hashCode;
+};
+
+val _equals = { !_String !self, !_String s ->
+ if( self == s ) return true;
+ if( self.cs# != s.cs# ) return false;
+ for( int i=0; i< self.cs#; i++ )
+ if( self.cs[i] != s.cs[i] )
+ return false;
+ return true;
+};
+
+val _hashCode = { !_String self ->
+ self._hashCode
+ ? self._hashCode
+ : (self._hashCode = _hashCodeString(self));
+};
+
+val _hashCodeString = { !_String self ->
+ int hash=0;
+ if( self.cs ) {
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ }
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+
+!_String !s = new _String(new u8[17]);
+s.cs[0] = 67; // C
+s.cs[1] = 108; // l
+_hashCode(s);
diff --git a/src/test/java/com/seaofnodes/simple/progs/array1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testArray1.smp
index 6b10f92c..6b10f92c 100644
--- a/src/test/java/com/seaofnodes/simple/progs/array1.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testArray1.smp
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp
new file mode 100644
index 00000000..58449f9c
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testNewtonFloat.smp
@@ -0,0 +1,13 @@
+// -*- mode: java; -*-
+// Newtons approximation to the square root
+val _test_sqrt = { 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;
+ guess = next;
+ }
+};
+flt farg = arg; return _test_sqrt(farg) + _test_sqrt(farg+2.0);
diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash21.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp
index 257f79f9..a18a5b88 100644
--- a/src/test/java/com/seaofnodes/simple/progs/stringHash21.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21AllocTest-testString.smp
@@ -1,11 +1,12 @@
// -*- mode: java; -*-
-struct String {
+struct _String {
u8[] cs;
int _hashCode;
+ new _String = { u8[] data -> cs=data; };
};
// Compare two Strings
-val equals = { !String !self, !String s ->
+val equals = { !_String !self, !_String s ->
if( self == s ) return true;
if( self.cs# != s.cs# ) return false;
for( int i=0; i< self.cs#; i++ )
@@ -15,13 +16,13 @@ val equals = { !String !self, !String s ->
};
// Return the String hashCode (cached, and never 0)
-val hashCode = { !String self ->
+val hashCode = { !_String self ->
self._hashCode
? self._hashCode
: (self._hashCode = _hashCodeString(self));
};
-val _hashCodeString = { !String self ->
+val _hashCodeString = { !_String self ->
int hash=0;
if( self.cs )
for( int i=0; i< self.cs#; i++ )
@@ -29,3 +30,6 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// This fixture exports its API, not the last declaration as a result.
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp
new file mode 100644
index 00000000..315cc5b6
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testAntiDeps1.smp
@@ -0,0 +1,10 @@
+struct _S { int f; };
+var v0 = new _S;
+!_S? !v1;
+if (arg) v1 = new _S;
+if (v1) {
+ v0.f = v1.f;
+} else {
+ v0.f = 2;
+}
+return v0;
diff --git a/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount-2.smp
index 20a05445..20a05445 100644
--- a/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount-2.smp
diff --git a/src/test/java/com/seaofnodes/simple/progs/arg_count.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount.smp
index 6b6bc0ae..6b6bc0ae 100644
--- a/src/test/java/com/seaofnodes/simple/progs/arg_count.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testArgCount.smp
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp
new file mode 100644
index 00000000..149872a7
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testFibExport.smp
@@ -0,0 +1,10 @@
+val fib = {int n ->
+ int f1=1;
+ int f2=1;
+ while( n-- > 1 ){
+ int temp = f1+f2;
+ f1=f2;
+ f2=temp;
+ }
+ return f2;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp
new file mode 100644
index 00000000..1804ad59
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testInfinite.smp
@@ -0,0 +1 @@
+struct _S { int i; }; !_S !s = new _S; while(1) s.i++;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp
new file mode 100644
index 00000000..cc54f790
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testLoop2.smp
@@ -0,0 +1,9 @@
+int i = 0;
+while(true) {
+ i += 1;
+ if( i==0 ) continue;
+ if( i==1 ) continue;
+ if( i==2 ) break;
+ continue;
+}
+return i;
diff --git a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testNewtonExport.smp
index be2dffec..2786558e 100644
--- a/src/test/java/com/seaofnodes/simple/progs/newtonFloat.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testNewtonExport.smp
@@ -1,5 +1,3 @@
-// -*- mode: java; -*-
-// Newtons approximation to the square root
val test_sqrt = { flt x ->
flt epsilon = 1e-15;
flt guess = x;
diff --git a/src/test/java/com/seaofnodes/simple/progs/person21.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp
index b5e6ee90..09285034 100644
--- a/src/test/java/com/seaofnodes/simple/progs/person21.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testPerson.smp
@@ -6,4 +6,4 @@ struct Person {
val fcn = { !Person?[] ps, int x ->
if( ps[x] )
ps[x].age++;
-}; \ No newline at end of file
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp
new file mode 100644
index 00000000..beb0b3ad
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testSieve.smp
@@ -0,0 +1,30 @@
+val sieve = { int N ->
+ // The main Sieve array
+ bool[] !ary = new bool[N];
+ // The primes less than N
+ u32[] !primes = new u32[N>>1];
+ // Number of primes so far, searching at index p
+ int nprimes = 0, p=2;
+ // Find primes while p^2 < N
+ while( p*p < N ) {
+ // skip marked non-primes
+ while( ary[p] ) p++;
+ // p is now a prime
+ primes[nprimes++] = p;
+ // Mark out the rest non-primes
+ for( int i = p + p; i < ary#; i+= p )
+ ary[i] = true;
+ p++;
+ }
+
+ // Now just collect the remaining primes, no more marking
+ for( ; p < N; p++ )
+ if( !ary[p] )
+ primes[nprimes++] = p;
+
+ // Copy/shrink the result array
+ u32[] !rez = new u32[nprimes];
+ for( int j=0; j < nprimes; j++ )
+ rez[j] = primes[j];
+ return rez;
+};
diff --git a/src/test/java/com/seaofnodes/simple/progs/stringHash.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp
index 7344fcdd..ee5a0560 100644
--- a/src/test/java/com/seaofnodes/simple/progs/stringHash.smp
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter21Test-testStringExport.smp
@@ -1,7 +1,7 @@
-// -*- mode: java; -*-
struct String {
u8[] cs;
int _hashCode;
+ new String = { u8[] data -> cs=data; };
};
// Compare two Strings
@@ -28,3 +28,6 @@ val _hashCodeString = { !String self ->
if( !hash ) hash = 123456789;
return hash;
};
+
+// This fixture exports its API, not the last declaration as a result.
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp
new file mode 100644
index 00000000..1bcac36d
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testHelloWorld.smp
@@ -0,0 +1,4 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+_p("Hello, World!");
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp
new file mode 100644
index 00000000..cc549808
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testInfiniteReturn.smp
@@ -0,0 +1 @@
+struct _S { int i; }; !_S !s = new _S; while(1) s.i++; return s.i;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp
new file mode 100644
index 00000000..21293a7a
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testNarrowCReturns.smp
@@ -0,0 +1,12 @@
+{i8} c_i8="C"; {u8} c_u8="C";
+{i16} c_i16="C"; {u16} c_u16="C";
+{i32} c_i32="C"; {u32} c_u32="C";
+{int} c_i64="C";
+val test_i8={->return c_i8();};
+val test_u8={->return c_u8();};
+val test_i16={->return c_i16();};
+val test_u16={->return c_u16();};
+val test_i32={->return c_i32();};
+val test_u32={->return c_u32();};
+val test_i64={->return c_i64();};
+val test_cmp={->return c_i32()==-1;};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp
new file mode 100644
index 00000000..f860091d
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testPerson.smp
@@ -0,0 +1,8 @@
+struct Person {
+ i32 age;
+};
+
+val fcn = { !Person?[] ps, int x ->
+ if( ps[x] )
+ ps[x].age++;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp
new file mode 100644
index 00000000..ec9c6d50
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail.smp
@@ -0,0 +1,4 @@
+struct _Person { i32 age;};
+!_Person !p = new _Person;
+p.age = (arg<<17)>>17;
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp
new file mode 100644
index 00000000..d5264aa2
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextFail2.smp
@@ -0,0 +1,5 @@
+
+ struct _Person { i32 age;};
+ !_Person !p = new _Person;
+ p.age = (arg<<48)>>48;
+ return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp
new file mode 100644
index 00000000..c4398a89
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter22Test-testSextSuccess.smp
@@ -0,0 +1,5 @@
+// Should fold away sign extend
+struct _Person { i8 age;};
+!_Person !p = new _Person;
+p.age = (arg<<48)>>48;
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp
new file mode 100644
index 00000000..49c80a99
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23AllocTest-testString.smp
@@ -0,0 +1,34 @@
+// -*- mode: java; -*-
+struct _String {
+ u8[] cs;
+ int _hashCode;
+ new _String = { u8[] data -> cs=data; };
+};
+
+// Compare two Strings
+val equals = { !_String !self, !_String s ->
+ if( self == s ) return true;
+ if( self.cs# != s.cs# ) return false;
+ for( int i=0; i< self.cs#; i++ )
+ if( self.cs[i] != s.cs[i] )
+ return false;
+ 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;
+ for( int i=0; i< self.cs#; i++ )
+ hash = hash*31 + self.cs[i];
+ if( !hash ) hash = 123456789;
+ return hash;
+};
+
+// This fixture exports its API, not the last declaration as a result.
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp
new file mode 100644
index 00000000..da85ef0e
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAnd.smp
@@ -0,0 +1,11 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+int a = 1;
+int b = 1;
+
+if(a && b) {
+ _p("And");
+} else {
+ _p("Or");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp
new file mode 100644
index 00000000..a1e4e9ab
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testAndPtr.smp
@@ -0,0 +1,12 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+struct _S { !_S? !fld; new _S={ !_S? f -> fld=f; }; };
+
+val ptr = arg == 1 ? null : new _S(arg==1 ? null : new _S(null));
+if( ptr && ptr.fld ) {
+ _p("true");
+} else {
+ _p("false");
+}
+
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp
new file mode 100644
index 00000000..1a6b5cf3
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffAnd.smp
@@ -0,0 +1,17 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+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) {
+ _p("Effected");
+} else {
+ _p("Not effected");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp
new file mode 100644
index 00000000..f2bb16c5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testCondSideEffOr.smp
@@ -0,0 +1,24 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+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) {
+ _p("Effected");
+} else {
+ _p("Not effected");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp
new file mode 100644
index 00000000..eac0a542
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFRefFields.smp
@@ -0,0 +1,20 @@
+// A hypothetical scanner class
+struct _Scan {
+ int !x;
+ u8[~] buf;
+ new _Scan={ u8[~] b -> buf=b; };
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ };
+ // Peek a character; if matched consume it, else false.
+ val peek = { u8 c ->
+ skip();
+ if( buf[x] != c ) return false;
+ x++;
+ return true;
+ };
+};
+!_Scan !s = new _Scan(" q");
+return s.peek('q');
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp
new file mode 100644
index 00000000..8a7b90d9
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testFuncCall.smp
@@ -0,0 +1,16 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+// -*- mode: java; -*-
+int a = 1;
+int b = 1;
+
+var _sq = { int x ->
+ x*x;
+};
+
+if(a && _sq(0)) {
+ _p("And");
+} else {
+ _p("Or");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp
new file mode 100644
index 00000000..ced649e3
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testMethod.smp
@@ -0,0 +1,17 @@
+// A hypothetical scanner class
+struct _Scan {
+ int !x;
+ u8[~] buf;
+ new _Scan={ u8[~] b -> buf=b; };
+ // Skip whitespace
+ val skip = { ->
+ while( buf[x] <= ' ' )
+ x++;
+ };
+ val require = { u8 ch ->
+ skip();
+ buf[x++]==ch;
+ };
+};
+!_Scan !s = new _Scan(" [1,2]");
+return s.require('[');
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp
new file mode 100644
index 00000000..dbab1ae5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOr.smp
@@ -0,0 +1,13 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+int a = 1;
+int b = 0;
+
+if(a++ || b++ ) {
+ if(b == 0 && a == 2) {
+ _p("Or");
+ }
+} else{
+ _p("And");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp
new file mode 100644
index 00000000..c4e4f9b5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopLocal.smp
@@ -0,0 +1,10 @@
+struct _S { int f; };
+int x=0;
+int result=arg&1;
+if(!result) {
+ !_S !s=new _S;
+ while(x<3) x++;
+ s.f=++x;
+ result=s.f;
+}
+return x*10+result;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp
new file mode 100644
index 00000000..79478408
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter23Test-testOrRhsLoopMemory.smp
@@ -0,0 +1,11 @@
+struct _S { int f; };
+!_S !s=new _S;
+int result=arg&1;
+if(!result) {
+ !_S !t=new _S;
+ while(s.f<3) s.f++;
+ s.f++;
+ t.f=s.f;
+ result=t.f;
+}
+return s.f*10+result;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp
new file mode 100644
index 00000000..f292254e
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testBubbleSort.smp
@@ -0,0 +1,217 @@
+
+ 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;
+
+ 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;
+ };
+ 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];
+ _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#);
+ _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; write(fd,ptr,len); };
+ };
+struct _Scan {
+ int !x;
+ u8[~] buf;
+ new _Scan={ u8[~] b -> buf=b; };
+
+ // 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;
+ };
+ };
+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 _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
+ // 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;
+ };
+
+
+ // Print string to stdout
+ val _p = { u8[~] str ->
+ i64 ptr = str; // cast array base to i64
+ return 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 = read(0,ptr,buf#);
+ if( rez < 0 ) _exit(errno);
+ len += rez;
+ if( rez < buf# )
+ break;
+ _exit(-2); // Need to implement
+ }
+ // Compress
+ var dst = new u8[len];
+ return _copy(dst,0,buf,0,len);
+ };
+ // -*- mode: java; -*-
+
+// An implementation of
+// https://sampleprograms.io/projects/bubble-sort/
+//
+// Bubble sort, with reading (parsing) and writing (formatting) the data.
+
+val _sort = { !_aryi64 nums ->
+ var done = false;
+ while( !done ) {
+ done = true;
+ for( var i = 0; i < nums.len-1; i++ )
+ if( nums.buf[i] > nums.buf[i+1] ) {
+ int tmp = nums.buf[i];
+ nums.buf[i] = nums.buf[i+1];
+ nums.buf[i+1] = tmp; // Swap
+ done = false;
+ }
+ }
+};
+
+// Read from stdin a line of the form "[ 4, 3, 2, 1]" and print it sorted.
+val main = { ->
+ // Parse array of numbers from stdin
+ var nums = _parseAryI64( _stdin() );
+ if( !nums ) {
+ _p("stdin must look like [4, 3, 2, 1]");
+ return 1;
+ }
+
+ // Bubble sort array of integers
+ _sort(nums);
+
+ // Print a list of numbers to stdout
+ nums.str().write(1);
+ return 0;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp
new file mode 100644
index 00000000..572e4ad7
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testNewtonInteger.smp
@@ -0,0 +1,11 @@
+// Newtons approximation to the square root
+val _sqrt = { int x ->
+ int guess = x;
+ while( 1 ) {
+ int next = (x/guess + guess)/2;
+ if( next == guess ) return guess;
+ guess = next;
+ }
+};
+int cast_int = arg+2;
+return _sqrt(arg) + _sqrt(cast_int);
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp
new file mode 100644
index 00000000..b7d52dc5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24AllocTest-testStackArguments.smp
@@ -0,0 +1,5 @@
+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 ->
+ return
+ i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 +
+ i10+f11+i12+f13+i14+f15+x16+f17+x18+f19 ;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp
new file mode 100644
index 00000000..ded92426
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack1.smp
@@ -0,0 +1,10 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+int something_specific = 11;
+
+if (10 < something_specific < 20) {
+ _p("In Range");
+} else {
+ _p("Out of Range");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp
new file mode 100644
index 00000000..d9fb38d5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10-2.smp
@@ -0,0 +1 @@
+return (0 < 1) == (1>0);
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp
new file mode 100644
index 00000000..f92e5b00
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack10.smp
@@ -0,0 +1,3 @@
+var stack10 = { int x ->
+ (x < 1) == (1>x);
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp
new file mode 100644
index 00000000..b5aac70b
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11-2.smp
@@ -0,0 +1 @@
+return 0 < arg < arg+1 < 4;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp
new file mode 100644
index 00000000..f50e3512
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack11.smp
@@ -0,0 +1,3 @@
+var stack11 = { int x ->
+ 0 < x < x+1 < 4;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp
new file mode 100644
index 00000000..575d1b4e
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12-2.smp
@@ -0,0 +1 @@
+return 0 != arg != 2;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp
new file mode 100644
index 00000000..b4f36c2e
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack12.smp
@@ -0,0 +1,3 @@
+var stack12 = { int x ->
+ 0 != x != 2;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp
new file mode 100644
index 00000000..9bde9687
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13-2.smp
@@ -0,0 +1 @@
+return 0 == arg == 1;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp
new file mode 100644
index 00000000..ae7b38a3
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack13.smp
@@ -0,0 +1,3 @@
+var stack13 = { int x ->
+ 0 == x == 1;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp
new file mode 100644
index 00000000..5590c42f
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack2.smp
@@ -0,0 +1,10 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+int score = 75;
+
+if (60 <= score < 90) {
+ _p("In Range");
+} else {
+_p("Out of range");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp
new file mode 100644
index 00000000..4700f8ce
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack3.smp
@@ -0,0 +1,11 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+var _sq = { int x ->
+ x*x;
+};
+
+int score = 5;
+
+val str = (_sq(2) <= score < _sq(3)) ? "In Range" : "Out of range";
+_p(str);
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp
new file mode 100644
index 00000000..47623b30
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack4.smp
@@ -0,0 +1,10 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+int something_specific = 9;
+
+if (10 < something_specific < 20) {
+ _p("In Range");
+} else {
+ _p("Out of Range");
+}
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp
new file mode 100644
index 00000000..3c2c4208
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack5.smp
@@ -0,0 +1,15 @@
+{i32 i64 u32 -> u32} write="C";
+val _p={u8[~] str -> i64 ptr=str; return write(1,ptr,str#);};
+var _sq = { int x ->
+ x*x;
+};
+
+int score = 5;
+
+if (_sq(2) <= score < _sq(3)) {
+_p("In Range");
+} else {
+_p("Out of range");
+}
+
+return 0;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp
new file mode 100644
index 00000000..f54558f5
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9-2.smp
@@ -0,0 +1 @@
+return (0 < 1) == 1;
diff --git a/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp
new file mode 100644
index 00000000..15979be6
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/Chapter24Test-testStack9.smp
@@ -0,0 +1,3 @@
+var stack9 = { int x ->
+ (x < 1) == 1;
+};
diff --git a/src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp b/src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp
new file mode 100644
index 00000000..e6478eeb
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/MergeSortTest-testMergeSort0.smp
@@ -0,0 +1,35 @@
+ 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];
+ };
diff --git a/src/test/java/com/seaofnodes/simple/spill/README.ja.md b/src/test/java/com/seaofnodes/simple/spill/README.ja.md
new file mode 100644
index 00000000..5ae019dd
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/README.ja.md
@@ -0,0 +1,50 @@
+# 過去の割り当てコホート
+
+[English](README.md) | 日本語
+
+この文書は [Historical allocation cohorts](README.md) の日本語訳です。
+
+`cohorts.tsv` は、第24章の `SpillStats` で測定した212件のコンパイル項目を固定したものです。
+コホート20〜24の項目数は、それぞれ39、52、24、30、67です。
+ソースファイルは内容で重複を除き、最初に参照したテストの名前を付けています。
+マニフェストには、テストの識別情報、ターゲット、ABI、元の停止フェーズを保持しています。
+`Win64` は、第25章で使う ABI の表記 `win64` に変更しています。
+引数の型は i64、再実行のシードは全項目で123です。
+
+これらの入力は、この最終監査で新たな正しさの回帰テストを追加する前に、
+第24章のテストから取得しました。比較のための安定したコーパスです。
+変更する場合は、比較可能性が失われる理由を説明する必要があります。
+人工的なアロケータの回帰テストや命令の回帰テストでの割り当ては、これらのコホートには含めません。
+
+第25章では、以前のソースをすべて無変更で解析できるわけではありません。適応のための変更を明記します。
+
+- String、Scan、連結された S のコンストラクタでは、`new S { field=...; }` を、
+ 宣言されたコンストラクタとその引数に置き換えています。
+ レシーバーを明示するスキャナ関数は、操作を保ったままインスタンスメソッドにしています。
+- 保存した `Chapter21Test-testStringExport`、`Chapter21AllocTest-testString`、
+ `Chapter23AllocTest-testString` のソースは、必須の非 null フィールド `cs` を初期化するために、
+ `new String = { u8[] data -> cs=data; };` を宣言しています。
+ これによって10件の再実行項目(4件、3件、3件)を修正していますが、
+ 等価性・ハッシュの本体やマニフェストの構成項目は変えていません。
+ これらの適応済みソースが新しい比較基準です。以前のコンストラクタなしのフィクスチャと
+ 同一入力として、その集計値を比較することはできません。
+- 短絡評価の右辺で初期化する2つのループは、同等のガード付きブロックにしています。
+ 割り当て、副作用、最終結果を保持しています。
+- 単純な I/O のケースには、以前の `write` の束縛と表示用の補助関数を直接含めています。
+ Bubble Sort には、名前空間の接頭辞を平坦化し、コンストラクタを明示し、
+ 置換用バッファを可変にした、以前のライブラリの本体を含めています。
+ これにより、第25章で大幅に改訂した例に差し替えるのではなく、以前のワークロードを保持しています。
+- 元の Newton の浮動小数点数版2種類を含め、ほかのソース本文は保持しています。
+
+すべての項目を **RegAlloc まで**再実行し、スケジューリング済みのレジスタ制約を確認します。
+過去のフェーズの列は来歴情報であり、第25章で以前のネイティブ実行ハーネスを動かすことを約束するものではありません。
+適応済み Bubble Sort は、割り当ての比較可能性のために、過去の `i32 errno="C"` 宣言を保持しています。
+直接のデータ束縛は現在、3つのターゲットすべてでエンコードできますが、
+errno がスレッドローカルまたはアクセサ経由の libc とは、この宣言をリンクできません。
+現在のシステムライブラリのコードは、代わりに `libc.errno()` を使います。
+このコーパスを、過去のランタイムの実行カバレッジとして数えてはいけません。
+
+その後 `SpillStats` は、シード456で現在のシステムライブラリをエンコーディングまでコンパイルし、
+既存のシードとネイティブ検査を使って Chapter25Test を実行します。
+これらを合わせてコホート25とします。ライブラリまたはテストが失敗すると、レポートも失敗します。
+部分的な集計値では改善を示せません。現在のコンパイラの実行検証は、通常の章全体のテストスイートが担います。
diff --git a/src/test/java/com/seaofnodes/simple/spill/README.md b/src/test/java/com/seaofnodes/simple/spill/README.md
new file mode 100644
index 00000000..b5ab9d80
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/README.md
@@ -0,0 +1,78 @@
+# Historical allocation cohorts
+
+English | [日本語](README.ja.md)
+
+`cohorts.tsv` freezes 199 compilation entries matching Chapter 24's
+`SpillStats`: 39, 52, 26, 24, and 58 entries for cohorts 20-24. The source files
+are deduplicated by contents and named for their first referring test. The
+manifest preserves test identity, target, ABI, and the original stopping phase.
+`Win64` becomes `win64`, the Chapter 25 ABI spelling. Argument type is i64 and
+the replay seed is 123 throughout.
+
+The original 212 entries were captured from the Chapter 24 tests before the
+allocator audit's new correctness regressions. The 2026-10-07 refresh adds
+Chapter22Test.testNarrowCReturns on x86 SystemV and Win64, matching the two
+entries already measured in Chapters 22-24. The source is unchanged; only the
+manifest's ABI spelling changes. The [cross-chapter matrix](../../../../../../../../docs/regalloc-spills.md)
+checks matching test/CPU/ABI membership, including repeated compilations.
+The same refresh removes all 15 Jig entries: six from cohort 23 and nine from
+cohort 24. Jig methods are ignored scratch pads for debugging, not regression
+tests or allocation benchmarks. Their frozen source copies were removed too.
+These inputs are a stable comparison corpus; changing one
+requires explaining the resulting break in comparability. Synthetic allocator
+and instruction regressions do not contribute allocations to these cohorts.
+
+Chapter 25 cannot parse all earlier sources unchanged. Adaptations are explicit:
+
+- String, Scan, and linked S constructors replace `new S { field=...; }` with
+ declared constructors and arguments. The explicit-receiver scanner functions
+ become instance methods, preserving their operations.
+- The saved `Chapter21Test-testStringExport`, `Chapter21AllocTest-testString`,
+ and `Chapter23AllocTest-testString` sources declare
+ `new String = { u8[] data -> cs=data; };` to initialize the required non-null
+ `cs` field. This repairs ten replay entries (four, three, and three), without
+ changing their equality/hash bodies or manifest membership. These adapted
+ sources are a new comparison baseline; their totals cannot be compared as
+ identical inputs with the earlier constructor-free fixtures.
+- The two short-circuit RHS initializer loops become equivalent guarded blocks,
+ preserving allocation, side effects, and the final result. Their RHS assigns
+ the field value directly: Simple's `||` returns an operand, not a normalized
+ boolean. The 2026-10-07 cleanup removes an erroneous `!!` in those adaptations;
+ both fixtures return 44 for even arguments and 1 for odd arguments.
+- Simple I/O cases include the old `write` binding and print helper directly.
+ Bubble Sort includes the old library bodies, with namespace prefixes flattened,
+ explicit constructors, and mutable replacement buffers. This retains the old
+ workload instead of substituting Chapter 25's substantially revised example.
+- Local implementation names are private in eleven fixtures: the four Newton
+ variants that call their helper from the top level, Chapter 20's MergeSort,
+ String and Cast programs, and Chapter 22's three sign-extension programs and
+ InfiniteReturn. Their helpers/classes acquire an underscore prefix; operations,
+ inputs and final results are unchanged. This avoids exporting implementation
+ functions and allocation factories solely because Chapter 25 treats top-level
+ declarations as module members. Export/API fixtures, including NewtonExport,
+ StringExport, Person and the C-return wrappers, retain their public names.
+ These visibility adaptations change the generated workload and are recorded
+ separately from the allocator improvement in the cross-chapter comparison.
+- The remaining visibility cleanup makes local classes/functions private in
+ AntiDeps1, Infinite, the Chapter 21/23 allocation String cases, the Chapter 23
+ scanner/pointer/short-circuit/function cases, and Stack3/Stack5. Bubble Sort's
+ copied library implementation is private; its `main` stays public. The three
+ adapted String API fixtures end in `return 0;`, avoiding an accidental return
+ of the last declared function. Their equality/hash APIs stay public, and the
+ named StringExport fixture also keeps its public String factory. No cohort
+ entries, workload operations, or intentional export APIs are removed.
+- Other source texts are retained.
+
+All entries replay **through RegAlloc**, checking scheduled register constraints.
+The historical phase column is provenance, not a promise to run those old native
+harnesses in Chapter 25. The adapted Bubble Sort retains its historical
+`i32 errno="C"` declaration for allocation comparability. Direct data bindings
+now encode on all three targets, but this declaration cannot link to a libc
+whose errno is thread-local or accessor-based. Current system-library code uses
+`libc.errno()` instead. Do not count this corpus as historical runtime coverage.
+
+`SpillStats` then compiles the current system library through encoding at seed
+456, and runs Chapter25Test with its existing seeds and native checks. Together
+these form cohort 25. A library or test failure makes the report fail; partial
+totals cannot establish an improvement. The ordinary full chapter suite remains
+the execution validation for the current compiler.
diff --git a/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv b/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv
new file mode 100644
index 00000000..83b19d2a
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/spill/cohorts.tsv
@@ -0,0 +1,200 @@
+# cohort test cpu abi original phase source
+Chapter20 Chapter20Test.testMergeSort x86_64_v2 SystemV RegAlloc Chapter20Test-testMergeSort.smp
+Chapter20 Chapter20Test.testMergeSort riscv SystemV RegAlloc Chapter20Test-testMergeSort.smp
+Chapter20 Chapter20Test.testMergeSort arm SystemV RegAlloc Chapter20Test-testMergeSort.smp
+Chapter20 Chapter20Test.testNewtonInteger x86_64_v2 SystemV RegAlloc Chapter20Test-testNewtonInteger.smp
+Chapter20 Chapter20Test.testNewtonInteger riscv SystemV RegAlloc Chapter20Test-testNewtonInteger.smp
+Chapter20 Chapter20Test.testNewtonInteger arm SystemV RegAlloc Chapter20Test-testNewtonInteger.smp
+Chapter20 Chapter20Test.testCast x86_64_v2 SystemV RegAlloc Chapter20Test-testCast.smp
+Chapter20 Chapter20Test.testCast riscv SystemV RegAlloc Chapter20Test-testCast.smp
+Chapter20 Chapter20Test.testCast arm SystemV RegAlloc Chapter20Test-testCast.smp
+Chapter20 Chapter20Test.testNewtonFloat x86_64_v2 SystemV RegAlloc Chapter20Test-testNewtonFloat.smp
+Chapter20 Chapter20Test.testNewtonFloat riscv SystemV RegAlloc Chapter20Test-testNewtonFloat.smp
+Chapter20 Chapter20Test.testNewtonFloat arm SystemV RegAlloc Chapter20Test-testNewtonFloat.smp
+Chapter20 Chapter20Test.testBrainfuck x86_64_v2 SystemV RegAlloc Chapter20Test-testBrainfuck.smp
+Chapter20 Chapter20Test.testBrainfuck riscv SystemV RegAlloc Chapter20Test-testBrainfuck.smp
+Chapter20 Chapter20Test.testBrainfuck arm SystemV RegAlloc Chapter20Test-testBrainfuck.smp
+Chapter20 Chapter20Test.testAlloc0 x86_64_v2 SystemV RegAlloc Chapter20Test-testAlloc0.smp
+Chapter20 Chapter20Test.testAlloc0 riscv SystemV RegAlloc Chapter20Test-testAlloc0.smp
+Chapter20 Chapter20Test.testAlloc0 arm SystemV RegAlloc Chapter20Test-testAlloc0.smp
+Chapter20 Chapter20Test.testAlloc2 x86_64_v2 SystemV RegAlloc Chapter20Test-testAlloc2.smp
+Chapter20 Chapter20Test.testAlloc2 riscv SystemV RegAlloc Chapter20Test-testAlloc2.smp
+Chapter20 Chapter20Test.testAlloc2 arm SystemV RegAlloc Chapter20Test-testAlloc2.smp
+Chapter20 Chapter20Test.testArray1 x86_64_v2 SystemV RegAlloc Chapter20Test-testArray1.smp
+Chapter20 Chapter20Test.testArray1 riscv SystemV RegAlloc Chapter20Test-testArray1.smp
+Chapter20 Chapter20Test.testArray1 arm SystemV RegAlloc Chapter20Test-testArray1.smp
+Chapter20 Chapter20Test.testBasic1 x86_64_v2 SystemV RegAlloc Chapter20Test-testBasic1.smp
+Chapter20 Chapter20Test.testBasic1 riscv SystemV RegAlloc Chapter20Test-testBasic1.smp
+Chapter20 Chapter20Test.testBasic1 arm SystemV RegAlloc Chapter20Test-testBasic1.smp
+Chapter20 Chapter20Test.testFlags1 x86_64_v2 SystemV RegAlloc Chapter20Test-testFlags1.smp
+Chapter20 Chapter20Test.testFlags1 riscv SystemV RegAlloc Chapter20Test-testFlags1.smp
+Chapter20 Chapter20Test.testFlags1 arm SystemV RegAlloc Chapter20Test-testFlags1.smp
+Chapter20 Chapter20Test.testFlags2 x86_64_v2 SystemV RegAlloc Chapter20Test-testFlags2.smp
+Chapter20 Chapter20Test.testFlags2 riscv SystemV RegAlloc Chapter20Test-testFlags2.smp
+Chapter20 Chapter20Test.testFlags2 arm SystemV RegAlloc Chapter20Test-testFlags2.smp
+Chapter20 Chapter20Test.testFltArg x86_64_v2 SystemV RegAlloc Chapter20Test-testFltArg.smp
+Chapter20 Chapter20Test.testFltArg riscv SystemV RegAlloc Chapter20Test-testFltArg.smp
+Chapter20 Chapter20Test.testFltArg arm SystemV RegAlloc Chapter20Test-testFltArg.smp
+Chapter20 Chapter20Test.testString x86_64_v2 SystemV RegAlloc Chapter20Test-testString.smp
+Chapter20 Chapter20Test.testString riscv SystemV RegAlloc Chapter20Test-testString.smp
+Chapter20 Chapter20Test.testString arm SystemV RegAlloc Chapter20Test-testString.smp
+Chapter21 Chapter21Test.testFibExport x86_64_v2 win64 Export Chapter21Test-testFibExport.smp
+Chapter21 Chapter21Test.testFibExport riscv SystemV Export Chapter21Test-testFibExport.smp
+Chapter21 Chapter21Test.testFibExport arm SystemV Export Chapter21Test-testFibExport.smp
+Chapter21 Chapter21Test.testAntiDeps1 x86_64_v2 SystemV Encoding Chapter21Test-testAntiDeps1.smp
+Chapter21 Chapter21Test.testAntiDeps1 riscv SystemV Encoding Chapter21Test-testAntiDeps1.smp
+Chapter21 Chapter21Test.testAntiDeps1 arm SystemV Encoding Chapter21Test-testAntiDeps1.smp
+Chapter21 Chapter21Test.testLoop2 x86_64_v2 win64 Encoding Chapter21Test-testLoop2.smp
+Chapter21 Chapter21Test.testLoop2 riscv SystemV Encoding Chapter21Test-testLoop2.smp
+Chapter21 Chapter21Test.testLoop2 arm SystemV Encoding Chapter21Test-testLoop2.smp
+Chapter21 Chapter21Test.testSieve x86_64_v2 win64 Export Chapter21Test-testSieve.smp
+Chapter21 Chapter21Test.testSieve riscv SystemV Export Chapter21Test-testSieve.smp
+Chapter21 Chapter21Test.testSieve arm SystemV Export Chapter21Test-testSieve.smp
+Chapter21 Chapter21Test.testArgCount x86_64_v2 win64 Export Chapter21Test-testArgCount.smp
+Chapter21 Chapter21Test.testArgCount riscv SystemV Export Chapter21Test-testArgCount-2.smp
+Chapter21 Chapter21Test.testArgCount arm SystemV Export Chapter21Test-testArgCount-2.smp
+Chapter21 Chapter21Test.testStringExport x86_64_v2 win64 Export Chapter21Test-testStringExport.smp
+Chapter21 Chapter21Test.testInfinite x86_64_v2 SystemV Encoding Chapter21Test-testInfinite.smp
+Chapter21 Chapter21Test.testInfinite riscv SystemV Encoding Chapter21Test-testInfinite.smp
+Chapter21 Chapter21Test.testInfinite arm SystemV Encoding Chapter21Test-testInfinite.smp
+Chapter21 Chapter21Test.testArray1 x86_64_v2 SystemV Encoding Chapter20Test-testArray1.smp
+Chapter21 Chapter21Test.testArray1 riscv SystemV Encoding Chapter20Test-testArray1.smp
+Chapter21 Chapter21Test.testArray1 arm SystemV Encoding Chapter20Test-testArray1.smp
+Chapter21 Chapter21Test.testBasic1 x86_64_v2 SystemV Encoding Chapter20Test-testBasic1.smp
+Chapter21 Chapter21Test.testBasic1 riscv SystemV Encoding Chapter20Test-testBasic1.smp
+Chapter21 Chapter21Test.testBasic1 arm SystemV Encoding Chapter20Test-testBasic1.smp
+Chapter21 Chapter21Test.testNewtonExport x86_64_v2 win64 Export Chapter21Test-testNewtonExport.smp
+Chapter21 Chapter21Test.testNewtonExport riscv SystemV Export Chapter21Test-testNewtonExport.smp
+Chapter21 Chapter21Test.testNewtonExport arm SystemV Export Chapter21Test-testNewtonExport.smp
+Chapter21 Chapter21Test.testPerson x86_64_v2 win64 Export Chapter21Test-testPerson.smp
+Chapter21 Chapter21Test.testPerson riscv SystemV Export Chapter21Test-testPerson.smp
+Chapter21 Chapter21Test.testPerson arm SystemV Export Chapter21Test-testPerson.smp
+Chapter21 Chapter21Test.testString x86_64_v2 SystemV Encoding Chapter21Test-testStringExport.smp
+Chapter21 Chapter21Test.testString riscv SystemV Encoding Chapter21Test-testStringExport.smp
+Chapter21 Chapter21Test.testString arm SystemV Encoding Chapter21Test-testStringExport.smp
+Chapter21 Chapter21AllocTest.testNewtonInteger x86_64_v2 win64 Encoding Chapter20Test-testNewtonInteger.smp
+Chapter21 Chapter21AllocTest.testNewtonInteger riscv SystemV Encoding Chapter20Test-testNewtonInteger.smp
+Chapter21 Chapter21AllocTest.testNewtonInteger arm SystemV Encoding Chapter20Test-testNewtonInteger.smp
+Chapter21 Chapter21AllocTest.testNewtonFloat x86_64_v2 SystemV Encoding Chapter21AllocTest-testNewtonFloat.smp
+Chapter21 Chapter21AllocTest.testNewtonFloat riscv SystemV Encoding Chapter21AllocTest-testNewtonFloat.smp
+Chapter21 Chapter21AllocTest.testNewtonFloat arm SystemV Encoding Chapter21AllocTest-testNewtonFloat.smp
+Chapter21 Chapter21AllocTest.testArray1 x86_64_v2 SystemV Encoding Chapter21AllocTest-testArray1.smp
+Chapter21 Chapter21AllocTest.testArray1 riscv SystemV Encoding Chapter21AllocTest-testArray1.smp
+Chapter21 Chapter21AllocTest.testArray1 arm SystemV Encoding Chapter21AllocTest-testArray1.smp
+Chapter21 Chapter21AllocTest.testString x86_64_v2 SystemV Encoding Chapter21AllocTest-testString.smp
+Chapter21 Chapter21AllocTest.testString riscv SystemV Encoding Chapter21AllocTest-testString.smp
+Chapter21 Chapter21AllocTest.testString arm SystemV Encoding Chapter21AllocTest-testString.smp
+Chapter22 Chapter22Test.testSextFail x86_64_v2 win64 Encoding Chapter22Test-testSextFail.smp
+Chapter22 Chapter22Test.testSextFail riscv SystemV Encoding Chapter22Test-testSextFail.smp
+Chapter22 Chapter22Test.testSextFail arm SystemV Encoding Chapter22Test-testSextFail.smp
+Chapter22 Chapter22Test.testSextFail riscv SystemV Export Chapter22Test-testSextFail.smp
+Chapter22 Chapter22Test.testSextFail arm SystemV Export Chapter22Test-testSextFail.smp
+Chapter22 Chapter22Test.testSextSuccess riscv SystemV Export Chapter22Test-testSextSuccess.smp
+Chapter22 Chapter22Test.testSextSuccess arm SystemV Export Chapter22Test-testSextSuccess.smp
+Chapter22 Chapter22Test.testSextSuccess x86_64_v2 win64 Encoding Chapter22Test-testSextSuccess.smp
+Chapter22 Chapter22Test.testSextSuccess riscv SystemV Encoding Chapter22Test-testSextSuccess.smp
+Chapter22 Chapter22Test.testSextSuccess arm SystemV Encoding Chapter22Test-testSextSuccess.smp
+Chapter22 Chapter22Test.testInfiniteReturn x86_64_v2 SystemV Encoding Chapter22Test-testInfiniteReturn.smp
+Chapter22 Chapter22Test.testInfiniteReturn riscv SystemV Encoding Chapter22Test-testInfiniteReturn.smp
+Chapter22 Chapter22Test.testInfiniteReturn arm SystemV Encoding Chapter22Test-testInfiniteReturn.smp
+Chapter22 Chapter22Test.testSextFail2 riscv SystemV Export Chapter22Test-testSextFail2.smp
+Chapter22 Chapter22Test.testSextFail2 arm SystemV Export Chapter22Test-testSextFail2.smp
+Chapter22 Chapter22Test.testSextFail2 x86_64_v2 win64 Encoding Chapter22Test-testSextFail2.smp
+Chapter22 Chapter22Test.testSextFail2 riscv SystemV Encoding Chapter22Test-testSextFail2.smp
+Chapter22 Chapter22Test.testSextFail2 arm SystemV Encoding Chapter22Test-testSextFail2.smp
+Chapter22 Chapter22Test.testPerson x86_64_v2 win64 Export Chapter22Test-testPerson.smp
+Chapter22 Chapter22Test.testPerson riscv SystemV Export Chapter22Test-testPerson.smp
+Chapter22 Chapter22Test.testPerson arm SystemV Export Chapter22Test-testPerson.smp
+Chapter22 Chapter22Test.testHelloWorld x86_64_v2 win64 Export Chapter22Test-testHelloWorld.smp
+Chapter22 Chapter22Test.testHelloWorld riscv SystemV Export Chapter22Test-testHelloWorld.smp
+Chapter22 Chapter22Test.testHelloWorld arm SystemV Export Chapter22Test-testHelloWorld.smp
+Chapter23 Chapter23Test.testAnd x86_64_v2 win64 Export Chapter23Test-testAnd.smp
+Chapter23 Chapter23Test.testAnd riscv SystemV Export Chapter23Test-testAnd.smp
+Chapter23 Chapter23Test.testAnd arm SystemV Export Chapter23Test-testAnd.smp
+Chapter23 Chapter23Test.testOr x86_64_v2 win64 Export Chapter23Test-testOr.smp
+Chapter23 Chapter23Test.testOr riscv SystemV Export Chapter23Test-testOr.smp
+Chapter23 Chapter23Test.testOr arm SystemV Export Chapter23Test-testOr.smp
+Chapter23 Chapter23Test.testCondSideEffAnd x86_64_v2 win64 Export Chapter23Test-testCondSideEffAnd.smp
+Chapter23 Chapter23Test.testCondSideEffAnd riscv SystemV Export Chapter23Test-testCondSideEffAnd.smp
+Chapter23 Chapter23Test.testCondSideEffAnd arm SystemV Export Chapter23Test-testCondSideEffAnd.smp
+Chapter23 Chapter23Test.testFRefFields x86_64_v2 win64 Encoding Chapter23Test-testFRefFields.smp
+Chapter23 Chapter23Test.testAndPtr riscv SystemV Export Chapter23Test-testAndPtr.smp
+Chapter23 Chapter23Test.testAndPtr arm SystemV Export Chapter23Test-testAndPtr.smp
+Chapter23 Chapter23Test.testOrRhsLoopLocal riscv SystemV Export Chapter23Test-testOrRhsLoopLocal.smp
+Chapter23 Chapter23Test.testMethod x86_64_v2 win64 Encoding Chapter23Test-testMethod.smp
+Chapter23 Chapter23Test.testOrRhsLoopMemory riscv SystemV Export Chapter23Test-testOrRhsLoopMemory.smp
+Chapter23 Chapter23Test.testCondSideEffOr x86_64_v2 win64 Export Chapter23Test-testCondSideEffOr.smp
+Chapter23 Chapter23Test.testCondSideEffOr riscv SystemV Export Chapter23Test-testCondSideEffOr.smp
+Chapter23 Chapter23Test.testCondSideEffOr arm SystemV Export Chapter23Test-testCondSideEffOr.smp
+Chapter23 Chapter23Test.testFuncCall x86_64_v2 win64 Export Chapter23Test-testFuncCall.smp
+Chapter23 Chapter23Test.testFuncCall riscv SystemV Export Chapter23Test-testFuncCall.smp
+Chapter23 Chapter23Test.testFuncCall arm SystemV Export Chapter23Test-testFuncCall.smp
+Chapter23 Chapter23AllocTest.testString x86_64_v2 SystemV Encoding Chapter23AllocTest-testString.smp
+Chapter23 Chapter23AllocTest.testString riscv SystemV Encoding Chapter23AllocTest-testString.smp
+Chapter23 Chapter23AllocTest.testString arm SystemV Encoding Chapter23AllocTest-testString.smp
+Chapter24 Chapter24Test.testAnd x86_64_v2 win64 Export Chapter23Test-testAnd.smp
+Chapter24 Chapter24Test.testAnd riscv SystemV Export Chapter23Test-testAnd.smp
+Chapter24 Chapter24Test.testAnd arm SystemV Export Chapter23Test-testAnd.smp
+Chapter24 Chapter24Test.testOr x86_64_v2 win64 Export Chapter23Test-testOr.smp
+Chapter24 Chapter24Test.testOr riscv SystemV Export Chapter23Test-testOr.smp
+Chapter24 Chapter24Test.testOr arm SystemV Export Chapter23Test-testOr.smp
+Chapter24 Chapter24Test.testCondSideEffAnd x86_64_v2 win64 Export Chapter23Test-testCondSideEffAnd.smp
+Chapter24 Chapter24Test.testCondSideEffAnd riscv SystemV Export Chapter23Test-testCondSideEffAnd.smp
+Chapter24 Chapter24Test.testCondSideEffAnd arm SystemV Export Chapter23Test-testCondSideEffAnd.smp
+Chapter24 Chapter24Test.testFRefFields x86_64_v2 win64 Encoding Chapter23Test-testFRefFields.smp
+Chapter24 Chapter24Test.testAndPtr riscv SystemV Export Chapter23Test-testAndPtr.smp
+Chapter24 Chapter24Test.testAndPtr arm SystemV Export Chapter23Test-testAndPtr.smp
+Chapter24 Chapter24Test.testMethod x86_64_v2 win64 Encoding Chapter23Test-testMethod.smp
+Chapter24 Chapter24Test.testStack1 x86_64_v2 win64 Export Chapter24Test-testStack1.smp
+Chapter24 Chapter24Test.testStack1 riscv SystemV Export Chapter24Test-testStack1.smp
+Chapter24 Chapter24Test.testStack1 arm SystemV Export Chapter24Test-testStack1.smp
+Chapter24 Chapter24Test.testStack2 x86_64_v2 win64 Export Chapter24Test-testStack2.smp
+Chapter24 Chapter24Test.testStack2 riscv SystemV Export Chapter24Test-testStack2.smp
+Chapter24 Chapter24Test.testStack2 arm SystemV Export Chapter24Test-testStack2.smp
+Chapter24 Chapter24Test.testStack3 x86_64_v2 win64 Export Chapter24Test-testStack3.smp
+Chapter24 Chapter24Test.testStack3 riscv SystemV Export Chapter24Test-testStack3.smp
+Chapter24 Chapter24Test.testStack3 arm SystemV Export Chapter24Test-testStack3.smp
+Chapter24 Chapter24Test.testStack4 x86_64_v2 win64 Export Chapter24Test-testStack4.smp
+Chapter24 Chapter24Test.testStack4 riscv SystemV Export Chapter24Test-testStack4.smp
+Chapter24 Chapter24Test.testStack4 arm SystemV Export Chapter24Test-testStack4.smp
+Chapter24 Chapter24Test.testStack5 x86_64_v2 win64 Export Chapter24Test-testStack5.smp
+Chapter24 Chapter24Test.testStack5 riscv SystemV Export Chapter24Test-testStack5.smp
+Chapter24 Chapter24Test.testStack5 arm SystemV Export Chapter24Test-testStack5.smp
+Chapter24 Chapter24Test.testStack9 x86_64_v2 win64 Export Chapter24Test-testStack9.smp
+Chapter24 Chapter24Test.testStack9 riscv SystemV Export Chapter24Test-testStack9-2.smp
+Chapter24 Chapter24Test.testStack9 arm SystemV Export Chapter24Test-testStack9-2.smp
+Chapter24 Chapter24Test.testCondSideEffOr x86_64_v2 win64 Export Chapter23Test-testCondSideEffOr.smp
+Chapter24 Chapter24Test.testCondSideEffOr riscv SystemV Export Chapter23Test-testCondSideEffOr.smp
+Chapter24 Chapter24Test.testCondSideEffOr arm SystemV Export Chapter23Test-testCondSideEffOr.smp
+Chapter24 Chapter24Test.testStack10 x86_64_v2 win64 Export Chapter24Test-testStack10.smp
+Chapter24 Chapter24Test.testStack10 riscv SystemV Export Chapter24Test-testStack10-2.smp
+Chapter24 Chapter24Test.testStack10 arm SystemV Export Chapter24Test-testStack10-2.smp
+Chapter24 Chapter24Test.testStack11 x86_64_v2 win64 Export Chapter24Test-testStack11.smp
+Chapter24 Chapter24Test.testStack11 riscv SystemV Export Chapter24Test-testStack11-2.smp
+Chapter24 Chapter24Test.testStack11 arm SystemV Export Chapter24Test-testStack11-2.smp
+Chapter24 Chapter24Test.testStack12 x86_64_v2 win64 Export Chapter24Test-testStack12.smp
+Chapter24 Chapter24Test.testStack12 riscv SystemV Export Chapter24Test-testStack12-2.smp
+Chapter24 Chapter24Test.testStack12 arm SystemV Export Chapter24Test-testStack12-2.smp
+Chapter24 Chapter24Test.testStack13 x86_64_v2 win64 Export Chapter24Test-testStack13.smp
+Chapter24 Chapter24Test.testStack13 riscv SystemV Export Chapter24Test-testStack13-2.smp
+Chapter24 Chapter24Test.testStack13 arm SystemV Export Chapter24Test-testStack13-2.smp
+Chapter24 Chapter24Test.testFuncCall x86_64_v2 win64 Export Chapter23Test-testFuncCall.smp
+Chapter24 Chapter24Test.testFuncCall riscv SystemV Export Chapter23Test-testFuncCall.smp
+Chapter24 Chapter24Test.testFuncCall arm SystemV Export Chapter23Test-testFuncCall.smp
+Chapter24 Chapter24AllocTest.testNewtonInteger x86_64_v2 win64 Encoding Chapter24AllocTest-testNewtonInteger.smp
+Chapter24 Chapter24AllocTest.testNewtonInteger riscv SystemV Encoding Chapter24AllocTest-testNewtonInteger.smp
+Chapter24 Chapter24AllocTest.testNewtonInteger arm SystemV Encoding Chapter24AllocTest-testNewtonInteger.smp
+Chapter24 Chapter24AllocTest.testStackArguments x86_64_v2 win64 Export Chapter24AllocTest-testStackArguments.smp
+Chapter24 Chapter24AllocTest.testStackArguments riscv SystemV Export Chapter24AllocTest-testStackArguments.smp
+Chapter24 Chapter24AllocTest.testStackArguments arm SystemV Export Chapter24AllocTest-testStackArguments.smp
+Chapter24 Chapter24AllocTest.testBubbleSort x86_64_v2 win64 Encoding Chapter24AllocTest-testBubbleSort.smp
+Chapter24 Chapter24AllocTest.testBubbleSort riscv SystemV Encoding Chapter24AllocTest-testBubbleSort.smp
+Chapter24 Chapter24AllocTest.testBubbleSort arm SystemV Encoding Chapter24AllocTest-testBubbleSort.smp
+Chapter21 BrainFuckTest.testBrainFuck x86_64_v2 win64 Export BrainFuckTest-testBrainFuck.smp
+Chapter21 BrainFuckTest.testBrainFuck riscv SystemV Export BrainFuckTest-testBrainFuck.smp
+Chapter21 BrainFuckTest.testBrainFuck arm SystemV Export BrainFuckTest-testBrainFuck.smp
+Chapter21 MergeSortTest.testMergeSort0 x86_64_v2 win64 Export MergeSortTest-testMergeSort0.smp
+Chapter21 MergeSortTest.testMergeSort0 riscv SystemV Export MergeSortTest-testMergeSort0.smp
+Chapter21 MergeSortTest.testMergeSort0 arm SystemV Encoding MergeSortTest-testMergeSort0.smp
+Chapter22 Chapter22Test.testNarrowCReturns x86_64_v2 SystemV Export Chapter22Test-testNarrowCReturns.smp
+Chapter22 Chapter22Test.testNarrowCReturns x86_64_v2 win64 Export Chapter22Test-testNarrowCReturns.smp
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/forward_ctor/m.smp b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m.smp
new file mode 100644
index 00000000..569f38be
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m.smp
@@ -0,0 +1,2 @@
+val a = a;
+val b = b;
diff --git a/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp
new file mode 100644
index 00000000..97113b71
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/a.smp
@@ -0,0 +1 @@
+val f = { -> new b(1); };
diff --git a/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp
new file mode 100644
index 00000000..43770b60
--- /dev/null
+++ b/src/test/java/com/seaofnodes/simple/test_smp/forward_ctor/m/b.smp
@@ -0,0 +1,4 @@
+struct b {
+ int x;
+ new b = { int y -> x = y; };
+};
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..e6b708a4
--- /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[~] xs ->
+ _grow(xs#);
+ ary.copy(buf,len,xs,0,xs#);
+ len += xs#;
+ 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;
+ };
+};