summaryrefslogtreecommitdiff
path: root/chapter25/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'chapter25/src/main')
-rw-r--r--chapter25/src/main/c/simple_crt.c61
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java199
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Parser.java2188
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/Var.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java20
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java214
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java738
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java143
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java87
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java382
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java236
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java467
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java371
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java86
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java177
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java69
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java91
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java79
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java60
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java22
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java26
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java98
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java216
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java411
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java150
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java672
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java49
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java177
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java56
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java112
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java41
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java96
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java143
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java172
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java30
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java412
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java94
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java79
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java51
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java62
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java16
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java27
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java116
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java29
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java27
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java56
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java375
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java68
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java10
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java205
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java191
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java137
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java68
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java32
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java37
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java113
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Node.java208
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java86
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java161
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java23
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java135
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java469
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java58
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java124
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java80
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java217
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java56
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java28
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java33
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java (renamed from chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java)9
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java6
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java37
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java18
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java113
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java11
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java35
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java41
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java5
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java31
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java116
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java12
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java7
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java8
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java3
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java48
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java21
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java152
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java40
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java43
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java2
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Field.java90
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/Type.java259
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java19
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java4
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java38
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java239
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java66
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java206
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java102
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java1
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java17
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java115
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java44
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java413
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java88
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java268
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java162
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java87
-rw-r--r--chapter25/src/main/java/com/seaofnodes/simple/util/SB.java2
-rw-r--r--chapter25/src/main/smp/sys.smp206
-rw-r--r--chapter25/src/main/smp/sys/adt.smp3
-rw-r--r--chapter25/src/main/smp/sys/adt/bitset.smp57
-rw-r--r--chapter25/src/main/smp/sys/ary.smp24
-rw-r--r--chapter25/src/main/smp/sys/aryi64.smp82
-rw-r--r--chapter25/src/main/smp/sys/aryu8.smp87
-rw-r--r--chapter25/src/main/smp/sys/char.smp8
-rw-r--r--chapter25/src/main/smp/sys/io.smp64
-rw-r--r--chapter25/src/main/smp/sys/libc.smp44
-rw-r--r--chapter25/src/main/smp/sys/scan.smp39
185 files changed, 4598 insertions, 11494 deletions
diff --git a/chapter25/src/main/c/simple_crt.c b/chapter25/src/main/c/simple_crt.c
deleted file mode 100644
index 8656a3cc..00000000
--- a/chapter25/src/main/c/simple_crt.c
+++ /dev/null
@@ -1,61 +0,0 @@
-#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/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
index e65ef6d9..2fe4e172 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/IterPeeps.java
@@ -1,14 +1,12 @@
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;
/**
@@ -40,7 +38,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. It's a useful
+ * check is linear at each peephole step... so quadratic overall. Its 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
@@ -50,11 +48,11 @@ import java.util.Random;
public class IterPeeps {
public final WorkList<Node> _work;
- final WorkList<CallEndNode> _workInline;
- private int _cnt;
+
+ private final WorkList<CallEndNode> _workInline;
public IterPeeps( long seed ) {
- _work = new WorkList<>(seed);
+ _work = new WorkList<>(seed);
_workInline = new WorkList<>(seed);
}
@@ -64,53 +62,36 @@ public class IterPeeps {
public void addAll( Ary<Node> ary ) { _work.addAll(ary); }
/**
- * Iterate peepholes and inlining to a fixed point
+ * Iterate peepholes 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.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();
+ while( (cend=_workInline.pop())!=null )
+ if( cend.inlineSmall() ) break;
+ if( cend==null ) return;
}
}
- // Run all the code-reduction and type-lifting peeps as possible
+ // Only try growing inlines once the shrinking peepholes reach a fixed point.
private void iteratePeeps( CodeGen code ) {
- assert !CodeGen.expensiveAssert(0) || (progressOnList(code, _work) && schedulableUses(code));
+ assert progressOnList(code, _work, true);
+ int cnt=0;
Node n;
while( (n=_work.pop()) != null ) {
if( n.isDead() ) continue;
- _cnt++; // Useful for debugging, searching which peephole broke things
- var obs = code._obs; // Collect peephole neighbors for graph display
+ if( n instanceof CallEndNode cend ) _workInline.push(cend);
+ cnt++; // Useful for debugging, searching which peephole broke things
+ var obs = CodeGen.CODE._obs;
if( obs != null ) obs.before(n);
Node x = n.peepholeOpt();
- if( n instanceof CallEndNode cend )
- _workInline.push(cend);
if( x != null ) {
- assert !x.isDead(); // Peepholes return alive answers
+ if( x.isDead() ) {
+ if( obs != null ) obs.after(n, x, true);
+ continue;
+ }
// 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
@@ -120,31 +101,22 @@ 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();
- // Very expensive assert.
- assert !CodeGen.expensiveAssert(_cnt) || (progressOnList(code, _work) && schedulableUses(code));
+ assert progressOnList(code, _work, true); // Very expensive assert
}
- boolean unused = n.isUnused();
- if( unused ) {
- assert !(n instanceof StopNode); // StopNodes can die if all code in the compunit dies
+ if( n.isUnused() && !(n instanceof StopNode) )
n.kill(); // Just plain dead
- }
- if( obs != null ) obs.after(n, x!=null ? x : unused ? n : null, true);
+ if( obs != null ) obs.after(n, x, true);
}
- assert !CodeGen.expensiveAssert(0) || schedulableUses(code);
}
@@ -161,112 +133,27 @@ 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 ) {
+ public static boolean progressOnList(CodeGen code, WorkList<Node> list, boolean dir ) {
code._midAssert = true;
Node changed = code._stop.walk( n -> {
- // Ignore most in-progress things
- if( n.iskeep() ) return null;
- // Types must be forwards, even if on the worklist.
+ Node m = n;
Type nval = n.compute();
- 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;
+ 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;
}
- // 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;
+ System.err.println("BREAK HERE FOR BUG");
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.
@@ -314,16 +201,12 @@ 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); }
+ public boolean isEmpty() { return _len==0; }
/**
* Removes a random Node from the WorkList; null if WorkList is empty
@@ -337,13 +220,11 @@ public class IterPeeps {
return x;
}
- 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;
- //}
+ public void clear() {
+ _len = 0;
+ _on.clear();
+ _R.setSeed(_seed);
+ _totalWork = 0;
+ }
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
index 5ceda6a9..56233250 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java
@@ -1,14 +1,11 @@
package com.seaofnodes.simple;
-import com.seaofnodes.simple.codegen.*;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.Utils;
import com.seaofnodes.simple.node.ScopeNode.Kind;
-
-import static com.seaofnodes.simple.util.Utils.TODO;
-
import java.util.*;
/**
@@ -20,32 +17,16 @@ 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
@@ -97,20 +78,34 @@ public class Parser {
*/
public final Stack<ScopeNode> _xScopes = new Stack<>();
- 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;
+ ScopeNode _continueScope;
+ ScopeNode _breakScope; // Merge all the while-breaks here
+ FunNode _fun; // Current function being parsed
// Mapping from a type name to a Type. The string name matches
// `type.str()` call.
- public static final HashMap<String, Type> TYPES = new HashMap<>();
- public static final HashMap<String, Type> INIT_TYPES =
- new HashMap<>() {{
+ 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<>() {{
put("bool",TypeInteger.U1 );
put("byte",TypeInteger.U8 );
put("f32" ,TypeFloat .F32);
@@ -128,420 +123,172 @@ public class Parser {
put("val" ,Type.TOP); // Marker type, indicates type inference
put("var" ,Type.BOTTOM); // Marker type, indicates type inference
}};
-
-
- public Parser(CodeGen code ) {
- _code = code;
}
- @Override
- public String toString() { return _lexer==null ? "" : _lexer.toString(); }
-
// Debugging utility to find a Node by index
public Node f(int nid) { return _code.f(nid); }
- // Read and set CTRL
private Node ctrl() { return _scope.ctrl(); }
private <N extends Node> N ctrl(N n) { return _scope.ctrl(n); }
- // Read and set Memory
- private Node mem() { return _scope.mem(1); }
- private void mem(Node mem) { _scope.mem(mem); }
- // Structural label for precise memory Phis. The '$' prefix cannot be
- // referenced in Simple source.
- public static String memName(int alias) { return ("$"+alias).intern(); }
-
- // 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);
- _frefs = new Ary<>(FRefNode.class);
- // Starting Scope has control, memory, initial arguments
- _scope = new ScopeNode();
+
+ public void parse() {
+ // Graph callbacks during scope setup need a source position.
+ _lexer = new Lexer(sys.SYS);
+
_scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer);
_scope.define(ScopeNode.MEM0, TypeMem.BOT , false, null, _lexer);
- // Track active scopes for Graph display
- _xScopes.push(_scope);
- ctrl(_code.XCTRL);
- mem(con(TypeMem.BOT));
+ _scope.define(ScopeNode.ARG0, TypeInteger.BOT, false, null, _lexer);
- // File-level struct declaration
- _ref = ref;
- _nestedType = ref._cname;
- String typeName = addClzPrefix(ref._cname);
+ ctrl(XCTRL);
+ _scope.mem(con(TypeMem.BOT));
- ReturnNode clzret = parseStruct( true, typeName );
+ // Parse the sys import
+ while( !_lexer.isEOF() ) {
+ parseStatement();
+ _lexer.skipWhiteSpace();
+ }
- if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
+ // Reset lexer for program text
+ _lexer = new Lexer(_code._src);
+ _xScopes.push(_scope);
+
+ // 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);
- // 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);
+ } 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";
}
- // The class being published
- ref._clz = (TypeStruct)TYPES.get(typeName);
+ if( !_lexer.isEOF() ) throw _errorSyntax("unexpected");
// Clean up and reset
_xScopes.pop();
_scope.kill();
- _scope = null;
- _frefs = null;
-
- return frefs;
+ for( StructNode init : INITS.values() )
+ init.unkeep().kill();
+ INITS.clear();
+ stop.peephole();
}
-
-
/**
* 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) {
- // 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;
+ 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();
// Stack parser state on the local Java stack, and unstack it later
- _scope.push(new Kind.Func(funName));
- ScopeNode breakScope = _breakScope; _breakScope = null;
+ FunNode oldfun = _fun;
+ ScopeNode breakScope = _breakScope; _breakScope = null;
ScopeNode continueScope = _continueScope; _continueScope = null;
- ScopeNode returnScope = _returnScope; _returnScope = null;
- int oldUID = _code.UID(); // Used to approximate function size
- FunNode fun = (FunNode)peep(new FunNode(loc(),sig, funName, _ref, null, _ref._start));
+ FunNode fun = _fun = (FunNode)peep(new FunNode(loc(),sig,_nestedType,null,_code._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,ConstantNode.seed(TypeRPC.BOT).peephole()).peephole();
+ 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,r,null).init();
+ ReturnNode ret = new ReturnNode(r, rmem, rrez, rpc, fun).init();
+ fun.setRet(ret);
+ assert ret.inProgress();
+ _code._stop.addDef(ret);
+
// 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.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);
+ _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());
// All args, "as-if" called externally
for( int i=0; i<ids.length; i++ ) {
Type t = sig.arg(i);
- // 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);
+ _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);
}
- // The initializer owns its run-once flag store. Parent initialization
- // is a structural requirement, executed before this body by CLInit lowering.
- if( fun.isClz() ) {
- Node self = fun.parm(2);
- Field flag = ((TypeMemPtr)self._type)._obj.field("$init");
- Node prior = mem().keep();
- Node st = peep(new StoreNode(loc,"$init",flag._alias,flag._t,ctrl(),prior,self,
- fldoff(self,"$init"),con(1),false));
- storeMem(st,flag._alias,prior);
- prior.unkeep();
- }
-
- // Parse the function body.
- Node last=_code.ZERO; // Last statement as the default result
-
- 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);
- }
- 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;
- _breakScope = breakScope;
- _scope.pop();
-
- return ret;
- }
+ // Parse the body
+ Node last=ZERO;
+ while (!peek('}') && !_lexer.isEOF())
+ last = parseStatement();
- 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() ) );
- }
+ // 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);
}
- // 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);
- }
+ // 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;
+ _continueScope = continueScope;
- private boolean isExplicitConstructorName(String funName) {
- TypeStruct self = _scope.constructorSelf();
- return self != null && funName == (self._name+"."+initFieldName(self._name)).intern();
- }
+ // 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());
- 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);
+ return ret;
}
-
/**
* Parses a block
*
@@ -554,11 +301,9 @@ public class Parser {
private Node parseBlock(Kind kind) {
// Enter a new scope
_scope.push(kind);
- Node last = _code.ZERO;
- while (!peek('}') && !_lexer.isEOF()) {
- _code.add(last);
+ Node last = ZERO;
+ while (!peek('}') && !_lexer.isEOF())
last = parseStatement();
- }
// Exit scope
last.keep();
_scope.pop();
@@ -581,8 +326,7 @@ public class Parser {
else if (matchx("break") ) return parseBreak();
else if (matchx("continue")) return parseContinue();
else if (matchx("struct") ) return parseStruct();
- else if (inStructDeclaration() && matchx("new")) return parseConstructorDeclaration();
- else if (matchx(";") ) return _code.ZERO; // Empty statement
+ else if (matchx(";") ) return null; // Empty statement
// Break ambiguity around leading function types and starting a block
else if (peek('{') && !isTypeFun() ) {
match("{");
@@ -592,31 +336,6 @@ 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
*
@@ -667,7 +386,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(),null,ctrl()).peephole()); // Note we set back edge to null here
+ ctrl(new LoopNode(loc(),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).
@@ -686,8 +405,8 @@ public class Parser {
// IfNode takes current control and predicate
Node ifNode = new IfNode(ctrl(), pred.keep()).peephole();
// Setup projection nodes
- CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- CFGNode ifF = (CFGNode)new CProjNode(ifNode.unkeep(), 1, "False").peephole();
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole();
// for( ;;next ) body
int nextPos = -1, nextEnd = -1;
@@ -740,8 +459,6 @@ 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();
@@ -759,12 +476,12 @@ public class Parser {
_xScopes.push( exit );
}
_scope = exit;
- return _code.ZERO;
+ return ZERO;
}
private ScopeNode jumpTo(ScopeNode toScope) {
ScopeNode cur = _scope.dup();
- ctrl(_code.XCTRL); // Kill current scope
+ ctrl(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() )
@@ -782,7 +499,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 _code.ZERO; }
+ private Node parseContinue() { checkLoopActive(); _continueScope = require(jumpTo( _continueScope ),";"); return ZERO; }
private Node parseBreak () {
checkLoopActive();
// At the time of the break, and loop-exit conditions are only valid if
@@ -790,8 +507,8 @@ public class Parser {
// conditions here, not the union.
_breakScope.removeGuards(_breakScope.ctrl());
_breakScope = require(jumpTo(_breakScope ),";");
- _breakScope.addGuards((CFGNode)_breakScope.ctrl(), null, false);
- return _code.ZERO;
+ _breakScope.addGuards(_breakScope.ctrl(), null, false);
+ return ZERO;
}
// Look for an unbalanced `)`, skipping balanced
@@ -801,7 +518,7 @@ public class Parser {
// Next X char handles skipping complex comments
switch( _lexer.nextXChar() ) {
case Character.MAX_VALUE:
- throw TODO();
+ throw Utils.TODO();
case ')':
if( --paren<0 ) {
posT( pos() - 1 );
@@ -835,8 +552,8 @@ public class Parser {
pred.keep();
Node ifNode = new IfNode(ctrl(), pred).peephole();
// Setup projection nodes
- CFGNode ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
- CFGNode ifF = new CProjNode(ifNode.unkeep(), 1, "False").peephole().keep();
+ Node ifT = new CProjNode(ifNode. keep(), 0, "True" ).peephole().keep();
+ Node 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
@@ -852,7 +569,7 @@ public class Parser {
case ":" -> parseAsgn(); // pred ? asgn;
case "&&" -> parseLogical(); // pred && expr
case "||" -> pred; // pred || expr_is_ignored
- default -> throw TODO();
+ default -> throw Utils.TODO();
};
lhs.keep();
_scope.removeGuards(ifT);
@@ -875,14 +592,12 @@ 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 "&&" -> con(lhs._type.makeZero()); // pred && expr_is_ignored
- case "||" -> parseLogical(); // pred || expr
- default -> throw TODO();
+ case "&&" -> rhs = pred;
+ case "||" -> rhs = parseLogical();
+ default -> throw Utils.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();
@@ -905,10 +620,9 @@ public class Parser {
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;
- ret.keep();
+ if( !fside.equals("else") && (err=ret.err()) !=null ) throw err;
r.peephole();
- return ret.unkeep();
+ return ret;
}
/**
@@ -922,69 +636,20 @@ public class Parser {
*/
private Node parseReturn() {
var expr = require(parseAsgn(), ";");
- return addReturn(expr);
+ // Force the lazy whole-memory Phi before closing this exit.
+ _fun.addReturn(ctrl(), _scope.mem(), expr);
+ ctrl(XCTRL); // Kill control
+ return 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('=','=') )
@@ -993,12 +658,16 @@ 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( def._extern!=0 ) // Parse the C binding as a memory lvalue
- { pos(old); return parseExpression(); }
+ // 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`
+ Var selfVar = _scope.lookup("self");
+ Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true);
+ return parsePostfixName(self,name);
+ }
// TOP fields are for late-initialized fields; these have never
// been written to, and this must be the final write. Other writes
@@ -1006,93 +675,34 @@ 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.inConstructor() && def._idx >= _scope.klast()._lexSize) &&
- !inExplicitConstructor() )
+ !_scope.canInit(def) )
throw error("Cannot reassign final '"+name+"'");
// Parse assignment expression
Node expr = parseAsgn();
// Lift expression, based on type
- Type decl = def.type(); // Declared type
- Node lift = convertExpr(expr.keep(), decl).keep();
+ Node lift = liftExpr(expr.keep(), def.type(), true);
// 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();
}
- // Widen ints to floats; narrow wide int types.
+ // Apply declared access permissions; widen floats and narrow integers.
// Early error if types do not match variable.
- 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());
+ private Node liftExpr(Node expr, Type t, boolean isLoad) {
+ if( t==Type.BOTTOM || t==Type.TOP ) return expr;
+ // Constructor sentinels record missing initialization, not a value
+ // needing conversion. Keep them visible to the completion check.
+ if( expr instanceof ConstantNode && !(expr instanceof FRefNode) &&
+ (expr._type==Type.TOP || expr._type==Type.BOTTOM) ) return expr;
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() )
- return peep(new AddNode(peep(new PtrToIntNode(expr)),off(tmp._obj,"[]")));
- // Widen ints to floats, narrow wide types
+ if( expr instanceof FunPtrNode && t instanceof TypeFunPtr ) return expr;
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 ) {
return (expr._type instanceof TypeInteger || expr._type==Type.NIL) && t instanceof TypeFloat
? peep(new ToFloatNode(expr)) : expr;
@@ -1119,6 +729,23 @@ 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) {
@@ -1137,45 +764,27 @@ public class Parser {
Lexer loc = loc();
String name = requireId();
// Optional initializing expression follows
- boolean fld_final = false; // Field is final, but not deeply final
- boolean uninit = false; // Required non-null constructor field
+ boolean fld_final = false; // Fixed field independent of pointee access
+ boolean initialized = match("=");
Node expr;
- 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();
- }
+ if( initialized ) {
+ expr = isExternDecl()
+ ? externDecl(name,t)
+ : 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 = mutableInferredType(t);
+ if( !xfinal ) t = inferredType(t); // Widen if not final
}
-
// expr is a constant 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
+ 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
}
} 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.
@@ -1183,33 +792,30 @@ public class Parser {
throw errorSyntax("=expression");
// Initial value for uninitialized struct fields.
expr = switch( t ) {
- // 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);
+ // 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);
// 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); }
};
- // 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;
- }
+ // 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;
}
// Lift expression, based on type
- // 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);
+ Node lift = liftExpr(expr, t, true);
+ // Lift type to the declaration. This will report as an error later if
+ // we cannot lift the type.
// Define a new name
if( !_scope.define(name,t,xfinal || fld_final,lift, loc) )
throw error("Redefining name '" + name + "'", loc);
- _scope.lookup(name)._uninit = uninit;
+ _scope.lookup(name)._init = initialized;
return lift;
}
@@ -1221,218 +827,59 @@ public class Parser {
*
* @return zero
*/
- private Node parseStruct( ) {
- // keyword "struct" already parsed, so expect the struct name next.
+ private String _nestedType;
+ private Node parseStruct() {
+ // "struct" already parsed, so expect the struct name next.
+ String old = _nestedType;
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 ) );
+ }
+ }
+ }
- // create type class:NESTED.typeName; no fields.
+ // Count struct declarations
+ int varlen = _scope._vars._len;
+ ts = ts.close();
+ TYPES.put(typeName,TypeMemPtr.make(ts));
- 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
+ // 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
- ReturnNode ret = parseStruct( false, fullName );
+ // 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());
- _nestedType = old;
+ // 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 );
- // 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>");
- // A same-named struct shares its file's class object and flag.
- Field flag = ts==null ? null : ts.field("$init");
- ts = ctorFld==null
- ? TypeStruct.make(clzName,false,fld)
- : TypeStruct.make(clzName,false,fld,ctorFld);
- if( flag!=null ) ts=ts.add(flag);
- TypeFunPtr alloc = makeAllocator((TypeStruct)TYPES.get(fullName),ret,ctorFld);
- ts = ts.add(Field.make("<new>",alloc,_code.alias(_ref._cname),true));
- TYPES.put(clzName,ts);
-
- // 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("}");
- 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());
- }
-
- // Class object keeps a runtime flag to signal the <clinit> has run.
- if( isClz )
- TYPES.put(typeName,tself=tself.add(Field.make("$init",TypeInteger.BOOL,_code.alias(_ref._cname),false)));
-
- // 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);
+ require(";");
+ _scope.pop();
+ return ZERO;
}
@@ -1442,135 +889,38 @@ 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), t1 = t0;
+ Type t0 = TYPES.get(tname);
// No new types as keywords
- 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);
+ if( t0 == null && KEYWORDS.contains(tname) )
+ return posT(old1);
+ if( t0 == Type.BOTTOM || t0 == Type.TOP ) return t0; // var/val type inference
+ // 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 ) {
@@ -1582,15 +932,14 @@ public class Parser {
Type t2 = t1;
while( true ) {
if( match("?") ) {
- if( t2 instanceof TypeMemPtr tmp ) {
- if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
- t2 = tmp.makeNullable();
- } else
+ if( !(t2 instanceof TypeMemPtr tmp) )
throw error("Type "+t0+" cannot be null");
+ if( tmp.nullable() ) throw error("Type "+t2+" already allows null");
+ t2 = tmp.makeNullable();
} else if( match("[~]") ) {
- t2 = TypeMemPtr.make(typeAry(t2,true)).makeRO();
+ t2 = typeAry(t2,true).makeRO();
} else if( match("[]") ) {
- t2 = TypeMemPtr.make(typeAry(t2,false));
+ t2 = typeAry(t2,false);
} else
break;
}
@@ -1602,64 +951,27 @@ public class Parser {
int old2 = pos();
if( !match("!") ) match("~");
String id = _lexer.matchId();
- if( !(peek(',') || peek(';') || peek('=') || match("->")) || id==null )
+ if( !(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
- forwardRefType(tname,(TypeStruct)((TypeMemPtr)t1)._obj);
+ TYPES.put(tname,((TypeMemPtr)t1).withAccess(false));
// Return the (array, final) type
return t2;
}
- 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);
+ // 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));
}
// A function type is `{ type... -> type }` or `{ type }`.
@@ -1668,19 +980,16 @@ public class Parser {
match("{"); // Skip already-peeked '{'
Type t0 = type(); // Either return or first arg
if( t0==null ) return posT(old); // Not a function
- 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);
+ return TypeFunPtr.make(match("?"),false,TypeFunPtr.TEMPTY,t0);
+ Ary<Type> ts = new Ary<>(Type.class);
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("?"),ts.asAry(),ret);
+ return TypeFunPtr.make(match("?"),false,ts.asAry(),ret);
}
Type t1 = type();
if( t1==null ) return posT(old); // Not a function
@@ -1789,8 +1098,8 @@ public class Parser {
return cmp.unkeep();
}
- // rhs is keeped() and becomes lhs
- // cmp is NOT keeped() and is the last test
+ // Keep the middle operand separately from the boolean result.
+ // The next test compares adjacent operands, not that boolean.
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();
@@ -1806,9 +1115,9 @@ public class Parser {
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
+ lhs = rhs; // Reuse the previous RHS exactly once
rhs = parseShift().keep();
- cmp = makeCompBool(dir,lhs.unkeep(),rhs); // Convert to a bool
+ cmp = makeCompBool(dir0,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();
@@ -1822,7 +1131,7 @@ public class Parser {
ctrl(ifT.unkeep());
RegionNode r = fail.mergeScopes(_scope, loc()).init();
_scope = fail;
- return new PhiNode("", r,_code.ZERO,con(1)).peephole();
+ return new PhiNode("", r,con(0),con(1)).peephole();
}
private int parseCompDir() {
@@ -1833,12 +1142,11 @@ public class Parser {
return 0;
}
- private Node makeCompBool( int dir0, Node lhs, Node rhs ) {
- // Convert to a bool
- 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);
+ private Node makeCompBool( int dir, Node lhs, Node rhs ) {
+ if( Math.abs(dir)==2 )
+ { Node tmp=lhs; lhs=rhs; rhs=tmp; }
+ Node cmp = dir < 0 ? new BoolNode.LE(lhs,rhs) : new BoolNode.LT(lhs,rhs);
+ return peep(cmp);
}
/**
@@ -1920,16 +1228,14 @@ public class Parser {
// Pre-dec/pre-inc
int old = pos();
if( match("--") || match("++") ) {
- int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Inc vs Dec
+ int delta = _lexer.peek(-1)=='+' ? 1 : -1; // Pre vs post
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 = zsMask(peep(new AddNode(_scope.in(n),con(delta))),n.type());
+ Node expr = liftExpr(peep(new AddNode(_scope.in(n),con(delta))),n.type(),true);
_scope.update(n,expr);
return expr;
}
@@ -1949,118 +1255,76 @@ public class Parser {
* <pre>
* primaryExpr : integerLiteral | "string" | 'char' | true | false | null |
* new Type | '(' expression ')' | Id['++','--'] |
- * type['.' Id]*
+ * [static.]* Id
* </pre>
* @return a primary {@link Node}, never {@code null}
*/
private Node parsePrimary() {
if( _lexer.isNumber(_lexer.peek()) ) return parseLiteral();
- if( _lexer.peek('"') ) return parsePostfix(newString(parseString()));
+ if( _lexer.peek('"') ) return newString(parseString());
if( matchx("true" ) ) return con(1);
- if( matchx("false") ) return _code.ZERO;
- if( matchx("null" ) ) return con(Type.NIL);
+ if( matchx("false") ) return ZERO;
+ if( matchx("null" ) ) return 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 ) {
- // 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 missing, assume a forward reference
+ _scope.define(id, FRefNode.FREF_TYPE, true, XCTRL, loc());
+ var = _scope.lookup(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));
+ // 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) ) {
+ Var selfVar = _scope.lookup("self");
+ Node self = liftExpr(_scope.in(selfVar),selfVar.type(),true);
+ return parsePostfixName(self,id);
}
// Load local value
- Node rvalue = _scope.in(var);
- 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);
- }
- }
+ Node oldValue = _scope.in(var);
+ if( oldValue instanceof ConstantNode && !(oldValue instanceof FRefNode) && oldValue._type==Type.BOTTOM )
+ throw error("Cannot read uninitialized field '"+id+"'");
+ Node rvalue = liftExpr(oldValue,var.type(),true);
+ if( rvalue!=oldValue ) _scope.update(var,rvalue);
+ if( rvalue instanceof ConstantNode && rvalue._type == Type.BOTTOM )
+ if( rvalue instanceof FRefNode )
+ return parsePostfix(rvalue);
+ else throw error("Cannot read uninitialized field '"+id+"'");
// Check for assign-update, x += e0;
char ch = _lexer.matchOperAssign();
- 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)));
- }
+ if( ch==0 ) // Normal primary, check for postfix updates
+ return parsePostfix(rvalue);
// Assign-update direct into Scope
if( var._final )
throw error("Cannot reassign final '"+var._name+"'");
@@ -2069,6 +1333,33 @@ 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.
@@ -2091,17 +1382,17 @@ public class Parser {
case '-' -> new SubNode(lhs,rhs);
case '*' -> new MulNode(lhs,rhs);
case '/' -> new DivNode(lhs,rhs);
- case '|' -> new OrNode(null,lhs,rhs);
- case '&' -> new AndNode(null,lhs,rhs);
+ case '&' -> new AndNode(loc(),lhs,rhs);
+ case '|' -> new OrNode(loc(),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 TODO();
+ default -> throw Utils.TODO();
};
// Convert to float ops, or narrow int types; error if not declared type.
// Also, if postfix LHS is still keep()
- return convertExpr(peep(op),t);
+ return liftExpr(peep(op),t,true);
}
@@ -2110,119 +1401,145 @@ public class Parser {
*/
private Node alloc() {
Type t = type();
- if( t==null ) {
- String tname = _lexer.matchId();
- if( tname==null || KEYWORDS.contains(tname) )
- throw error("Expected a type");
- t = forwardRefType(tname);
- }
+ if( t==null ) throw error("Expected a type");
// Parse ary[ length_expr ]
if( match("[") ) {
if( !t.makeZero().isa(t) )
- throw error("Cannot allocate a non-nullable, since arrays are always zero/null filled");
- Node len = parseAsgn().keep();
+ 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);
require("]");
- 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;
+ 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( missingInit(init.at(i),new java.util.BitSet()) )
+ 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;
+ }
+
+ // The old constructor model requires each field on every path. An
+ // unresolved expression also has BOTTOM now, so inspect the actual
+ // sentinel and merges instead of mistaking an unknown type for no write.
+ private boolean missingInit(Node n, java.util.BitSet visit) {
+ if( n==null || visit.get(n._nid) || n instanceof FRefNode ) return false;
+ visit.set(n._nid);
+ if( n instanceof ConstantNode ) return n._type==Type.BOTTOM || n._type==Type.TOP;
+ if( n instanceof ConvertNode || n instanceof ReadOnlyNode ) return missingInit(n.in(1),visit);
+ if( n instanceof PhiNode phi )
+ for( int i=1; i<n.nIns(); i++ )
+ if( phi.region().in(i)!=null && phi.region().in(i)._type!=Type.XCONTROL && missingInit(n.in(i),visit) ) return true;
+ return false;
}
- private Node allocArray(TypeStruct ts, Node len) {
- Field lenFld = ts.field("#" );
- Field bodyFld = ts.field("[]");
-
- ConFldOffNode base = off(ts, "[]");
- int scale = ts.aryScale();
- Node size = peep(new AddNode(base,peep(new ShlNode(null,len,con(scale)))));
-
- // Build a NewNode; takes in and puts out all aliases.
- NewNode nnn = new NewNode(ts, ctrl(), size ).init();
- ProjNode self = new ProjNode(nnn,0,ts.str()).init().keep();
- ProjNode smem = new ProjNode(nnn,1,"#selfMem").init().keep();
-
- // Store length. Rest of array is zero'd via CALLOC during CodeGen.
- // Length is casted to sanity.
- // TODO: Needs runtime check.
- Node st = peep(new StoreNode(null, "#", 1, TypeInteger.U32, null, smem, self, off(ts,"#"), len.unkeep(), true));
- // TODO: Allow add-on "constructor" to init the array elements
-
- Node prior = mem();
- Node esc = peep(new EscapeNode(lenFld,self,st,prior));
- mem(mergeAlias(prior,lenFld._alias,esc));
- prior = mem();
- esc = peep(new EscapeNode(bodyFld,self,smem,prior));
- mem(mergeAlias(prior,bodyFld._alias,esc));
- smem.unkeep();
- return self.unkeep();
+ /**
+ * 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();
}
+ private static final Ary<Node> ALTMP = new Ary<>(Node.class);
+ private Node newArray(TypeStruct ary, Node len) {
+ ConFldOffNode base = off(ary, "[]");
+ int scale= ary.aryScale();
+ Node size = peep(new AddNode(base,peep(new ShlNode(null,len.keep(),con(scale)))));
+ ALTMP.clear(); ALTMP.add(len.unkeep()); ALTMP.add(con(ary._fields[1]._t.makeZero()));
+ return newStruct(ary,size,0,ALTMP);
+ }
private Node newString(String s) {
- 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());
+ 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();
// 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", slen, lenAlias, body, elemAlias, true),true);
+ TypeMemPtr str = TypeMemPtr.make((byte)2,TypeStruct.makeAry("[]u8", TypeInteger.constant(s.length()), lenAlias, con, 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 '#'
@@ -2235,35 +1552,15 @@ 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) {
- _lexer.skipWhiteSpace();
- Lexer site = loc();
String name;
if( match(".") ) name = requireId();
else if( match("#") ) name = "#";
else if( match("[") ) name = "[]";
- // 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 if( match("(") ) return parsePostfix(require(functionCall(expr,null),")"));
else return expr; // No postfix
- return parsePostfixName(expr,name,0,site);
- }
-
- private Node defaultSelf() {
- int selfx = _scope._kinds.at(_scope.enclosingFuncOrDecl())._lexSize;
- return _scope.in(_scope.update(_scope.var(selfx),null));
+ return parsePostfixName(expr,name);
}
/**
@@ -2276,127 +1573,137 @@ 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) )
+ return parseUnknownField(expr,name);
+
+ // 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(ptr2._obj); // The declaration fixes the field shape; retain this view's access.
+ TypeStruct base = ptr._obj;
+ int fidx = base.find(name);
+ if( fidx==-1 && base._open ) return parseUnknownField(expr,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) {
- return parsePostfixName(expr,name,pre,loc());
- }
-
- private Node parsePostfixName(Node expr, String name, int pre, Lexer site) {
- // 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+"'");
-
- // We have to record "active touches" of a new class, to track when we
- // need to force class initialization. Basically, we insert an CLInit,
- // and if it does not fold away it'll turn into a runtime check.
- Kind.Func func = (Kind.Func)_scope._kinds.at(_scope.enclosingFuncOrDecl());
- if( (ts==null || ts==TypeStruct.BOT || ts.isHigh() || startsClzPrefix(ts._name)) &&
- !(ts!=null && (ts._name+".<clinit>").equals(func._name)) )
- classInit(site,expr);
-
- // 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=="[]"
- // 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();
+ 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();
// Disambiguate "obj.fld==x" boolean test from "obj.fld=x" field assignment
- if( pre==0 && matchOpx('=','=') ) {
- // Field assignment
+ if( matchOpx('=','=') ) {
Node val = parseAsgn().keep();
- // 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();
+ Node lift = liftExpr( val, tf, false );
+ 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 = peep(new LoadNode(loc(),name, alias, ctrl(), mem, expr, off));
+ Node load = f._one && INITS.containsKey( base._name )
+ ? INITS.get(base._name).in(base.find(name))
+ : new LoadNode(loc(),name, f._alias, 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);
// Check for assign-update, "ptr.fld += expr" or "ary[idx]++"
- char ch = pre==0 ? _lexer.matchOperAssign() : (char)pre;
+ char ch = _lexer.matchOperAssign();
if( ch!=0 ) {
- 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();
-
- 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;
+ 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!="[]" &&
+ _code.externFunc((TypeFunPtr)load._type)==null &&
+ ((TypeMemPtr)TYPES.get(base._name))._obj.field(name)._final &&
+ // And calling a function
+ match("(") ) {
+ return parsePostfix(require(functionCall(load,expr),")"));
+ }
+
+ // Drop expr, keeping load alive
+ load.keep();
+ expr.unkill();
+ load.unkeep();
}
- off.unkill();
- // Might be a method call, so pass expr as 'self'
- return parsePostfixMethod(load,expr.unkeep());
+
+ return parsePostfix(load);
}
+ // Syntax fixes the access even when a forward definition or a loop
+ // leaves the receiver untyped. Resolve its alias and layout in the graph.
+ private Node parseUnknownField(Node expr, String name) {
+ expr.keep();
+ Node off = new FldOffNode(expr,name).peephole();
+ if( name.equals("[]") ) {
+ Node index = require(parseAsgn(),"]");
+ Node scale = new FldOffNode(expr,"<<").peephole();
+ off = peep(new AddNode(off,peep(new ShlNode(loc(),index,scale))));
+ }
+ off.keep();
+ if( matchOpx('=','=') ) {
+ Node value = parseAsgn().keep();
+ Node st = new StoreNode(loc(),name,1,Type.BOTTOM,mem(),expr,off,value,false);
+ st.setDef(0,ctrl());
+ mem(st.peephole());
+ expr.unkill();
+ off.unkill();
+ return value.unkeep();
+ }
+ Node load = new LoadNode(loc(),name,1,mem(),expr,off);
+ load.setDef(0,ctrl());
+ load = peep(load);
+ char ch = _lexer.matchOperAssign();
+ if( ch!=0 ) {
+ Node op = opAssign(ch,load,Type.BOTTOM).keep();
+ Node st = new StoreNode(loc(),name,1,Type.BOTTOM,mem(),expr,off,op,false);
+ st.setDef(0,ctrl());
+ mem(st.peephole());
+ // The field width is not known yet. A compound assignment returns
+ // the stored value after conversion; postfix returns the old load.
+ // Reading our own Store folds once its alias and width are known.
+ if( postfix(ch) ) load.unkeep();
+ else {
+ load = new LoadNode(loc(),name,1,mem(),expr,off);
+ load.setDef(0,ctrl());
+ load = peep(load);
+ }
+ load.keep();
+ op.unkill();
+ } else load.keep();
+ expr.unkill();
+ off.unkill();
+ return parsePostfix(load.unkeep());
+ }
+
// zero/sign extend. "i" is limited to either classic unsigned (min==0) or
// classic signed (min=minus-power-of-2); max=power-of-2-minus-1.
private Node zsMask(Node val, Type t ) {
@@ -2430,9 +1737,15 @@ 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();
- Type self = implicitSelfType();
- ts.push(self);
- ids.push(self == TypePtr.PTR ? "#self" : "self");
+
+ // 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");
+ }
// Parse other arguments
_lexer.skipWhiteSpace();
@@ -2448,44 +1761,9 @@ public class Parser {
}
require("->");
// Make a concrete function type, with a fidx
- TypeFunPtr tfp = TypeFunPtr.make1((byte)2,true,ts.asAry(),Type.BOTTOM,_code.fidx(_ref._cname));
+ TypeFunPtr tfp = _code.makeFun(TypeFunPtr.make(false,false,ts.asAry(),Type.BOTTOM));
ReturnNode ret = parseFunctionBody(tfp,loc,finals,ids.asAry());
- 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();
+ return new FunPtrNode(ret._fun.sig(),ret).peephole();
}
/**
@@ -2495,17 +1773,17 @@ public class Parser {
* </pre>
*/
private Node functionCall(Node fcn, Node self) {
- Lexer site = loc();
- site._position--; // Opening '('; arguments can contain nested calls.
if( fcn._type == Type.NIL )
throw error("Calling a null function pointer");
+ if( !(fcn instanceof FRefNode) && fcn._type!=Type.BOTTOM && fcn._type!=TypeScalar.BOT && !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.keep());
+ args.push(self);
while( !peek(')') ) {
Node arg = parseAsgn();
if( arg==null ) break;
@@ -2514,19 +1792,11 @@ public class Parser {
}
// Control & memory after parsing args
args.set(0,ctrl().keep());
- args.set(1,mem().keep());
+ args.set(1,_scope.mem().keep());
args.push(fcn); // Function pointer
// Unkeep them all
for( Node arg : args )
arg.unkeep();
- return functionCall(args,site);
- }
-
- private Node functionCall( Ary<Node> args ) {
- return functionCall(args,loc());
- }
-
- private Node functionCall( Ary<Node> args, Lexer site ) {
// Dead into the call? Skip all the node gen
if( ctrl()._type == Type.XCONTROL ) {
for( Node arg : args )
@@ -2536,18 +1806,16 @@ public class Parser {
}
// Into the call
- CallNode call = (CallNode)new CallNode(site, args.asAry()).peephole();
+ CallNode call = (CallNode)new CallNode(loc(), args.asAry()).peephole();
// Post-call setup
- CallEndNode cend = (CallEndNode)new CallEndNode(call,TypeRPC.constant(_code.rpc(_ref._cname))).peephole();
+ CallEndNode cend = (CallEndNode)new CallEndNode(call).peephole();
call.peephole(); // Rerun peeps after CallEnd, allows early inlining
- cend = (CallEndNode)cend.keep().peephole(); // TODO: Might inline
// Control from CallEnd
ctrl(new CProjNode(cend,0,ScopeNode.CTRL).peephole());
- // Memory from CallEnd
- Node mem = new ProjNode(cend,1,ScopeNode.MEM0).peephole();
- mem(mem);
+ // A call may update any alias, so its memory result is the new whole state.
+ _scope.mem(new ProjNode(cend,1,ScopeNode.MEM0).peephole());
// Call result
- return new ProjNode(cend.unkeep(),2,"#2").peephole();
+ return new ProjNode(cend,2,"#2").peephole();
}
// Just after parsing "type foo = " can parse `"C"`
@@ -2559,27 +1827,10 @@ public class Parser {
return false;
}
// External linked constant
- 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));
+ 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();
}
/**
@@ -2587,36 +1838,13 @@ public class Parser {
*
* <pre>
* integerLiteral: [1-9][0-9]* | [0]
- * floatLiteral: [digits].[digits]?[e [digits]]?[f]?
+ * floatLiteral: [digits].[digits]?[e [digits]]?
* </pre>
*/
private ConstantNode parseLiteral() { return con(_lexer.parseNumber()); }
- ConstantNode con( long con ) { return _code.con(con); }
- ConstantNode con( Type t ) { return _code.con(t ); }
- private void classInit(Lexer site, Node ptr) {
- // Ordinary load machinery resolves the flag alias and offset when
- // the receiver is still a forward reference.
- Field flag = ptr._type instanceof TypeMemPtr tmp ? tmp._obj.field("$init") : null;
- int alias = flag==null ? 1 : flag._alias;
- Node ready = new LoadNode(site,"$init",alias,null,mem(),ptr,fldoff(ptr,"$init")).peephole();
- CLInitNode init = new CLInitNode(site,ctrl(),mem(),ptr,ready).peephole().keep();
- ctrl(new CProjNode(init,0,ScopeNode.CTRL).peephole());
- Node bulk = new ProjNode(init,1,ScopeNode.MEM0).peephole().keep();
- flag = init.flag();
- mem(flag==null ? bulk : mergeAlias(bulk,flag._alias,
- new ProjNode(init,2,memName(flag._alias)).peephole()));
- bulk.unkeep();
- init.unkeep();
- }
-
- // Field offset for a known type (but unknown offset)
- public static ConFldOffNode off( TypeStruct ts, String fname ) {
- return (ConFldOffNode)(new ConFldOffNode(ts,fname).peephole());
- }
- // Field offset for an unknown type
- public Node fldoff( Node n, String name ) {
- return new FldOffNode(n,name,loc()).peephole();
- }
+ 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()); }
public Node peep( Node n ) {
// Peephole, then improve with lexically scoped guards
return _scope.upcastGuard(n.peephole());
@@ -2666,14 +1894,6 @@ 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
@@ -2738,23 +1958,18 @@ public class Parser {
// Input buffer; an array of text bytes read from a file or a string
private final byte[] _input;
- public final CompUnit _cu; // Original source, retained across inlining.
// Tracks current position in input buffer
private int _position = 0;
+ public boolean before(Lexer that) {
+ return that!=null && _input==that._input && _position<that._position;
+ }
+
//
private int _line_number = 1;
// 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 && (_cu!=null ? _cu==that._cu : _input==that._input) && _position < that._position;
- }
-
- public String position() { return _line_number+":"+(_position-_line_start+1); }
- public int pos() { return _position; }
-
/**
* Record the source text for lexing
*/
@@ -2767,45 +1982,6 @@ public class Parser {
*/
public Lexer(byte[] buf) {
_input = buf;
- _cu = null;
- }
-
- private Lexer(CompUnit cu) {
- _input = cu._src.getBytes();
- _cu = cu;
- }
-
- // Imported location: source text is only needed when reporting an error.
- private Lexer(CompUnit cu, int pos) {
- _input = null;
- _cu = cu;
- _position = pos;
- }
-
- public static void gather(Lexer loc, HashMap<String,Integer> strs) {
- if( loc==null || loc._cu==null ) return;
- Serialize.gather(strs,loc._cu._cname);
- String file=loc._cu.sourceFile();
- if( file!=null ) Serialize.gather(strs,file);
- }
-
- public static void packed(Lexer loc, BAOS out, HashMap<String,Integer> strs) {
- if( loc==null || loc._cu==null ) { out.packed4(0); return; }
- out.packed4(loc._position+1);
- out.packed4(strs.get(loc._cu._cname));
- String file=loc._cu.sourceFile();
- out.packed4(file==null ? 0 : strs.get(file));
- }
-
- public static Lexer unpack(BAOS in, String[] strs) {
- int pos=in.packed4();
- if( pos==0 ) return null;
- String owner=strs[in.packed4()];
- String file=strs[in.packed4()];
- CompUnit cu=CodeGen.CODE._compunits.get(owner.replace('.','/'));
- assert cu!=null : owner;
- if( !file.isEmpty() ) cu._sourceFile=file;
- return new Lexer(cu,pos-1);
}
/**
@@ -2813,7 +1989,6 @@ public class Parser {
*/
private Lexer(Lexer l) {
_input = l._input;
- _cu = l._cu;
_position = l._position;
_line_number = l._line_number;
_line_start = l._line_start;
@@ -2822,7 +1997,7 @@ public class Parser {
// Very handy in the debugger, shows the unparsed program
@Override
public String toString() {
- return _input==null ? _cu._cname+"@"+_position : new String(_input, _position, _input.length - _position);
+ return new String(_input, _position, _input.length - _position);
}
// True if at EOF
@@ -2941,7 +2116,7 @@ public class Parser {
// Used for errors
String getAnyNextToken() {
- if (isEOF()) return "EOF";
+ if (isEOF()) return "";
if (isIdStart(peek())) return parseId();
if (isNumber(peek())) return parseNumberString();
if (isPunctuation(peek())) return parsePunctuation();
@@ -2955,26 +2130,17 @@ 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,n)));
+ return TypeFloat.constant(Double.parseDouble(new String(_input,old,-len)));
}
private String parseNumberString() {
int old = _position;
int len = Math.abs(isLongOrDouble());
- if( peek()=='f' ) {
- nextChar();
- len++;
- }
return new String(_input,old,len);
}
@@ -3004,7 +2170,7 @@ public class Parser {
private String parseId() {
int start = _position;
while (isIdLetter(nextChar())) ;
- return new String(_input, start, --_position - start).intern();
+ return new String(_input, start, --_position - start);
}
//
@@ -3039,9 +2205,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+" but found `" + _lexer.getAnyNextToken()+"`");
+ return error("Syntax error, "+msg+": " + _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/chapter25/src/main/java/com/seaofnodes/simple/Var.java b/chapter25/src/main/java/com/seaofnodes/simple/Var.java
index af371d21..e13b85c5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/Var.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/Var.java
@@ -1,7 +1,6 @@
package com.seaofnodes.simple;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.Utils;
/**
@@ -14,12 +13,9 @@ 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 ) {
@@ -33,8 +29,14 @@ public class Var {
_fref = fref;
_loc = loc;
}
- public Type type() { return _type; }
- public Type _type() { return _type; } // ScopeNode printer needs to have no side effects
+ // 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));
+ }
// Forward reference variables (not types) must be BOTTOM and
// distinct from inferred variables
@@ -49,6 +51,6 @@ public class Var {
}
@Override public String toString() {
- return (_fref ? "FREF " : "")+(_uninit ? "UNINIT " : "")+_type.toString()+(_final ? " ": " !")+_name;
+ return _type.toString()+(_final ? " ": " !")+_name;
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
index 0b314978..2319714b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/BuildLRG.java
@@ -15,16 +15,11 @@ 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) {
- FunNode fun = null;
- for( Node bb : alloc._code._cfg ) {
- if( bb instanceof FunNode fun2 )
- fun = fun2;
+ for( Node bb : alloc._code._cfg )
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);
@@ -33,8 +28,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,fun);
- if( phi instanceof MachNode ) defLRG(alloc,n,fun);
+ if( lrg==null ) lrg = alloc.newLRG(n);
+ if( phi instanceof MachNode ) defLRG(alloc,n);
// Pass 2: everybody uses the same LRG
lrg=alloc.union(lrg,phi);
for( int i=phi instanceof ParmNode ? 2 : 1; i<n.nIns(); i++ )
@@ -56,7 +51,7 @@ abstract public class BuildLRG {
}
// Define live range
- defLRG(alloc,n,fun);
+ defLRG(alloc,n);
// Now, look in the opposite direction. How are incoming
// LRGs affected by this node: For all uses, make live lrgs
@@ -76,9 +71,8 @@ abstract public class BuildLRG {
if( n instanceof MultiNode && !(n instanceof CFGNode) )
for( Node proj : n.outs() )
if( proj instanceof MachNode )
- defLRG(alloc,proj,fun);
+ defLRG(alloc,proj);
}
- }
// Collect live ranges
alloc.unify();
@@ -86,12 +80,12 @@ abstract public class BuildLRG {
return alloc.success();
}
- private static void defLRG( RegAlloc alloc, Node n, FunNode fun ) {
+ private static void defLRG( RegAlloc alloc, Node n ) {
MachNode mach = (MachNode)n;
RegMask def_mask = mach.outregmap();
if( def_mask == null ) return;
LRG lrg = mach.twoAddress() == 0
- ? alloc.newLRG(n,fun) // Define a new LRG for N
+ ? alloc.newLRG(n) // 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
deleted file mode 100644
index 6c70b71d..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ClassInitDependencies.java
+++ /dev/null
@@ -1,214 +0,0 @@
-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.*;
-
-// Search for Class Initialization Cycles.
-
-// Done after Opto, so dead code does not make a cycle.
-
-// Build the inits-graph, a call-graph plus CLInits plus class parents.
-
-// (1) Scan all functions f, building _edges[f][]: the set of <cinit> fcns from
-// CompUnits from CLInitNodes, and the set of fcn call targets in f and the
-// class parent. These are all reported as fidxs (ints).
-//
-// (2) Walk the _edges graph using Tarjans SCC. Reject cycles containing more
-// than one <clinit>.
-//
-// (3) Report on <clinit> cycles
-//
-public class ClassInitDependencies {
- private final CodeGen _code;
- // Function FIDX -> callees, touched initializers, and parent initializer.
- public final int[][] _edgess;
- private final int[] _pres;
- private final int[] _lows;
- private final int[] _stk ;
- private int _sp;
-
-
- // Build once; tests can inspect the graph independently of cycle checking.
- public ClassInitDependencies( CodeGen code ) {
- _code = code;
- assert code._phase.ordinal() >= CodeGen.Phase.IterPostOpto.ordinal();
- _pres = new int[code._linker._len];
- _lows = new int[code._linker._len];
- _stk = new int[code._linker._len];
-
- //
- BitSet visit = code.visit(); // Visit set per-fcn
- Ary<CFGNode> work = new Ary<>(CFGNode.class); // Worklist per-fcn
- IntHashMap iset = new IntHashMap(0); // dedup hash set per-fcn
- _edgess = new int[code._linker._len][];
-
- // For all functions, build the _edges array
- for( FunNode fun : code._linker )
- if( fun!=null && !fun.isDead() )
- // Visit function and collect direct class touches and callees
- _edgess[fun.sig().fidx()] = walk(code,fun,work,iset,visit,null);
- visit.clear();
- }
-
- // Return the outgoing init edge set for this function
- private static int[] walk( CodeGen code, FunNode fun, Ary<CFGNode> work, IntHashMap iset, BitSet visit,
- Ary<Parser.Lexer> sites) {
- // Walk function body, looking for outgoing init edges
- assert iset .isEmpty(); // int hash set for de-dup
- assert visit.isEmpty(); // needs a visit set, because loops
- assert work .isEmpty(); // needs a worklist, because DAG, parallel explore paths
-
- // Children classes always dependent on parents
- if( fun.isClz() && fun._compunit._par != null )
- iset.put(code.link(fun._compunit._par).sig().fidx(),1);
-
- work.push(fun);
- while( !work.isEmpty() ) {
- CFGNode cfg = work.pop();
- if( visit.get(cfg._nid) ) continue; // Been there, done that
- visit.set(cfg._nid);
- assert !cfg._type.isHigh();
- // Do not walk off end of function
- if( cfg instanceof ReturnNode ) continue;
-
- // CLInit adds a class init requirement to this function
- // (plus all parent classes)
- if( cfg instanceof CLInitNode init ) {
- FunNode clz = code.link(init.cu()); // <clinit> referred to
- iset.put(clz.sig().fidx(),1); // Add to edge set
- if( sites!=null && sites.atX(clz.sig().fidx())==null ) sites.setX(clz.sig().fidx(),init._loc);
- }
-
- // Calls union all their fptr fidxs into the callee set
- if( cfg instanceof CallNode call && call.fptr()._type instanceof TypeFunPtr fp ) {
- assert !XInt.isHigh(fp.fidxs()) && fp.nfcns()>0; // Call is alive with valid targets
- // Union call targets into the callee set
- for( int dstx = XInt.next(fp.fidxs(),0); dstx>=0; dstx=XInt.next(fp.fidxs(),dstx) ) {
- iset.put(dstx,1);
- if( sites!=null && sites.atX(dstx)==null ) sites.setX(dstx,call._loc);
- }
- }
-
- // Forward recursion to walk the whole function
- for( Node use : cfg._outputs ) {
- // Do not walk into another function
- if( !(use instanceof CFGNode cfg1) || use instanceof FunNode || use instanceof StopNode ) continue;
- // Expect control flow edge is sensible
- assert (use instanceof RegionNode r && r._inputs.find(cfg)>0) || use.in(0)==cfg;
- if( !visit.get(cfg1._nid) ) // Small speed opt
- work.add(cfg1);
- }
- }
-
- visit.clear();
- int[] edges = iset.isEmpty() ? null : iset.keySet();
- iset.clear();
- return edges;
- }
-
- // Return an error string, or null if no cycle contains multiple clinits.
- public String check() {
- Arrays.fill(_pres,0);
- _sp = 0;
- _pres[0] = 1; // Preorders stored in pres[0] and start at 1
-
- int idx = -1;
- for( int fidx = 1; fidx < _edgess.length; fidx++ )
- if( _pres[fidx] == 0 && // Already visited
- (idx = scc(fidx)) != -1 ) // And found a cycle
- return report(idx); // Report cycle
- return null;
- }
-
- // Every internal edge of an SCC belongs to a cycle. Print all of them:
- // stack order is not a path, and enumerating all cycle paths can explode.
- // This bookkeeping and formatting are only needed on an error.
- private String report(int idx) {
- BitSet members = new BitSet();
- for( int i=idx; i<_sp; i++ ) members.set(_stk[i]);
- SB sb = new SB().p("Cyclic class initialization:");
- // Recover one surviving source site per edge only after finding an
- // error. A representative touch is enough; runtime first-touch order
- // need not be statically known. Lexer snapshots retain the source CU
- // even when the node has been inlined into a different function.
- Ary<CFGNode> work = new Ary<>(CFGNode.class);
- IntHashMap iset = new IntHashMap(0);
- BitSet visit = new BitSet();
- Ary<Parser.Lexer> sites = new Ary<>(Parser.Lexer.class);
- for( int i=idx; i<_sp; i++ ) {
- int src = _stk[i];
- FunNode fun = _code.link(src);
- Arrays.fill(sites._es,null);
- sites.clear();
- walk(_code,fun,work,iset,visit,sites);
- if( _edgess[src]!=null )
- for( int dst : _edgess[src] )
- if( members.get(dst) ) {
- sb.p("\n ").p(name(src)).p(" -> ").p(name(dst));
- Parser.Lexer loc = sites.atX(dst);
- if( loc!=null && loc._cu!=null )
- sb.p('\n').p(SourceError.format(loc._cu,loc.pos(),"cyclic class initialization"));
- else if( fun.isClz() && fun._compunit._par==_code.link(dst)._compunit )
- sb.p("\n parent initialization requirement");
- }
- }
- return sb.toString();
- }
-
- private String name(int fidx) {
- FunNode fun = _code.link(fidx);
- String owner=fun._compunit._cname;
- if( fun.isClz() ) return owner;
- String label=fun.label();
- return (label.startsWith(owner+".") ? label : owner+"."+label)+" [fidx "+fidx+"]";
- }
-
- // Tarjan SCC
- int scc( int fidx ) {
- // Assign pre-order number
- _lows[fidx] = _pres[fidx] = _pres[0]++;
- _stk[_sp++] = fidx;
- // walk outbound edges
- if( _edgess[fidx]!=null ) {
- for( int edge : _edgess[fidx] ) {
- if( edge >= _pres.length ) continue; // Out of bounds happens when calling C fcns
- if( _pres[edge] == 0 ) { // Not visited
- int idx = scc(edge);
- if( idx != -1 ) return idx; // Found a cycle
- _lows[fidx] = Math.min(_lows[fidx],_lows[edge]);
- } else if( _edgess[edge]!=null && onStk(edge) ) {
- _lows[fidx] = Math.min(_lows[fidx],_pres[edge]);
- }
- }
- }
- // Found a cycle
- if( _lows[fidx] == _pres[fidx] ) {
- int idx = find(fidx);
- if( hasClzCycle(idx) )
- return idx; // Found a class cycle
- // Pop stack, including fidx
- _sp = idx;
- }
- return -1; // No cycle
- }
-
- private int find(int e) {
- for( int i=0; i<_sp; i++ ) if( _stk[i] == e ) return i;
- return -1;
- }
- private boolean onStk(int e) { return find(e) != -1; }
-
- private boolean hasClzCycle(int idx) {
- int clinits = 0;
- for( int i=idx; i<_sp; i++ ) {
- FunNode fun = _code.link(_stk[i]);
- if( fun!=null && fun.isClz() && ++clinits > 1 )
- return true;
- }
- return false;
- }
-
-}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
index 47ec0112..b79eb73d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java
@@ -1,340 +1,118 @@
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.*;
-import java.io.File;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.io.IOException;
import java.util.*;
-
-// Compile Driver for a Compilation Unit
@SuppressWarnings("unchecked")
public class CodeGen {
-
- // 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 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".
+ public static CodeGen CODE;
- // Compile phases
public enum Phase {
- Parse, // Parse ASCII text into Sea-of-Nodes IR
- Iter, // Pessimistic peepholes after parse
- Opto, // SCCP, numeric modes, seed the peephole worklist
+ Parse, // Build ideal IR from source
+ Iter, // Pessimistic peepholes
+ Opto, // Optimistic interprocedural SCCP
IterPostOpto, // Peepholes and inlining using SCCP results
- FinalizeIR, // Field layout, offset cleanup, function/CU roots
- TypeCheck, // Last check for bad programs
- Serialize, // Save ideal IR, including opaque class initialization
- Lower, // Expand checked class initialization to ordinary IR
- LoopTree, // Build a loop tree; break infinite loops
- 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 used in this compile; typically one per source/object
- // file, although you can nest structs and get more than one. These are all
- // required to compile, and are expected to have their source timestamps
- // ahead of their obj file. Some of these may entirely be dead or inlined
- // in this CompUnit and thus not needed in any linking stage. All the
- // alive ones (inlined away or not) participate in class init cycle checks.
- 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 class or instance field.
- public boolean publicFIDX(int fidx) {
- if( fidx < GlobalBits.RESERVED ) return false;
- // A public class initializer is itself an external root; unlike
- // 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;
- // Include instance methods even while their constant function fields
- // are stored in each instance rather than on the class object.
- for( Type t : Parser.TYPES.values() )
- if( t instanceof TypeStruct clz &&
- Parser.startsClzPrefix(clz._name) &&
- Parser.isPublicClassName(clz._name) &&
- resolvedClass(clz) ) {
- if( publicFieldFIDX(clz,fidx) ) return true;
- Type self = Parser.TYPES.get(clz._name.substring(Parser.CLZ.length()));
- if( self instanceof TypeStruct ts && publicFieldFIDX(ts,fidx) ) return true;
- }
- return false;
- }
-
- private static boolean publicFieldFIDX(TypeStruct clz, int fidx) {
- boolean hasCtor = clz.field("<ctor>") != null;
- for( Field fld : clz._fields )
- if( fld._fname.charAt(0)!='_' && fld._t instanceof TypeFunPtr tfp &&
- // 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; }
-
- // ---------------------------
-
- // Very common nodes, cached here
- public final ConstantNode ZERO;
- public final XCtrlNode XCTRL;
+ FinalizeIR, // Finish function cleanup and layouts
+ TypeCheck, // Check the surviving ideal graph
+ LoopTree, // Discover loops and connect nonreturning paths
+ Select, // Select machine instructions
+ Schedule, // Global code motion
+ LocalSched, // Order instructions within blocks
+ RegAlloc, // Assign registers and stack slots
+ Encoding, // Encode instructions
+ Export; // Link in memory or write ELF
- public Node constant( Type t ) {
- assert _phase==null || _phase.ordinal() <= Phase.Lower.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)); }
-
-
- // ---------------------------
- // 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);
- }
-
- // 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( 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;
- // 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);
- }
-
+ public Phase _phase;
- // Run requested phases. File export must request Serialize before Lower;
- // requesting Export up front does that automatically. Encoding alone does
- // not retain reloadable ideal IR.
-
- // No code emission, just IR generation
- public CodeGen driver( Phase phase ) { return driver(phase,null,null,false,false,0); }
- // 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.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); }
-
- // The Generic driver
- public CodeGen driver( Phase target, String cpu, String callingConv, boolean inMemory, boolean emitEntrySymbol, int dump ) {
- // Parse a Simple file, and for mentioned external symbols either load
- // from .o or recursively parse
- step(target, Phase.Parse, this::parse, dump);
- // Run peepholes to a fixed point
- step(target, Phase.Iter, this::iter, dump);
- // Run SCCP (and resolve int/flt expressions)
- step(target, Phase.Opto, this::opto, dump);
- // Run peepholes to a fixed point again, on the better types.
+ // Run through target, skipping phases already completed.
+ public CodeGen driver(Phase target) { return driver(target,null,null); }
+ public CodeGen driver(Phase target, String cpu, String callingConv) { return driver(target,cpu,callingConv,0); }
+ public CodeGen driver(Phase target, String cpu, String callingConv, int dump) {
+ if( target.ordinal()>=Phase.RegAlloc.ordinal() && cpu==null && _mach==null )
+ throw new IllegalArgumentException("Machine code requires a CPU target");
+ if( target==Phase.Export ) throw new IllegalArgumentException("Use exportELF with an output file or null for memory linking");
+ step(target, Phase.Parse, this::parse, dump);
+ step(target, Phase.Iter, this::iter, dump);
+ step(target, Phase.Opto, this::opto, dump);
step(target, Phase.IterPostOpto, this::iterPostOpto, dump);
- // Field layouts; field symbols become constant offsets
- step(target, Phase.FinalizeIR, this::finalizeIR, dump);
- // Final error check - bad programs fail here
- step(target, Phase.TypeCheck, this::typeCheck, dump);
- // Save reloadable ideal IR for a later seperate compilation
- step(target, Phase.Serialize, () -> Serialize.forTarget(this,target,inMemory), dump);
- // Replace "fat" nodes with their exploded explicit IR; this can
- // introduce CFG diamonds
- step(target, Phase.Lower, this::lower, dump);
- // Compute the loop-tree, used for approximating exec frequencies for
- // scheduling and register allocation
- step(target, Phase.LoopTree, this::loopTree, dump);
- // Drop imports for machine selection, retain them for ideal evaluation.
- step(target, Phase.Unlink, () -> unlink(cpu!=null), dump);
- // Instruction selection
+ step(target, Phase.FinalizeIR, this::finalizeIR, dump);
+ step(target, Phase.TypeCheck, this::typeCheck, dump);
+ step(target, Phase.LoopTree, this::loopTree, dump);
step(target, Phase.Select, () -> instSelect(cpu,callingConv), dump);
- // Global Code Motion
- step(target, Phase.Schedule, this::GCM, dump);
- // Local (basic block only) scheduling
- step(target, Phase.LocalSched, this::localSched, dump);
- // Register allocation
- step(target, Phase.RegAlloc, this::regAlloc, dump);
- // Encode machine instructions into bits
- step(target, Phase.Encoding, this::encode, dump);
- // Export bits (and serialized IR) into ELF files
- step(target, Phase.Export, () -> exportELF(inMemory,emitEntrySymbol), dump);
+ step(target, Phase.Schedule, this::GCM, dump);
+ step(target, Phase.LocalSched, this::localSched, dump);
+ step(target, Phase.RegAlloc, this::regAlloc, dump);
+ step(target, Phase.Encoding, this::encode, dump);
return this;
}
- // The driver names the order; optional phases decide locally whether to run.
private void step(Phase target, Phase next, Runnable work, int dump) {
- if( next.ordinal() > target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return;
+ if( next.ordinal()>target.ordinal() || (_phase!=null && _phase.ordinal()>=next.ordinal()) ) return;
Phase prior = _phase;
work.run();
- if( _phase!=prior ) dump(dump);
+ if( _phase!=prior && (dump & (1<<_phase.ordinal()))!=0 ) {
+ if( (dump & (1<<30))!=0 ) System.err.println("After "+_phase+":");
+ System.err.println(this);
+ }
}
- // All phase entry points share ordering checks, timing, and viewer notification.
- // Record elapsed time even on failure, but report completion only on success.
- CodeGen phase(Phase next, boolean ordered, Runnable work) {
+ public final long[] _times = new long[Phase.values().length];
+ private CodeGen phase(Phase next, boolean ordered, Runnable work) {
assert ordered : "Cannot run "+next+" after "+_phase;
_phase = next;
long t0 = System.currentTimeMillis();
- work.run(); // Do The Phase
- _times[next.ordinal()] = System.currentTimeMillis() - t0;
- if( _obs!=null ) _obs.phase(next.name()); // Record for viewer
+ try { work.run(); }
+ finally { _times[next.ordinal()] = System.currentTimeMillis()-t0; }
+ if( _obs!=null ) _obs.phase(next.name());
return this;
}
- public String entryClinitName() {
- return Parser.addClzPrefix(_srcName==null ? "Test" : _srcName)+".<clinit>";
- }
- // Verbose printing during compilation
- private void dump(int dump) {
- int p2 = _phase.ordinal();
- if( (dump & (1<<p2)) == 0 ) return;
- if( (dump & (1<<30)) != 0 )
- System.err.println("After "+_phase+":");
- System.err.println(_phase.ordinal() >= Phase.LocalSched.ordinal()
- ? asm(new SB()).toString()
- : IRPrinter.prettyPrint(this));
+ // ---------------------------
+ // Compilation source code
+ public final String _src;
+ // Compile-time known initial argument type
+ public final TypeInteger _arg;
+
+ // ---------------------------
+ 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 ); }
+
+ public CodeGen( String src, TypeInteger arg, long workListSeed, boolean reset ) {
+ CODE = this;
+ if( reset ) Type.reset();
+ _main = makeFun(TypeFunPtr.MAIN);
+ _phase = null;
+ _callingConv = null;
+ _start = new StartNode(arg);
+ _stop = new StopNode(src);
+ _src = src;
+ _arg = arg;
+ _iter = new IterPeeps(workListSeed);
+ P = new Parser(this,arg);
}
- // Record times by phase
- public final long[] _times = new long[Phase.LastPhase.ordinal()];
+ // 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);
+ }
+
// ---------------------------
/**
@@ -347,45 +125,9 @@ public class CodeGen {
return _uid++;
}
-
- // 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); }
+ // Next available memory alias number
+ private int _alias = 2; // 0 is for control, 1 for memory
+ public int getALIAS() { return _alias++; }
// Inlining can insert CFG edges and invalidate cached depth ordering.
@@ -400,35 +142,40 @@ 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;
+ public final HashMap<Node,Node> _gvn = new HashMap<>();
+ // Source of unique function indices
+ private int _fidx =0;
+ public TypeFunPtr makeFun( TypeFunPtr fun ) {
+ int fidx = _fidx++;
+ assert fidx<64; // TODO: need a larger FIDX space
+ return fun.makeFrom(fidx);
+ }
+ // Signature for MAIN
+ public TypeFunPtr _main;
+ public void setMain(FunNode main) {
+ _main = main.sig();
+ link(main);
+ }
// Reverse from a constant function pointer to the IR function being called.
// Error to call with a non-constant TFP
public FunNode link( TypeFunPtr tfp ) { return link(tfp.fidx()); }
- // Read-only linker lookup, suitable for printers and debugger display.
- // In particular, do not lazily remove a partially dead function.
- public FunNode _link(int fidx) { return _linker.atX(fidx); }
+ // Read-only diagnostic lookup; preserve stale entries.
public FunNode _link(TypeFunPtr tfp) { return _link(tfp.fidx()); }
- // Return the FunNode from a fidx
+ public FunNode _link(int fidx) { return _linker.atX(fidx); }
+ // Return the FunNode from a fidx, cleaning up dead entries.
public FunNode link( int fidx ) {
FunNode fun =_linker.atX(fidx);
if( fun!=null && fun.isDead() ) {
_linker.setX(fidx,null); fun = null; }
return fun;
}
- // Return the <clinit> for a CompUnit
- public FunNode link( CompUnit cu ) {
- for( FunNode fun : _linker )
- if( fun!=null && fun._compunit == cu && fun.isClz() )
- return fun;
- return null;
- }
// Insert linker mapping from constant function signature to the function
// being called.
@@ -440,33 +187,32 @@ 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).
- 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;
- }
+ final Ary<FunNode> _linker = new Ary<>(FunNode.class);
+ // Native symbols belong to function identities, not to one ExternNode.
+ // Selection and spill copies can turn that node into an ordinary constant.
+ private final HashMap<TypeFunPtr,String> _externs = new HashMap<>();
+ public void externFunc(TypeFunPtr tfp, String name) { _externs.put(tfp.makeFrom(Type.BOTTOM),name); }
+ public String externFunc(TypeFunPtr tfp) { return _externs.get(tfp.makeFrom(Type.BOTTOM)); }
- // ---------------------------
// Parser object
public final Parser P;
// Parse ASCII text into Sea-of-Nodes IR
- public CodeGen parse() { return phase(Phase.Parse, _phase==null, () -> ParseAll.parse(this,_src)); }
+ public CodeGen parse() {
+ return phase(Phase.Parse, _phase==null, () -> {
+ P.parse();
+ // Close the declarations as a group before replacing old forward
+ // shapes in the graph. A can mention B before B mentions A; both
+ // must use the same complete cycle, including through functions.
+ HashMap<String,Type> closed = new HashMap<>();
+ for( var entry : Parser.TYPES.entrySet() )
+ if( entry.getValue() instanceof TypeMemPtr ptr && !ptr._obj._open )
+ closed.put(entry.getKey(),ptr.makeFrom(ptr._obj.close(Parser.TYPES)));
+ Parser.TYPES.putAll(closed);
+ _start.walk(n -> { n.upgradeType(Parser.TYPES); return null; });
+ });
+ }
// ---------------------------
@@ -480,55 +226,28 @@ 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() { return phase(Phase.Iter, _phase==Phase.Parse, () -> Iter.run(this)); }
+ // Pessimistic peepholes reach a fixed point after parsing.
+ public CodeGen iter() { return phase(Phase.Iter, _phase==Phase.Parse, () -> _iter.iterate(this)); }
- // SCCP and numeric-mode resolution; leave cleanup work queued for Iter.
+ // SCCP computes types and queues changed nodes; Iter applies their rewrites.
public CodeGen opto() { return phase(Phase.Opto, _phase==Phase.Iter, () -> Opto.opto(this)); }
-
public CodeGen iterPostOpto() { return phase(Phase.IterPostOpto, _phase==Phase.Opto, () -> _iter.iterate(this)); }
public CodeGen finalizeIR() { return phase(Phase.FinalizeIR, _phase==Phase.IterPostOpto, () -> FinalizeIR.run(this)); }
-
- // Root a surviving function without claiming that it has unknown callers.
- void keepInCompUnit(FunNode fun) {
- StopCUNode stop = fun._compunit._stop;
- if( stop._inputs.find(fun.ret()) == -1 )
- add(stop).addDef(fun.ret());
- }
public <N extends Node> N add( N n ) { return _iter.add(n); }
public void addAll( Ary<Node> ary ) { _iter.addAll(ary); }
- public void addAll( Node n ) { _iter.add(n); _iter.addAll(n._inputs); }
// ---------------------------
// Last check for bad programs
public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); }
- public static TypeStruct requiredStruct(Node n) {
- // CNC: remove? cleanup?
- 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;
- }
-
- // ---------------------------
- // TypeCheck queued surviving CLInits during its existing error walk.
- public CodeGen lower() {
- return phase(Phase.Lower, (_phase==Phase.TypeCheck || _phase==Phase.Serialize), () -> _iter.iterate(this));
- }
-
// ---------------------------
// Build the loop tree; break never-exit loops
public CodeGen loopTree() {
- return phase(Phase.LoopTree, _phase==Phase.Lower, () -> _start.buildLoopTree(_linker,_stop));
- }
-
- // ---------------------------
- public BAOS _serial;
- public void serialize() {
- phase(Phase.Serialize, _phase==Phase.TypeCheck, () -> Serialize.serialize(this));
+ return phase(Phase.LoopTree, _phase==Phase.TypeCheck, () -> {
+ // Build the loop tree, fix never-exit loops
+ _start.buildLoopTree(_start,_stop);
+ });
}
// ---------------------------
@@ -536,10 +255,8 @@ public class CodeGen {
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
@@ -554,16 +271,11 @@ public class CodeGen {
// Convert to target hardware nodes
public CodeGen instSelect( String cpu, String callingConv ) { return instSelect(cpu,callingConv,PORTS); }
public CodeGen instSelect( String cpu, String callingConv, String base ) {
- return InstSelect.select(this,cpu,callingConv,base);
+ if( cpu==null ) return this;
+ return phase(Phase.Select, _phase==Phase.LoopTree,
+ () -> new InstSelect(this).run(cpu,callingConv,base));
}
- // ---------------------------
- public void unlink() {
- unlink(true);
- }
- private void unlink(boolean removeImports) {
- phase(Phase.Unlink, _phase==Phase.LoopTree, () -> Unlink.run(this,removeImports));
- }
// ---------------------------
// Control Flow Graph in Reverse Post Order.
@@ -571,12 +283,17 @@ public class CodeGen {
// Global schedule (code motion) nodes
public CodeGen GCM() {
- return phase(Phase.Schedule, (_phase==Phase.Unlink || _phase==Phase.Select), () -> GlobalCodeMotion.buildCFG(this));
+ return phase(Phase.Schedule, (_phase==Phase.LoopTree || _phase==Phase.Select), () -> {
+ GlobalCodeMotion.buildCFG(this);
+
+ });
}
// ---------------------------
// Local (basic block) scheduler phase, a classic list scheduler
- public CodeGen localSched() { return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this)); }
+ public CodeGen localSched() {
+ return phase(Phase.LocalSched, _phase==Phase.Schedule, () -> ListScheduler.sched(this));
+ }
// ---------------------------
@@ -589,10 +306,11 @@ public class CodeGen {
});
}
- // Human-readable register name
- public String reg(Node n) {
+ // Human readable register name
+ public String reg(Node n) { return reg(n,null); }
+ public String reg(Node n, FunNode fun) {
if( _phase.ordinal() >= Phase.RegAlloc.ordinal() ) {
- String s = _regAlloc.reg(n);
+ String s = _regAlloc.reg(n,fun);
if( s!=null ) return s;
}
return "N"+ n._nid;
@@ -601,154 +319,50 @@ public class CodeGen {
// ---------------------------
// Encoding
- public Encoding _encoding;
- public CodeGen encode() { return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> _encoding = new Encoding(this).encode()); }
-
- // ---------------------------
- // Exporting to external formats
- ElfWriter _elf;
- public CodeGen exportELF( boolean inMemory, boolean emitEntrySymbol ) {
- return phase(Phase.Export, _phase==Phase.Encoding, () -> {
- if( inMemory ) new LinkMem(this).link(_encoding);
- else _elf = new ElfWriter(this).export(emitEntrySymbol);
+ public Encoding _encoding; // Encoding object
+ public void preEncode() { } // overridden by alternative ports
+ public CodeGen encode() {
+ return phase(Phase.Encoding, _phase==Phase.RegAlloc, () -> {
+ _encoding = new Encoding(this);
+ preEncode();
+ _encoding.encode();
});
}
-
// ---------------------------
-
- // 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);
-
- // Publish definitions only; referenced classes belong to other objects.
- for( String fname : elf._units )
- _externSymbols.putIfAbsent(Parser.addClzPrefix(fname.replace('/','.')),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;
+ // Exporting to external formats
+ public CodeGen exportELF(String fname) throws IOException {
+ assert _phase==Phase.Encoding;
+ _phase=Phase.Export;
+ long t0=System.currentTimeMillis();
+ try {
+ if( fname == null ) new LinkMem(this).link(); // In memory patching
+ else new ElfFile(this).export(fname); // External ELF file
+ } finally { _times[Phase.Export.ordinal()]=System.currentTimeMillis()-t0; }
+ if( _obs!=null ) _obs.phase(Phase.Export.name());
+ return this;
}
// ---------------------------
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 compunit()._stop.ret().ctrl(); }
- public Node expr() { return compunit()._stop.ret().expr(); }
- public String print() { return compunit()._stop.print(); }
+ public Node ctrl() { return _stop.ret().ctrl(); }
+ public Node expr() { return _stop.ret().expr(); }
+ public String print() { return _stop.print(); }
// Debugging helper
@Override public String toString() {
- if( _stop == null )
- return "No StopNode";
- return IRPrinter.prettyPrint(this);
+ return _phase.ordinal() > Phase.Schedule.ordinal()
+ ? IRPrinter._prettyPrint( this )
+ : IRPrinter.prettyPrint(this);
}
// Debugging helper
- 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;
- }
+ public Node f(int idx) { return _stop.find(idx); }
String printCFG() {
@@ -767,4 +381,28 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
deleted file mode 100644
index 128c4136..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/CompUnit.java
+++ /dev/null
@@ -1,143 +0,0 @@
-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
- // Original source path from an imported diagnostic location. Does not
- // affect discovery/rebuild decisions, which use _smp and _src.
- public String _sourceFile;
-
- public String sourceFile() {
- return _smp!=null ? _smp.getAbsolutePath() : _sourceFile;
- }
-
- public TypeStruct _clz; // The one TypeStruct being published
-
- // _deps records units required for types, code, or class references,
- // without requiring their initializers to run. These discovery/rebuild
- // dependencies may form a cycle.
- Ary<CompUnit> _deps;
-
- // 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
new file mode 100644
index 00000000..9d9b8a1c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfExpr.java
@@ -0,0 +1,87 @@
+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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
new file mode 100644
index 00000000..b6267600
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfFile.java
@@ -0,0 +1,382 @@
+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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
deleted file mode 100644
index 10720300..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfReader.java
+++ /dev/null
@@ -1,236 +0,0 @@
-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;
- // Compilation units defined by this object, independent of init analysis.
- public String[] _units;
- // 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
deleted file mode 100644
index 5a280723..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ElfWriter.java
+++ /dev/null
@@ -1,467 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.TypeFunPtr;
-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_LOCAL ? _contents : _globals;
- 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_LOCAL ? -99 : gidx;
- }
-
- 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_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 final int SECTION_HDR_SIZE = 64;
-
- // ------------------------------------------------------------------------
- public class SimpleSection extends DataSection {
- SimpleSection(BAOS serial) { super(".simple", SHT_SIMPLE, serial, 0); }
- }
-
-
- // ------------------------------------------------------------------------
-
- // Native code can live above 4G, outside our four-byte function pointers.
- // Give address-taken natives a low entry which tail-jumps through the PLT.
- // A weak, stable name makes references from separate objects agree on one
- // entry. Do not patch references straight to the local copy: that would
- // bypass the linker's choice and break pointer equality across objects.
- private int nativeEntry(String name, HashMap<String,Integer> entries,
- DataSection text, SymbolSection symbols, ReloSection relos) {
- Integer old = entries.get(name);
- if( old!=null ) return old;
- int offset = text.size();
- text._contents.write(0xE9); // jmp rel32; preserve arguments and return address
- text.write4(0);
- int target = symbols.symbol(name,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
- relos.writeReloOffPlus(offset+1,target,(byte)4/*PLT32*/);
- int entry = symbols.symbol("__simple_native$"+name,text._index,
- SYM_BIND_WEAK,SYM_TYPE_FUNC,offset,5);
- entries.put(name,entry);
- return entry;
- }
-
- public ElfWriter export(boolean emitEntrySymbol) {
- if( _code._serial==null )
- throw new IllegalStateException("ELF export needs ideal IR: request Export from the driver, or Serialize before Lower");
- // Sections are created in the order they are emitted.
- _sections = new Ary<>(Section.class);
-
- // 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;
- // Append ELF-only thunks to a copy. Export must not change instruction
- // offsets or the image used by the in-memory linker and emulators.
- BAOS textBits = new BAOS(enc._bits.toByteArray(),enc._bits.size());
- DataSection text = new DataSection(".text" , Section.SHT_PROGBITS, textBits, 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);
- HashMap<String,Integer> nativeEntries = new HashMap<>();
-
- for( Node n : enc._externals.keySet()) {
- String extern = enc._externals.get(n);
- boolean nativeAddress = n instanceof FunPtrNode &&
- _code.externFunc(((TypeFunPtr)n._type).fidx())!=null;
- int symidx = nativeAddress
- ? nativeEntry(extern,nativeEntries,text,symbols,relocations)
- : symbols.symbol(extern,0,SYM_BIND_GLOBAL,SYM_TYPE_FUNC);
- int offset = enc.opStart(n) + enc.opLen(n) - 4;
- // Address materialization and static fields name the same thunk;
- // direct calls still go straight to the native symbol's PLT entry.
- relocations.writeReloOffPlus(offset,symidx,(byte)(nativeAddress ? 2/*PC32*/ : 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());
- String nativeName=relo.value() instanceof TypeFunPtr fp ? _code.externFunc(fp.fidx()) : null;
- Integer sym=nativeName==null ? dataSymbols.get(name)
- : nativeEntry(nativeName,nativeEntries,text,symbols,relocations);
- 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
index 06bf14f5..019e3892 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Encoding.java
@@ -1,10 +1,12 @@
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.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.io.ByteArrayOutputStream;
import java.util.*;
@@ -22,14 +24,11 @@ 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
- // chunk relative offsets; no relocation info is required.
+ // check 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.
@@ -37,12 +36,17 @@ 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 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<>();
+ 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;
// Big Constant relocation info.
public static class Relo {
@@ -52,28 +56,17 @@ 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;
- _data = new StaticData(this);
- }
+ Encoding( CodeGen code ) { _code = code; }
// Shortcut to the defining register
public short reg(Node n) {
@@ -114,6 +107,14 @@ 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);
@@ -126,95 +127,52 @@ 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 ) {
- FunNode fun = _code.link(call.tfp());
- if( fun != null && !_code.owns(fun) )
- _externals.put(call,fun._name);
- else
- _internals.put(call,fun);
+ _internals.put(call,_code.link(call.tfp()));
return this;
}
- // Code address as a constant
- public Encoding relo( FunPtrNode fptr ) {
- TypeFunPtr tfp = (TypeFunPtr)fptr._type;
- // Native functions have no local FunNode, but are still callable.
- // Do not treat their addresses as identities of deleted functions.
- String nativeName = _code.externFunc(tfp.fidx());
- if( nativeName!=null ) return external(fptr,nativeName);
- FunNode fun = _code.link(tfp);
- if( fun != null && !_code.owns(fun) )
- _externals.put(fptr,fun._name);
- else
- _internals.put(fptr,fun);
+ public Encoding relo( ConstantNode con ) {
+ TypeFunPtr tfp = (TypeFunPtr)con._con;
+ // A native address has no local FunNode. Keep its symbol even after
+ // selection or a spill copy has replaced the original ExternNode.
+ String name = _code.externFunc(tfp);
+ if( name!=null ) return external(con,name);
+ _internals.put(con,_code.link(tfp));
return this;
}
- // Local jump
public void jump( CFGNode jmp, CFGNode dst ) {
_internals.put(jmp,dst.uctrlSkipEmpty());
}
- // External references only located by Strings a link-time
- public final HashMap<Node,String> _externals = new HashMap<>();
+
+ 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. Type is stored in either the .rodata or .data.
+ // addressing to load it
public void largeConstant( Node relo, Type t, int off, int elf ) {
- // TODO: Any-old struct in the cpool
- assert t.isConstant() || (t instanceof TypeStruct ts && Parser.startsClzPrefix(ts._name));
+ assert t.isConstant();
assert (byte)off == off;
assert (byte)elf == elf;
_bigCons.put(relo,new Relo(relo,t,(byte)off,(byte)elf));
}
// --------------------------------------------------
- Encoding encode( ) {
+ void 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.
@@ -224,13 +182,8 @@ 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;
}
// --------------------------------------------------
@@ -238,48 +191,29 @@ 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<>();
- _cfg = new Ary<>(CFGNode.class);
- rpos.put(_code._start.loop(),_cfg);
+ Ary<CFGNode> rpo = new Ary<>(CFGNode.class);
+ rpos.put(_code._start.loop(),rpo);
BitSet visit = _code.visit();
-
- // 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;
- }
+ 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;
}
- }
- // 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;
- }
-
+ // 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);
// Reverse in-place
- for( int i=0; i< _cfg.size()>>1; i++ )
- _cfg.swap(i,_cfg.size()-1-i);
+ for( int i=0; i< rpo.size()>>1; i++ )
+ rpo.swap(i,rpo.size()-1-i);
visit.clear();
+ _code._cfg = rpo; // Save the new ordering
}
@@ -353,7 +287,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, then `fld < bld` must be false, or else both directions
+ // bld` is true, the `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
@@ -398,26 +332,24 @@ public class Encoding implements com.seaofnodes.isa.CodeSink {
// --------------------------------------------------
// Write encoding bits in order into a big byte array.
// Record opcode start and length.
-
- // Current function is used by the spill-op encodings to query the stack
- // frame layout and get the spill offsets.
+ public FunNode _fun; // Currently encoding function
private void writeEncodings() {
- for( CFGNode bb : _cfg ) {
+ _opStart= new int [_code.UID()];
+ _opLen = new byte[_code.UID()];
+ for( CFGNode bb : _code._cfg ) {
if( !(bb instanceof MachNode mach0) )
- opStart(bb, _bits.size());
- else if( bb instanceof FunNode ) {
- opStart(bb, _bits.size());
+ _opStart[bb._nid] = _bits.size();
+ else if( bb instanceof FunNode fun ) {
+ _fun = fun; // Currently encoding function
+ _opStart[bb._nid] = _bits.size();
mach0.encoding( this );
- opLen(bb, (byte) (_bits.size() - opStart(bb)));
+ _opLen[bb._nid] = (byte) (_bits.size() - _opStart[bb._nid]);
}
for( Node n : bb._outputs ) {
- 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());
+ if( n instanceof MachNode mach && !(n instanceof FunNode) ) {
+ _opStart[n._nid] = _bits.size();
mach.encoding( this );
- opLen(n, (byte) (_bits.size() - opStart(n)));
+ _opLen[n._nid] = (byte) (_bits.size() - _opStart[n._nid]);
}
}
}
@@ -427,10 +359,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 = _cfg._len;
+ int len = _code._cfg._len;
int[] oldStarts = new int[len];
for( int i=0; i<len; i++ )
- oldStarts[i] = opStart(_cfg.at(i));
+ oldStarts[i] = _opStart[_code._cfg.at(i)._nid];
// TODO: Rewrite this algo to use the small "_jmps" list of just the
// jumps instead of walking all blocks.
@@ -444,27 +376,24 @@ 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 = _cfg.at(i);
- opStartAdd(bb, slide);
+ CFGNode bb = _code._cfg.at(i);
+ _opStart[bb._nid] += slide;
// Slide down all other (non-CFG) ops in the block
for( Node n : bb._outputs )
- 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( n instanceof MachNode && !(n instanceof CFGNode) )
+ _opStart[n._nid] += 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) - opStart(bb);
+ int delta = _opStart[target._nid] - _opStart[bb._nid];
byte opLen = riprel.encSize(delta);
// Recorded size is smaller than the current size?
- if( opLen(bb) < opLen ) {
+ if( _opLen[bb._nid] < opLen ) {
// Start sliding the code down; record slide amount and new size
- slide += opLen - opLen(bb);
- opLen(bb, opLen);
+ slide += opLen - _opLen[bb._nid];
+ _opLen[bb._nid] = opLen;
}
}
}
@@ -475,21 +404,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 = _cfg.at(i);
+ CFGNode bb = _code._cfg.at(i);
// Functions pad to align 16
if( bb instanceof FunNode ) {
- int newStart = opStart(bb)+slide;
+ int newStart = _opStart[bb._nid]+slide;
slide += (newStart+15 & -16)-newStart;
}
- opStartAdd(bb, slide);
+ _opStart[bb._nid] += slide;
for( Node n : bb._outputs )
- if( n instanceof MachNode && !(n instanceof CFGNode) &&
- !(n instanceof FunPtrNode && bb instanceof ReturnNode) )
- opStartAdd(n, slide);
+ if( n instanceof MachNode && !(n instanceof CFGNode) )
+ _opStart[n._nid] += slide;
}
+ _padFunHeads = true;
// Copy/slide the bits to make space for all the longer branches
- int grow = opStart(_cfg.at(len-1)) - oldStarts[len-1];
+ int grow = _opStart[_code._cfg.at(len-1)._nid] - 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];
@@ -497,7 +426,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(_cfg.at(i));
+ int newStart = _opStart[_code._cfg.at(i)._nid];
System.arraycopy(_bits.buf(),oldStart,bits,newStart,end-start);
end = start;
}
@@ -512,59 +441,103 @@ 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);
+ int start = _opStart[src._nid];
Node dst = _internals.get(src);
- // 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);
+ int target = _opStart[dst._nid];
+ ((RIPRelSize)src).patch(this, start, _opLen[src._nid], target - start);
}
}
- // 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);
+ // --------------------------------------------------
+ // 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;
+ }
+ }
}
+
// 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);
+ int start = _opStart[src._nid];
String dst = _externals.get(src);
- int target = externalAddress(dst);
- ((RIPRelSize)src).patch(this, start, opLen(src), target - start);
+ 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);
}
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
index 923486c6..9e3b3e32 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/FinalizeIR.java
@@ -1,44 +1,76 @@
package com.seaofnodes.simple.codegen;
+import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.type.*;
import java.util.Arrays;
-// Comnpute field layouts only after SCCP and its peepholes converge.
-// Keep surviving functions rooted without introducing unknown callers.
+// Settle layouts and detach the analyzed call graph before the backend.
abstract class FinalizeIR {
static void run(CodeGen code) {
- // Compute field layouts and replace symbolic field offsets by constants.
+ fieldSizes(code);
+ code._iter.iterate(code);
+ removeCallGraph(code);
+ Arrays.sort(code._stop._inputs._es,0,code._stop.nIns(),(x,y) -> x._nid - y._nid);
+ }
+
+ // Freeze field sizes; do struct layouts; convert field offsets into
+ // constants.
+ private static void fieldSizes( CodeGen code ) {
for( int i=0; i<code._start.nOuts(); i++ ) {
Node use = code._start.out(i);
if( use instanceof ConFldOffNode off ) {
- Node con = off.asOffset();
- assert con != null : off._ts._name+"."+off._fname;
- off.subsume(con);
+ TypeMemPtr tmp = (TypeMemPtr) Parser.TYPES.get(off._name);
+ off.subsume( off.asOffset(tmp._obj) );
i--;
}
}
- code._iter.iterate(code); // Propagate field offsets and clean up.
-
- // Retain surviving functions for emission without adding unknown callers.
- for( FunNode fun : code._linker )
- if( fun != null ) {
- // A folded static function field can leave a detached body
- // with internal cycles. It has no callers; later phases must
- // not revive it merely because its nodes are still allocated.
- if( fun.isDead() || fun._type.isHigh() ) {
- code._linker.set(fun.sig().fidx(),null);
- continue;
+ }
+
+ // 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();
}
- code.keepInCompUnit(fun);
- }
- for( CompUnit cu : code._compunits.values() ) {
- ProjNode mem = cu._start.proj(1);
- if( mem != null ) code.add(mem.unkeep());
+ 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());
}
- // Stable print order: initializers first, then other Returns by NID.
- for( Node stop : code._stop._inputs )
- Arrays.sort(stop._inputs._es,0,stop.nIns(),
- (x,y) -> ((((ReturnNode)x).fun().isClz() ? -1 : x._nid) -
- (((ReturnNode)y).fun().isClz() ? -1 : y._nid) ));
+ fun.ret().setDef(3,rpc.init());
+ return rpc;
}
+
+
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
deleted file mode 100644
index bc9c9d39..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalBits.java
+++ /dev/null
@@ -1,177 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
index 09eb8d33..da45db4f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/GlobalCodeMotion.java
@@ -27,15 +27,16 @@ 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
- assert !( def instanceof ReturnNode && use instanceof CallEndNode ); // All calls unwired now
- assert !( def instanceof CallNode && use instanceof FunNode ); // All calls unwired now
+ if( def instanceof ReturnNode && use instanceof CallEndNode )
+ return;
+ 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);
@@ -49,7 +50,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.
}
@@ -60,7 +61,6 @@ 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._mach==null || CodeGen.CODE.owns(fun); // Ideal evaluation includes imported bodies.
var local = copies.computeIfAbsent(fun,f -> new IdentityHashMap<>());
Node copy = cloneGlobal(con,fun,globals,local);
for( int i=0; i<use.nIns(); i++ )
@@ -142,9 +142,8 @@ public abstract class GlobalCodeMotion {
// Record Load NIDs at all their CFG block choices, then check against
// Store block choices to force a Load above an anti-dependent Store.
int[] anti = new int[code.UID()];
- Ary<Node> antideps = new Ary<>(Node.class);
// Breadth-first scheduling
- breadth(code._stop,ns,late,anti, antideps);
+ breadth(code._stop,ns,late,anti);
// Copy the best placement choice into the control slot
for( int i=0; i<late.length; i++ )
@@ -152,7 +151,7 @@ public abstract class GlobalCodeMotion {
ns[i].setDef(0,late[i]);
}
- private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ private static void breadth(Node stop, Node[] ns, CFGNode[] late, int[] anti) {
// Things on the worklist have some (but perhaps not all) uses done.
WorkList<Node> work = new WorkList<>();
work.push(stop);
@@ -173,12 +172,12 @@ public abstract class GlobalCodeMotion {
// Loads need their memory inputs' uses also done
if( n instanceof MemOpNode ld && ld._isLoad )
- for( Node memuse : ld.antiDeps(antideps) )
+ for( Node memuse : ld.antiDeps() )
if( late[memuse._nid]==null )
continue outer;
// All uses done, schedule
- _doSchedLate(n,ns,late,anti,antideps);
+ _doSchedLate(n,ns,late,anti);
}
// A use just finished; reconsider its inputs and waiting loads,
@@ -195,7 +194,7 @@ public abstract class GlobalCodeMotion {
}
}
- private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ private static void _doSchedLate(Node n, Node[] ns, CFGNode[] late, int[] anti) {
// Walk uses, gathering the LCA (Least Common Ancestor) of uses
CFGNode early = n.in(0) instanceof CFGNode cfg ? cfg : n.in(0).cfg0();
assert early != null;
@@ -206,7 +205,8 @@ public abstract class GlobalCodeMotion {
// Loads may need anti-dependencies, raising their LCA
if( n instanceof MemOpNode load && load._isLoad )
- lca = find_anti_dep(lca,load,early,late,anti, antideps);
+ 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
@@ -224,8 +224,7 @@ public abstract class GlobalCodeMotion {
// Block of use. Normally from late[] schedule, except for Phis, which go
// to the matching Region input.
private static CFGNode use_block(Node n, Node use, CFGNode[] late) {
- if( use instanceof ParmNode parm )
- return late[use._nid];
+ if( use instanceof ParmNode ) return late[use._nid];
if( !(use instanceof PhiNode phi) )
return late[use._nid];
CFGNode found=null;
@@ -246,7 +245,7 @@ public abstract class GlobalCodeMotion {
best instanceof IfNode;
}
- // A writer may consume a load's memory through an aggregate.
+ // An allocation may consume a load's memory through a partial aggregate.
private static void wakeLoads(Node def, CFGNode[] late, WorkList<Node> work, BitSet visit) {
if( visit.get(def._nid) ) return;
visit.set(def._nid);
@@ -257,26 +256,35 @@ public abstract class GlobalCodeMotion {
if( def.in(i)!=null ) wakeLoads(def.in(i),late,work,visit);
}
- private static CFGNode find_anti_dep(CFGNode lca, MemOpNode load, CFGNode early, CFGNode[] late, int[] anti, Ary<Node> antideps) {
+ 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; cfg!=early.idom(); cfg = cfg.idom() )
+ for( CFGNode cfg=lca; early!=null && 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(antideps) ) {
- if( mem instanceof PhiNode phi ) {
+ for( Node mem : load.antiDeps() ) {
+ switch( mem ) {
+ case MemOpNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
+ break;
+ case CallNode st:
+ assert late[st._nid]!=null;
+ lca = anti_dep(load,late[st._nid],st.cfg0(),lca,st,anti);
+ break;
+ case PhiNode phi:
// Inputs carrying different memory might overwrite this alias.
- // Raise the LCA for those paths; the unchanged-memory paths
- // do not clobber the load. No scheduling edge to the Phi.
+ // Raise the LCA for those paths; unchanged memory does not
+ // clobber the load. No scheduling edge to the Phi.
for( int i=1; i<phi.nIns(); i++ )
if( phi.in(i)!=load.mem() )
lca = anti_dep(load,phi.region().cfg(i),load.mem().cfg0(),lca,null,anti);
- } else {
- // Hunt for clobbering stores
- assert mem instanceof MemOpNode || mem instanceof EscapeNode ||
- mem instanceof CallNode || mem instanceof CLInitNode;
- assert late[mem._nid]!=null;
- lca = anti_dep(load,late[mem._nid],mem.cfg0(),lca,mem,anti);
+ 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();
}
}
return lca;
@@ -286,15 +294,14 @@ public abstract class GlobalCodeMotion {
private static CFGNode anti_dep( MemOpNode load, CFGNode stblk, CFGNode defblk, CFGNode lca, Node st, int[] anti ) {
// A conditional writer can precede the load without dominating its late block.
// Walk back to the first overlap with the load's placement range.
- // A different Phi input need not be dominated by this memory's
- // definition. Stop at its depth rather than looking for that ancestor.
- // Retain the sentinel too: a function entry has a null idom.
+ // Another Phi input need not be dominated by this memory definition.
+ // Include its depth, and retain the sentinel for an entry's null idom.
for( ; stblk != defblk.idom() && stblk.idepth() >= defblk.idepth(); stblk = stblk.idom() ) {
// Store and Load overlap, need anti-dependence
if( anti[stblk._nid]==load._nid ) {
lca = stblk.domLCA(lca,null); // Raise Loads LCA
if( lca == stblk && st != null && !(st instanceof CallNode) && st._inputs.find(load) == -1 ) // And if something moved,
- st.addDef(load); // Add anti-dep as well (calls already end their block)
+ st.addDef(load); // Add anti-dep as well; Calls already end their block.
return lca; // Cap this stores' anti-dep to here
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
index bcdb14bf..bcd1957d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/IFG.java
+++ b/chapter25/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,13 +208,7 @@ abstract public class IFG {
assert tlrg.leader();
// Always, tlrg cannot use kills
if( tlrg._mask.overlap(killMask) ) {
- 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);
+ if( !tlrg.sub(killMask) ) alloc.fail(tlrg);
}
}
}
@@ -322,7 +316,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,alloc);
+ pickColor(color_stack,sptr,swork);
// Pick a trivial lrg, and (temporarily) remove from the IFG.
LRG lrg = color_stack[sptr++];
@@ -378,10 +372,10 @@ abstract public class IFG {
}
// Pick LRG from color stack
- private static void pickColor(LRG[] color_stack, int sptr, int swork, RegAlloc alloc) {
+ private static void pickColor(LRG[] color_stack, int sptr, int swork) {
// Out of trivial colorable, pick an at-risk to pull
if( sptr==swork )
- swap(color_stack,sptr,pickRisky(color_stack,sptr,alloc));
+ swap(color_stack,sptr,pickRisky(color_stack,sptr));
// 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
@@ -398,7 +392,6 @@ 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() )
@@ -406,22 +399,18 @@ 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, RegAlloc alloc ) {
+ private static int pickRisky( LRG[] color_stack, int sptr ) {
int best=sptr;
- int bestScore = pickRiskyScore(color_stack[best],alloc);
+ int bestScore = pickRiskyScore(color_stack[best]);
for( int i=sptr+1; i<color_stack.length; i++ ) {
if( bestScore == 1000000 ) return best; // Already max score
- int iScore = pickRiskyScore(color_stack[i],alloc);
+ int iScore = pickRiskyScore(color_stack[i]);
if( iScore > bestScore )
{ best = i; bestScore = iScore; }
}
@@ -436,60 +425,30 @@ 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, 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));
-
+ private static int pickRiskyScore( LRG lrg ) {
// Pick single-def clonables that are not right next to their single-use.
- // Rank by the area recovered by cloning closer to the use.
+ // Failing to color these will clone them closer to their uses.
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();
- 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 )
+ 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) )
return 1000000;
- if( area > 0 )
- return areaScore(area,loopCost(use));
}
- // 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));
- }
+ // 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;
- private static int loopCost(Node n) {
- return 1 << (n.cfg0().loopDepth()*3);
+ // TODO: cost/benefit model. Perhaps counting loop-depth (freq) of def/use for cost
+ // and "area" for benefit
+ return 1000;
}
private static short biasColor( RegAlloc alloc, LRG lrg, short reg, RegMask mask ) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
index 5fc002a1..5a91cf9f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/InstSelect.java
@@ -1,43 +1,30 @@
package com.seaofnodes.simple.codegen;
import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
import java.util.BitSet;
import java.util.IdentityHashMap;
-// Replace ideal nodes with machine nodes while preserving CFG/loop metadata.
-// Selection also owns target ABI masks and rebuilding the copied graph's uses.
+// Replace ideal nodes with machine nodes, then rebuild the copied graph's uses.
class InstSelect {
private final CodeGen _code;
- private InstSelect(CodeGen code) { _code=code; }
+ InstSelect(CodeGen code) { _code=code; }
- static CodeGen select(CodeGen code, String cpu, String callingConv, String base) {
- // Ideal evaluation schedules the ideal graph without a CPU port.
- if( cpu==null ) return code;
- return code.phase(CodeGen.Phase.Select, code._phase==CodeGen.Phase.Unlink,
- () -> new InstSelect(code).run(cpu,callingConv,base));
- }
-
- private void run(String cpu, String callingConv, String base) {
+ @SuppressWarnings("unchecked")
+ void run(String cpu, String callingConv, String base) {
_code._callingConv = callingConv;
// Look for CPU in fixed named place:
// com.seaofnodes.simple.node.cpus."cpu"."cpu.class"
String clzFile = base+"."+cpu+"."+cpu;
- try { _code._mach = Class.forName(clzFile).asSubclass(Machine.class).getDeclaredConstructor(CodeGen.class).newInstance(_code); }
+ try { _code._mach = ((Class<Machine>) Class.forName( clzFile )).getDeclaredConstructor(new Class[]{CodeGen.class}).newInstance(_code); }
catch( Exception e ) { throw new RuntimeException(e); }
- _code._cCallingConv = _code._mach.cCallingConv(callingConv);
-
// Build global copies of common register masks.
long callerSave = _code._mach.callerSave();
- long cCallerSave= _code._mach.callerSave(_code._cCallingConv);
long neverSave = _code._mach. neverSave();
int maxReg = Math.min(64,_code._mach.regs().length);
assert maxReg>=64 || (-1L << maxReg & callerSave)==0; // No stack slots in callerSave
- assert maxReg>=64 || (-1L << maxReg & cCallerSave)==0; // No stack slots in cCallerSave
_code._callerSave = new RegMask(callerSave);
- _code._cCallerSave= new RegMask(cCallerSave);
// Build a Return RegMask array. All returns have the following inputs:
// 0 - ctrl
@@ -56,18 +43,11 @@ class InstSelect {
_code._uid = 1; // All new machine nodes reset numbering
var map = new IdentityHashMap<Node,Node>();
_instSelect( _code._stop, map );
- _code._stop = (StopNode)map.get(_code._stop );
+ _code._stop = ( StopNode)map.get(_code._stop );
StartNode start = (StartNode)map.get(_code._start);
_code._start = start==null ? new StartNode(_code._start) : start;
_instOuts(_code._stop,_code.visit());
_code._visit.clear();
-
- // Replace the CompUnit stop (and list of functions)
- // with hardware-specific ones
- for( CompUnit cu : _code._compunits.values() ) {
- cu._start= (StartCUNode)map.get(cu._start);
- cu._stop = (StopCUNode )map.get(cu._stop );
- }
}
private Node _instSelect( Node n, IdentityHashMap<Node,Node> map ) {
@@ -85,28 +65,14 @@ class InstSelect {
}
// Produce a machine node from n; map it to flag as done so stops cycles.
- if( n instanceof MemOpNode mem && mem.off() instanceof ExternOffsetNode off ) {
- // External fields replace base+offset with symbol+0. Keep the ideal
- // declaring type on the address for machine load/store width selection.
- TypeMemPtr ptr = (TypeMemPtr)mem.ptr()._type;
- n._inputs.set(2,new ExternNode(TypeMemPtr.make((byte)2,ptr._obj,true),off._extern));
- n._inputs.set(3,ConstantNode.raw(TypeInteger.ZERO));
- }
- map.put(n, x=_code._mach.instSelect(n) );
- // Carry loop-tree and pre-order info across the ideal->mach transition
- if( n instanceof CFGNode ncfg && x instanceof CFGNode xcfg ) {
- xcfg._ltree = ncfg._ltree;
- xcfg._pre = ncfg._pre;
- }
+ map.put(n, x=select(n) );
+ // Stop retains the callable body. Its lifetime edge is not a machine operand.
+ if( n instanceof FunPtrNode ) x._inputs.pop();
// Walk machine op and replace inputs with mapped inputs
for( int i=0; i < x.nIns(); i++ )
x._inputs.set(i, _instSelect(x.in(i),map) );
- // Post selection action
- if( x instanceof MachNode mach ) {
- if( n instanceof ReturnNode ret )
- ((ReturnNode)mach)._fun = (FunNode)map.get(ret._fun);
+ if( x instanceof MachNode mach )
mach.postSelect(_code); // Post selection action
- }
return x;
}
@@ -117,10 +83,29 @@ class InstSelect {
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);
}
}
+
+
+ private Node select(Node n) {
+ if( n instanceof GuardNode guard ) return new GuardMach(guard);
+ if( n instanceof PtrToIntNode ptr ) return new PtrToIntMach(ptr);
+ Node op = n;
+ if( n instanceof ModeNode mode && mode.mode()==2 ) {
+ // Adapt to the existing CPU selectors. As with machine expansion,
+ // copy inputs without adding uses to the old graph; _instOuts
+ // rebuilds reverse edges after selection has finished.
+ op = switch(n) {
+ case AddNode add -> new AddFNode(add);
+ case SubNode sub -> new SubFNode(sub);
+ case MulNode mul -> new MulFNode(mul);
+ case DivNode div -> new DivFNode(div);
+ case MinusNode neg -> new MinusFNode(neg);
+ default -> n;
+ };
+ op._type = n._type;
+ }
+ return _code._mach.instSelect(op);
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java
deleted file mode 100644
index c5120370..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Iter.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-import com.seaofnodes.simple.type.*;
-
-// Establish a pessimistic fixed point after parsing/loading. Later Iter runs
-// reuse the worklist and types produced by the preceding phase.
-abstract class Iter {
- static void run(CodeGen code) {
- // The phase shift closes all parser-in-progress states and freezes
- // unknown external inputs. Imported nodes carry post-Opto escape
- // precision, but Iter is a fresh pessimistic solve: memory escape
- // summaries must restart at FULL so newly discovered private escapes
- // can only be removed, never added. Seed the complete graph once.
- boolean hasImports = code._externPaths != null;
- code._start.walk(n -> {
- if( !(n instanceof ConstantNode) )
- n._type = hasImports ? iterImportReset(n._type) : iterEscapeReset(n._type);
- code.add(n);
- return null;
- });
- code._iter.iterate(code);
- // Expensive assert.
- assert !CodeGen.expensiveAssert() || Opto.fixedPointCheck(code);
- }
-
- private static Type iterEscapeReset(Type t) {
- if( t instanceof TypeMem mem )
- return mem._one ? mem : mem.makeFrom(XInt.FULL,XInt.FULL);
- if( t instanceof TypeTuple tt ) {
- TypeTuple rez = tt;
- for( int i=0; i<tt._types.length; i++ ) {
- Type tx = iterEscapeReset(tt._types[i]);
- if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
- }
- return rez;
- }
- return t;
- }
-
- private static Type iterImportReset(Type t) {
- if( t.isHigh() )
- return t.dual();
- if( t instanceof TypeMem mem )
- return TypeMem.make(mem._alias,iterImportReset(mem._t),mem._one,mem._clz,mem._final,XInt.FULL,XInt.FULL);
- if( t instanceof TypeMemPtr )
- return t.makeStorage();
- if( t instanceof TypeStruct )
- return t.makeStorage();
- if( t instanceof TypeTuple tt ) {
- TypeTuple rez = tt;
- for( int i=0; i<tt._types.length; i++ ) {
- Type tx = iterImportReset(tt._types[i]);
- if( tx != tt._types[i] ) rez = rez.makeFrom(i,tx);
- }
- return rez;
- }
- return t;
- }
-}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
index fdde8838..0c36ae2b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LRG.java
@@ -56,10 +56,6 @@ 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; }
public RegMask useMask() { return _machUse==null ? null : _machUse.regmap(_uidx); }
@@ -84,7 +80,7 @@ public class LRG {
// More registers than neighbors
boolean lowDegree() { return nadj() < _mask.size(); }
- LRG( short lrg, FunNode fun ) { _lrg = lrg; _reg = -1; _fun = fun; }
+ LRG( short lrg ) { _lrg = lrg; _reg = -1; }
boolean leader() { return _leader == null; }
@@ -120,7 +116,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
index 252b5bf0..2415203d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/LinkMem.java
@@ -1,8 +1,6 @@
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
@@ -10,24 +8,16 @@ public class LinkMem {
final CodeGen _code;
LinkMem( CodeGen code ) { _code = code; }
- public CodeGen link(Encoding enc) {
+ public CodeGen link() {
+ Encoding enc = _code._encoding;
+
// Patch external calls internally
enc.patchGlobalRelocations();
- // 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);
- }
+ // Write any large constants into a constant pool; they
+ // are accessed by RIP-relative addressing.
+ enc.writeConstantPool(enc._bits,true);
- enc._data.link(cpool,sdata);
return _code;
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
index b6b3a22c..ddf5a679 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ListScheduler.java
+++ b/chapter25/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 CFGNode) ? n.in(0) : n); }
+ static XSched get(Node n) { return n==null ? null : XS.get(n instanceof ProjNode && !(n.in(0) instanceof CallEndNode) ? n.in(0) : n); }
private XSched init(CFGNode bb, Node n) {
_n = n;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
index 3eee8fc3..7ca465ba 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Machine.java
@@ -7,42 +7,28 @@ 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.
- // 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 );
-
+ // Create a split op; any register to any register, including stack slots
+ public abstract SplitNode split( String kind, byte round, LRG lrg);
// 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 );
@@ -108,17 +94,13 @@ 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()._type instanceof TypeFunPtr tfp && tfp.isConstant() &&
- CodeGen.CODE.externFunc(tfp.fidx())!=null &&
+ cend.call().fptr()._type instanceof TypeFunPtr tfp &&
+ CodeGen.CODE.externFunc(tfp)!=null &&
tfp.ret() instanceof TypeInteger ret && ret.log_size()<3 )
return ret;
return null;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java
deleted file mode 100644
index a5d85dbc..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Module.java
+++ /dev/null
@@ -1,98 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
index ef222f60..c78e732e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Opto.java
@@ -1,9 +1,9 @@
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 java.util.BitSet;
// Optimistic Algorithm
@@ -24,25 +24,41 @@ abstract public class Opto {
// get a fairly precise set of Call Graph edges between CallNodes and
// FunNodes.
public static void opto(CodeGen code) {
+
// Reset Types before running the worklist algorithm
Ary<Type> oldTypes = new Ary<>(Type.class); // Used for asserts only
resetToTop(code,oldTypes);
- unlinkStart(code);
+
+ // 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);
+
// 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);
+ 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);
+
+
}
// Reset Types before running the worklist algorithm
@@ -51,6 +67,9 @@ 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;
} );
}
@@ -59,57 +78,44 @@ 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(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();
- }
-
- // 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. Initializers,
- // including private/imported ones, are called by opaque CLInit
- // obligations whose Call edges are introduced only in Lower.
- if( !fun.isClz() &&
- !code.publicFIDX(fun.sig().fidx()) &&
- fun.in(1) instanceof StartNode ) {
- fun.removeDeadPath(1);
- // 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--);
+ 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);
}
}
}
+ // 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;
+ }
+
// 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
- 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);
+ for( long fidxs=tfp.fidxs(); fidxs!=0; fidxs=TypeFunPtr.nextFIDX(fidxs) ) {
+ int fidx = Long.numberOfTrailingZeros(fidxs);
+ 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) ) {
@@ -120,48 +126,6 @@ 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();
- code.keepInCompUnit(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);
- }
- }
- }
-
-
// The core SCCP algorithm
private static void sccp( CodeGen code, Ary<Type> oldTypes ) {
// Iter workList is empty at this point
@@ -173,85 +137,47 @@ abstract public class Opto {
count++;
Type oval = n._type, nval = n.compute();
if( oval == nval ) continue;
- 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
+ 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
n._type = nval;
- // 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 a TFP adds a new function input to a call, link to the new Fun
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. Like above, adds new Call
- // Graph edges to the graph.
- if( n instanceof CallNode call && nval == Type.CONTROL && call.fptr()._type instanceof TypeFunPtr )
+ // Link a Call which becomes alive
+ if( n instanceof CallNode call && oval.isHigh() && !nval.isHigh() && call.fptr()._type instanceof TypeFunPtr )
linkCG(code,call.tfp(),call);
- // If an otherwise-dead function pointer escapes, any future linked
- // caller might find and call it. Force the function to be alive
- // and called by Start.
- if( n instanceof StartNode )
- 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 !CodeGen.expensiveAssert(count) || worklistCheck(code);
+ // Quadratic (expensive) small-step assert
+ assert check(code);
}
}
- // 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 -> {
+ // Quadratic (expensive) small-step assert
+ private static boolean check(CodeGen code) {
+ code._start.walk( x -> {
Type xval = x.compute();
- assert xval == x._type;
+ assert xval == x._type || (x._type.isa( xval ) && code._iter._work.on( x ));
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.add(x);
+ code._iter.add(x);
return null;
});
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
deleted file mode 100644
index 7ce727fd..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java
+++ /dev/null
@@ -1,411 +0,0 @@
-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 {
-
- static void parse(CodeGen code, String src) {
- Parser.TYPES.clear();
- Parser.TYPES.putAll(Parser.INIT_TYPES);
- if( src != null ) {
- // No source file, just the source itself.
- parseSource(code,code._srcName==null ? "Test" : code._srcName,src);
- } else {
- // Path from module root to source file
- parsePath(code,code._srcName);
- }
- }
-
- // Worklist of symbols to search and files to parse
- private static final Ary<CompUnit> WORK = new Ary<>(CompUnit.class);
-
- 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);
-
- // 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);
- // All imported bodies are loaded; link them before Iter checks types.
- if( n instanceof CallNode call && !NEEDS_LOAD.isEmpty() )
- code.add(call).idealize();
- 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);
- else
- assert false : "expected to find required type";
- } 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?
- for( CompUnit loaded : NEEDS_LOAD )
- if( loaded._obj.equals(cunit._obj) ) return; // Another class in the same object.
- try {
- NEEDS_LOAD.add(cunit);
- // Load dependent classes
- ElfReader elf = ElfReader.load(cunit._obj,cunit);
- elf.loadPublicSymbols();
- // Register bundled definitions before resolving dependencies or IR.
- for( String fname : elf._units )
- makeCUnit(code,null,fname,cunit._obj);
- for( String fname : elf._deps ) {
- // Check CompUnit for being up to date
- CompUnit sub = code._compunits.get(fname);
- if( sub==null ) sub=makeCUnit(code,null,fname,depObj(code,cunit,fname));
- // If not seen before or out of date, parse the new nested cunit
- if( sub._clz == null && WORK.find(sub)==-1 && NEEDS_LOAD.find(sub)==-1 )
- 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);
- elf.loadSimple(code);
- cunit._clz = (TypeStruct)Parser.TYPES.get(Parser.addClzPrefix(cunit._cname));
- 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 && inst._t instanceof TypeFunPtr tfp && tfp._ret instanceof TypeMem mem )
- Parser.TYPES.put(cunit._cname,mem._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);
- if( typedElf != null ) typedElf.loadPublicTypes(code);
- TypeStruct 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
index 48319006..777182dc 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RIPRelSize.java
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
index ac792988..565fa6ba 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegAlloc.java
@@ -57,8 +57,6 @@ 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<>();
@@ -70,7 +68,6 @@ public class RegAlloc {
_failed.put(lrg,"");
}
boolean success() { return _failed.isEmpty(); }
- public boolean done() { return _done; }
// -----------------------
@@ -79,16 +76,16 @@ public class RegAlloc {
short _lrg_num;
// Define a new LRG, and assign n
- LRG newLRG( Node n, FunNode fun ) {
+ LRG newLRG( Node n ) {
LRG lrg = lrg(n);
if( lrg!=null ) return lrg;
- lrg = new LRG(_lrg_num++, fun);
+ lrg = new LRG(_lrg_num++);
LRG old = _lrgs.put(n,lrg); assert old==null;
return lrg;
}
// LRG for n
- public LRG lrg( Node n ) {
+ LRG lrg( Node n ) {
LRG lrg = _lrgs.get(n);
if( lrg==null ) return null;
LRG lrg2 = lrg.find();
@@ -145,19 +142,18 @@ public class RegAlloc {
}
// Printable register number for node n
- String reg( Node n ) {
+ String reg( Node n ) { return reg(n,null); }
+ String reg( Node n, FunNode fun ) {
LRG lrg = _lrg(n);
- return lrg==null ? null : reg(lrg);
- }
- String reg( LRG lrg ) {
+ if( lrg==null ) return null;
// 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() || lrg._fun==null )
+ if( lrg._reg < regs.length || _code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() || fun==null )
return RegMask.reg(regs,lrg._reg);
// Stack-slot past RA uses the frame layout logic
- return "[rsp+"+lrg._fun.computeStackOffset(_code,lrg._reg)+"]";
+ return "[rsp+"+fun.computeStackOffset(_code,lrg._reg)+"]";
}
// -----------------------
@@ -183,7 +179,8 @@ public class RegAlloc {
for( Node n : bb._outputs )
if( n instanceof NewNode nnn ) nnn.cacheRegs(_code);
}
- // Expensive assert: useful when chasing cross-function allocator edges.
+
+ // Optional diagnostic for values accidentally shared across functions.
//assert verifyFunctionLocalEdges();
// Top driver: repeated rounds of coloring and splitting.
@@ -195,64 +192,21 @@ public class RegAlloc {
round++;
}
postColor(); // Remove no-op spills
- _done = true;
}
boolean verifyFunctionLocalEdges() {
- 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(")");
+ 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;
+ }
}
- 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();
+ return true;
}
private boolean graphColor(byte round) {
@@ -298,7 +252,7 @@ public class RegAlloc {
// Register mask when empty; split around defs and uses with limited
// register masks.
- if( lrg._mask.isEmpty() ) {
+ if( lrg._mask.isEmpty() && (!lrg._multiDef || lrg._1regUseCnt==1) ) {
if( lrg._1regDefCnt <= 1 &&
lrg._1regUseCnt <= 1 &&
(lrg._1regDefCnt + lrg._1regUseCnt) > 0 &&
@@ -311,10 +265,6 @@ 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);
@@ -327,20 +277,18 @@ public class RegAlloc {
// single-register. Split after the def and before the use. Does not
// require a full pass.
- // Compute def nOuts *before* replacing those uses with a split
- int defNouts = ((Node)lrg._machDef).nOuts();
// Split just after def
if( lrg._1regDefCnt==1 && !lrg._machDef.isClone() )
- // Force must-split, even if a prior split same block because register
- // conflicts. Example:
+ // An earlier copy is reusable only if no intervening clobber.
+ // Being in the same block alone is insufficient. 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/*true*/);
+ insertAfterAndReplace(makeSplit("def/empty1",round,lrg),(Node)lrg._machDef,false);
// Split just before use
- if( lrg._machUse!=null && (lrg._1regUseCnt==1 || (lrg._1regDefCnt==1 && defNouts==1)) )
+ if( lrg._1regUseCnt==1 )
insertBefore((Node)lrg._machUse,lrg._uidx,"use/empty1",round,lrg);
return true;
}
@@ -383,27 +331,6 @@ 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 ) {
@@ -512,11 +439,9 @@ public class RegAlloc {
int min = (int)ld;
int max = (int)(ld>>32);
- 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.
+
+ // If the minLoopDepth is less than the maxLoopDepth: for-all defs and
+ // uses, if at minLoopDepth or lower, split after def and before use.
for( Node n : _ns ) {
if( n instanceof SplitNode split && !split.isNonStackSplit() && min!=max ) continue; // Ordinary splits already reach stack storage.
if( n.isDead() ) continue; // Some Cloneable went dead by other spill changes
@@ -527,7 +452,7 @@ public class RegAlloc {
if( lrg(n)==lrg && // This is a LRG def
// At loop boundary, or splitting in inner loop
- (min==max || (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) ) {
+ (min==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
@@ -542,7 +467,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 || (emptyMulti ? phi.region().cfg(i).loopDepth() >= max : phi.region().cfg(i).loopDepth() <= min)) &&
+ (min==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
@@ -554,8 +479,7 @@ 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()) ||
- (emptyMulti ? n.cfg0().loopDepth() >= max : n.cfg0().loopDepth() <= min)) )
+ (min == max || (n.in(i) instanceof MachNode mach && mach.isClone()) || n.cfg0().loopDepth() <= min) )
// Split before in this block
insertBefore( n, i, "use/loop/use", round,lrg, false );
}
@@ -604,8 +528,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;
@@ -666,12 +590,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(lrg,kind,round);
+ : _code._mach.split(kind,round,lrg);
_lrgs.put(split,lrg);
return split;
}
private SplitNode makeSplit( String kind, byte round, LRG lrg ) {
- SplitNode split = _code._mach.split(lrg,kind,round);
+ SplitNode split = _code._mach.split(kind,round,lrg);
_lrgs.put(split,lrg);
return split;
}
@@ -775,6 +699,6 @@ public class RegAlloc {
if( clobbers(n,defreg) )
return false; // Clobbered
}
- throw Utils.TODO("Should not reach here");
+ throw Utils.TODO();
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
index 9cb9ec16..303bba9e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/RegMask.java
@@ -33,6 +33,7 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
deleted file mode 100644
index 6a23252d..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Serialize.java
+++ /dev/null
@@ -1,672 +0,0 @@
-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 {
-
- // Encoding/emulation need only machine code. File export also needs ideal
- // IR for a later compiler to import; an explicit Serialize request stops here.
- static void forTarget(CodeGen code, CodeGen.Phase target, boolean inMemory) {
- if( target==CodeGen.Phase.Serialize || (target.ordinal()>=CodeGen.Phase.Export.ordinal() && !inMemory) )
- code.serialize();
- }
-
- static void serialize( CodeGen code ) {
-
- // Get all Nodes in a sane order
- 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()]);
- Arrays.sort(deps);
- Ary<String> owned = new Ary<>(String.class);
- for( CompUnit cu : code._compunits.values() )
- if( cu._src!=null ) owned.add(cu._fname);
- String[] units = owned.asAry();
- Arrays.sort(units);
-
- // 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, units, 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,elf._units, 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, String[] units, GlobalBits aliases, GlobalBits fidxs, GlobalBits rpcs, HashMap<Integer,String> externFunc) {
- // Initialize the mapping from bits/tags to types
- Type.TAGOFFS();
-
- IdentityHashMap<Node,Integer> anodes = new IdentityHashMap<>();
- for( Node n : nodes ) anodes.put(n,anodes.size()+1); // 0 is null.
- // The saved wrappers summarize this object's units only. Keep the
- // live whole-program graph intact for subsequent lowering.
- TypeTuple stopType = TypeTuple.STOP_HIGH;
- for( Node n : nodes.last()._inputs )
- if( anodes.containsKey(n) ) stopType = (TypeTuple)stopType.meet(n._type);
- Type startType = TypeTuple.make(Type.CONTROL,stopType.mem(),((StartNode)nodes.at(0)).arg());
-
- BAOS baos = new BAOS();
- // A - Print the C0DE header. The format is not versioned yet.
- baos.write('C').write('0').write('D').write('E');
-
- // 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);
- }
- stopType.gather(types);
- startType.gather(types);
- // A defined class remains importable even when its initializer body dies.
- for( TypeStruct ts : pubTypes )
- if( Parser.startsClzPrefix(ts._name) &&
- Arrays.binarySearch(units,ts._name.substring(Parser.CLZ.length()).replace('.','/'))>=0 )
- ts.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);
- for( String fname : units ) 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);
-
- // Defined units are loader metadata, not initialization dependencies.
- baos.packed4(units.length);
- for( String fname : units ) baos.packed4(strs.get(fname));
-
- // 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);
-
- // 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==nodes.at(0) ? startType : n==nodes.last() ? stopType : n._type));
- n.packed(baos,strs,types,anodes);
- }
- // Write out the input indices packed.
- for( Node n : nodes ) {
- for( int j=0; j<n.nIns(); j++ ) {
- if( !writeInput(n,j,anodes) ) continue;
- Node def = n.in(j);
- Integer idx = def==null ? Integer.valueOf(0) : anodes.get(def);
- // Imported function constants retain their symbol, not a body.
- if( idx==null && n instanceof FunPtrNode && j==1 ) idx=0;
- assert idx != null : "Missing serialized input for "+n+" input "+j+" def "+def;
- baos.packed2(idx);
- }
- if( n instanceof FunNode fun )
- baos.packed2(anodes.get(fun.ret()));
- }
-
- return baos;
- }
-
- // Omit cross-object call-graph links without unlinking the live IR.
- // A parameter follows exactly the same retained callers as its function.
- private static boolean writeInput(Node n, int i, IdentityHashMap<Node,Integer> nodes) {
- if( n instanceof ParmNode parm ) n=parm.fun();
- if( (n instanceof FunNode || n instanceof CallEndNode) && i>0 )
- return nodes.containsKey(n.in(i));
- if( n instanceof StopNode ) return nodes.containsKey(n.in(i));
- return true;
- }
-
- public static int nIns(Node n, IdentityHashMap<Node,Integer> nodes) {
- int len=0;
- for( int i=0; i<n.nIns(); i++ ) if( writeInput(n,i,nodes) ) len++;
- return len;
- }
- public static <T> void gather( HashMap<T,Integer> strs, T s) {
- if( s!=null && !strs.containsKey(s) )
- strs.put(s,strs.size());
- }
- 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];
- elf._units = new String[bais.packed4()];
- for( int i=0; i<elf._units.length; i++ )
- elf._units[i] = strs[bais.packed4()];
- }
-
- // --------------------------------------------------
- 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];
- elf._units = new String[bais.packed4()];
- for( int i=0; i<elf._units.length; i++ )
- elf._units[i] = strs[bais.packed4()];
- }
-
- // 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;
- // Cross-object calls are relinked only after every body has
- // loaded. Their saved return types cannot be recomputed yet.
- if( n instanceof CallEndNode end && end.call().fptr()._type instanceof TypeFunPtr fp &&
- end.nIns()<=fp.nfcns() ) 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 ) {
- 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 ) {
- 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._src!=null && cu._start != null && !cu._start.isDead() ) {
- nodes.add(cu._start);
- // A memory-free, nonreturning unit can lose its memory projection.
- ProjNode mem=cu._start.proj(1);
- if( mem!=null ) nodes.add(mem);
- // 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);
- // A nonreturning function still owns a Return, even before LoopTree
- // inserts synthetic exits. No loop-depth ordering is needed on disk.
- _funRPO(fun.ret(),rpo,visit);
- int len = rpo._len - start;
- for( int i=0; i< len>>1; i++ )
- rpo.swap(start+i,start+len-1-i);
- }
-
- // 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( n instanceof IfNode iff ) {
- CProjNode c0 = iff.cproj(0);
- CProjNode c1 = iff.cproj(1);
- _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/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
deleted file mode 100644
index f2f46146..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/SourceError.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.util.SB;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/** Format one parser byte offset. Source is read and text built only on error. */
-public abstract class SourceError {
- public static String format(CompUnit cu, int pos, String message) {
- String file=cu.sourceFile();
- String name=file==null ? cu._fname+".smp" : file;
- byte[] source;
- try {
- source=cu._src!=null ? cu._src.getBytes(StandardCharsets.UTF_8)
- : file==null ? null : Files.readAllBytes(Path.of(file));
- } catch( IOException ex ) { source=null; }
- if( source==null || pos<0 || pos>source.length )
- return name+": error: "+message+" (source unavailable at byte offset "+pos+")";
-
- int line=1, start=0;
- for( int i=0; i<pos; i++ )
- if( source[i]=='\n' ) { line++; start=i+1; }
- int end=pos;
- while( end<source.length && source[end]!='\n' ) end++;
- if( end>start && source[end-1]=='\r' ) end--;
- int point=Math.min(pos,end);
- String before=new String(source,start,point-start,StandardCharsets.UTF_8);
- String text=new String(source,start,end-start,StandardCharsets.UTF_8);
- int column=before.codePointCount(0,before.length())+1;
- SB sb=new SB().p(name).p(':').p(line).p(':').p(column).p(": error: ").p(message).p("\n ");
- int width=0, caret=0;
- for( int i=0; i<text.length(); ) {
- if( i==before.length() ) caret=width;
- int ch=text.codePointAt(i);
- if( ch=='\t' ) {
- int spaces=8-(width%8);
- for( int j=0; j<spaces; j++ ) sb.p(' ');
- width+=spaces;
- } else { sb.p(new String(Character.toChars(ch))); width++; }
- i+=Character.charCount(ch);
- }
- if( before.length()==text.length() ) caret=width;
- sb.p("\n ");
- for( int i=0; i<caret; i++ ) sb.p('-');
- return sb.p('^').toString();
- }
-}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
deleted file mode 100644
index 616753d4..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/StaticData.java
+++ /dev/null
@@ -1,177 +0,0 @@
-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;
- }
-
- 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/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
index 9b4b0995..bc698e19 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/codegen/TypeCheck.java
@@ -5,47 +5,29 @@ import com.seaofnodes.simple.node.*;
import com.seaofnodes.simple.type.Type;
import com.seaofnodes.simple.util.Ary;
-// Reject invalid optimized programs before lowering erases their source-level
-// obligations. Queue surviving CLInits during the same walk for Lower's Iter.
abstract class TypeCheck {
static void run(CodeGen code) {
- // Check for class-init cycles first; such a cycle can prevent other
- // things from resolving types. The dependency graph is temporary.
- String clzInitCycle = new ClassInitDependencies(code).check();
- if( clzInitCycle != null )
- throw Parser.error(clzInitCycle,null);
-
Ary<Node> errs = new Ary<>(Node.class);
- code._stop.walk( n -> {
- if( n.err() != null )
- errs.add(n);
- if( n instanceof CLInitNode ) code.add(n);
- return null;
- });
+ code._stop.walk(n -> {
+ if( n.err()!=null ) errs.add(n);
+ return null;
+ });
+ if( errs.isEmpty() ) return;
- if( !errs.isEmpty() ) {
- // Prefer an originating error over a secondary error caused by a
- // BOTTOM input. If all errors are downstream-poisoned, retain
- // the old first-error behavior.
- Node min = null;
- for( Node n : errs )
- if( noBottomInputs(n) )
- { min = n; break; }
- if( min==null )
- min = errs.at(0);
- for( Node n : errs )
- if( n!=min &&
- noBottomInputs(n) &&
- ((n instanceof MemOpNode nmop &&
- min instanceof MemOpNode mmop &&
- nmop._loc.before(mmop._loc)) ||
- (n instanceof CFGNode ncfg &&
- min instanceof CFGNode mincfg &&
- ncfg.idepth() < mincfg.idepth())) )
- min = n;
- if( code._obs != null ) code._obs.error(min, min.err().getMessage());
- throw min.err();
- }
+ // A bad Load now produces BOTTOM. Report the bad access before a
+ // Convert or arithmetic use that merely received its failed result.
+ Node first = errs.at(0);
+ for( Node n : errs )
+ if( noBottomInputs(n) ) { first=n; break; }
+ for( Node n : errs )
+ if( noBottomInputs(n) &&
+ ((n instanceof MemOpNode nm && first instanceof MemOpNode fm &&
+ nm._loc!=null && nm._loc.before(fm._loc)) ||
+ (n instanceof CFGNode nc && first instanceof CFGNode fc && nc.idepth()<fc.idepth())) )
+ first=n;
+ Parser.ParseException err = first.err();
+ if( code._obs!=null ) code._obs.error(first,err.getMessage());
+ throw err;
}
private static boolean noBottomInputs(Node n) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java b/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java
deleted file mode 100644
index fb1c4a6f..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/codegen/Unlink.java
+++ /dev/null
@@ -1,112 +0,0 @@
-package com.seaofnodes.simple.codegen;
-
-import com.seaofnodes.simple.node.*;
-
-// Discard call-graph edges after LoopTree. Machine selection keeps only this
-// object's bodies; ideal evaluation keeps imports so their calls can execute.
-abstract class Unlink {
- private static void unlinkImports(CodeGen code) {
- // Imported function constants keep their symbolic identity, not an
- // embedded copy of the other object's Return and body.
- for( Node n : code._start._outputs.asAry() )
- if( n instanceof FunPtrNode ptr && ptr.ret()!=null && !code.owns(ptr.fun()) ) {
- ptr.liftType(ptr._type);
- ptr.setDef(1,null);
- }
- for( FunNode fun : code._linker ) {
- if( fun==null || fun.isDead() || code.owns(fun) )
- continue;
- for( int i=1; i<fun.nIns(); i++ )
- if( fun.in(i) instanceof CallNode call )
- call.unlink(fun,i--);
- }
- for( int i=0; i<code._stop.nIns(); i++ ) {
- StopCUNode stop=(StopCUNode)code._stop.in(i);
- CompUnit cu=code._compunits.get(stop.start()._fname);
- if( cu._src==null ) code._stop.delDef(i--);
- }
- // The whole-object wrappers now summarize only the emitted units.
- code._stop ._type=code._stop .compute();
- code._start._type=code._start.compute();
- }
-
- static void run(CodeGen code, boolean removeImports) {
- if( removeImports ) unlinkImports(code);
-
- // The remaining passes assume all calls are unlinked; i.e. we are throwing
- // away the Call Graph here. Functions only reachable from internal calls
- // need to be re-hooked to stop/start less they go dead.
- for( FunNode fun : code._linker ) {
- // Already linked to start, not going dead
- if( fun==null || fun.isDead() )
- continue;
-
- // Imported functions are not emitted by this object. Ideal
- // evaluation retains them so their calls can actually execute.
- if( removeImports && !code.owns(fun) ) {
- while( fun.nIns() > 1 ) {
- if( fun.in(1) instanceof CallNode call )
- call.unlink(fun,1);
- else
- fun.removeDeadPath(1);
- }
- StartCUNode start = fun._compunit._start;
- StopCUNode stop = fun._compunit._stop;
- // Unhook the Stop->Return edge also
- ReturnNode ret = fun.ret();
- int idx = stop._inputs.find(ret);
- if( idx != -1 ) {
- stop._inputs.del(idx);
- ret.delUse(stop);
- }
- //
- if( stop.nIns()==0 ) {
- idx = code._stop._inputs.find(stop);
- if( idx != -1 )
- code._stop.delDef(idx);
- if( start.nIns() > 0 && start.in(0) != null )
- start.setDef(0,null);
- if( start.nIns() > 1 && start.in(1) != null )
- start.setDef(1,null);
- }
- continue;
- }
-
- // Insert the post-Opto code-generation hook. Unlike an Opto-time
- // Start input, this no longer carries unknown-caller semantics.
- ReturnNode ret = fun.ret();
- StartNode start = fun._compunit._start;
- assert start!=null;
- if( fun.nIns() <= 1 || fun.in(1) != start ) {
- fun.insertDef(1,start);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode parm )
- parm.insertDef(1,ConstantNode.raw(parm._type));
- if( fun._compunit._stop._inputs.find(ret) == -1 )
- fun._compunit._stop.addDef(ret);
- }
- // Unlink from Call
- for( int i=2; i<fun.nIns(); i++ )
- ((CallNode)fun.in(i)).unlink(fun,i--);
- if( fun.rpc()==null ) { // Ensure valid RPC
- // First make sure a valid RPC; single-call into multi-fun will have each
- // function having a single call site, so the RPC becomes a constant and
- // folds - but since multiple targets, the Call never inlines. Recreate a
- // valid RPC so codegen (and Eval2) understands the calling convention.
- ParmNode rpc = new ParmNode( "$rpc", 0, ret.rpc()._type, fun );
- rpc.addDef(ret.rpc());
- ret.setDef( 3, rpc.init() );
- }
- }
-
- code._stop.walk(n -> {
- if( n instanceof CallEndNode cend )
- for( int i=1; i<cend.nIns(); i++ )
- if( cend.in(i) instanceof ReturnNode )
- cend.delDef(i--);
- if( n instanceof CallNode call )
- call.unlink_all();
- return null;
- });
- }
-}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java
new file mode 100644
index 00000000..0547e29c
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AddFNode.java
@@ -0,0 +1,32 @@
+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); }
+ public AddFNode(ArithNode n) { super(n); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
index a29ba73b..aa0ee024 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AddNode.java
@@ -7,7 +7,6 @@ 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 "+"; }
@@ -42,7 +41,7 @@ public class AddNode extends ArithNode {
// Add of same to a multiply by 2
if( lhs==rhs && _mode==1 )
- return new ShlNode(null,lhs,CodeGen.CODE.con(1));
+ return new ShlNode(null,lhs,com.seaofnodes.simple.Parser.con(1));
// Goal: a left-spine set of adds, with constants on the rhs (which then fold).
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java
index f02007bc..2bb289e9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/AndNode.java
@@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger;
public class AndNode extends ArithNode {
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 op() { return "&"; }
@Override long doOp( long x, long y ) { return x & y; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
new file mode 100644
index 00000000..7d219215
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithFNode.java
@@ -0,0 +1,31 @@
+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); }
+ protected ArithFNode(ArithNode n) { super(n); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
index 92ebd587..5f0862b2 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ArithNode.java
@@ -5,7 +5,6 @@ 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;
@@ -19,9 +18,7 @@ public abstract class ArithNode extends Node implements ModeNode {
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) {
@@ -98,8 +95,6 @@ public abstract class ArithNode extends Node implements ModeNode {
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);
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java
index 73e786e3..02421e43 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BoolNode.java
@@ -5,7 +5,6 @@ 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;
@@ -18,9 +17,7 @@ abstract public class BoolNode extends Node implements ModeNode {
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
@@ -73,7 +70,7 @@ abstract public class BoolNode extends Node implements ModeNode {
public Node idealize() {
// Compare of same
if( in(1)==in(2) )
- return this instanceof LT ? CodeGen.CODE.ZERO : ConstantNode.make(TRUE);
+ return this instanceof LT ? com.seaofnodes.simple.Parser.ZERO : ConstantNode.make(TRUE);
// Can we decide int vs flt?
Type t1 = in(1)._type, t2 = in(2)._type;
if( t1==null || t2==null )
@@ -119,8 +116,7 @@ abstract public class BoolNode extends Node implements ModeNode {
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 "=="; }
+ 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;
@@ -133,8 +129,7 @@ abstract public class BoolNode extends Node implements ModeNode {
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 "!="; }
+ 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;
@@ -147,8 +142,7 @@ abstract public class BoolNode extends Node implements ModeNode {
public static class LT extends BoolNode {
public LT(Node lhs, Node rhs) { super(lhs,rhs); }
public LT(Node lhs, Node rhs, byte mode) { super(lhs,rhs, mode); }
- @Override public Tag serialTag() { return Tag.LT; }
- public String op() { return "<" ; }
+ public String op() { return "<" ; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1._max < i2._min ) return TRUE;
if( i1._min >= i2._max ) return FALSE;
@@ -161,8 +155,7 @@ abstract public class BoolNode extends Node implements ModeNode {
public static class LE extends BoolNode {
public LE(Node lhs, Node rhs) { super(lhs,rhs); }
public LE(Node lhs, Node rhs, byte mode) { super(lhs,rhs,mode); }
- @Override public Tag serialTag() { return Tag.LE; }
- public String op() { return "<="; }
+ public String op() { return "<="; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( i1._max <= i2._min ) return TRUE;
if( i1._min > i2._max ) return FALSE;
@@ -179,8 +172,7 @@ abstract public class BoolNode extends Node implements ModeNode {
super(lhs,rhs,(byte)1);
assert mode==1;
}
- @Override public Tag serialTag() { return Tag.ULT; }
- public String op() { return "u<" ; }
+ public String op() { return "u<" ; }
TypeInteger doOp(TypeInteger i1, TypeInteger i2) {
if( Long.compareUnsigned(i1._max,i2._min) < 0 ) return TRUE;
if( Long.compareUnsigned(i1._min,i2._max) >= 0 ) return FALSE;
@@ -188,4 +180,23 @@ abstract public class BoolNode extends Node implements ModeNode {
}
Node copy(Node lhs, Node rhs) { return new ULT(lhs,rhs); }
}
+ public static class EQF extends EQ {
+ public EQF(Node lhs, Node rhs) { super(lhs,rhs,(byte)2); }
+ @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,(byte)2); }
+ @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,(byte)2); }
+ @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); }
+ }
+
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
index c6a6ff3b..8399ed09 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/BulkMemPhiNode.java
@@ -1,15 +1,9 @@
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};
@@ -21,53 +15,24 @@ public class BulkMemPhiNode extends PhiNode {
private static final BitSet EMPTY = new BitSet();
// Excluded Aliases; empty means NO exclusions.
- // Directly visible by design. Empty sets share EMPTY; splitAlias performs
- // copy-on-first-write and removes the node from GVN before mutation.
- public BitSet _aliases;
+ // Each split clones the exclusions; published sets are never mutated.
+ public final BitSet _aliases;
public BulkMemPhiNode(String label, Node... inputs) {
this(label,EMPTY,inputs);
}
private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) {
- super(label, 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); }
- @Override public Tag serialTag() { return Tag.BulkMemPhi; }
-
- @Override
- public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
- IdentityHashMap<Node,Integer> anodes) {
- super.packed(baos,strs,types,anodes);
- baos.packed2(_aliases.cardinality());
- for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) )
- baos.packed2(alias);
- }
+ public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi); _aliases = phi._aliases; }
- 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 boolean isMem() { return true; }
+ @Override public String label() { return "BulkPhi"+_aliases; }
@Override
public Type compute() {
@@ -87,7 +52,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,mmm.in(1));
+ setDef(i,CodeGen.CODE.add(mmm.in(1)));
return this;
}
@@ -99,8 +64,8 @@ public class BulkMemPhiNode extends PhiNode {
return slice(alias);
}
- for( int i=0; i<nOuts(); i++ ) {
- int alias = outputAlias(out(i));
+ for( int i=0; i<_outputs.size(); i++ ) {
+ int alias = outputAlias(_outputs.get(i));
if( alias!=0 )
return slice(alias);
}
@@ -125,22 +90,19 @@ public class BulkMemPhiNode extends PhiNode {
// True if the MemMerge has no precise slice still covered by this Phi.
private boolean canPeek(MemMergeNode mmm) {
- return missingAlias(mmm)==0;
+ return mmm.in(1)!=null && missingAlias(mmm)==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 ConstantNode con -> {
- assert !(con._con instanceof TypeMem tmem && tmem._alias != 1);
- yield 0;
- }
+ case ParmNode parm -> 0;
+ case ConstantNode con -> 0;
case MemMergeNode mmm -> missingAlias(mmm);
case MemOpNode mem -> unsplit(mem._alias);
case BulkMemPhiNode bulk -> missingAlias(bulk);
- default -> throw Utils.TODO("Should not reach here");
+ default -> throw new AssertionError("Unexpected memory input "+n);
};
}
@@ -150,18 +112,16 @@ 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.
- use = addDepForwards(use);
+ addDepForwards(use);
return switch(use) {
case ScopeNode scope -> 0;
- case ParmNode parm -> 0;
case MemMergeNode mmm -> missingAlias(mmm);
case BulkMemPhiNode bulk -> missingAlias(bulk);
case MemOpNode mem -> unsplit(mem._alias);
case MemPhiNode phi -> unsplit(phi._alias);
- case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0;
default -> {
- assert use instanceof ReturnNode || use instanceof CallNode || use instanceof CLInitNode
- : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use;
+ assert use instanceof ReturnNode || use instanceof CallNode || use instanceof ParmNode
+ : "Unexpected bulk-memory user "+use;
yield 0;
}
};
@@ -183,9 +143,9 @@ public class BulkMemPhiNode extends PhiNode {
return 0;
}
- // First explicit MemMerge slice still covered by this Phi. A user's slice
- // may point to this Phi after a precise Phi folds; splitting is still valid.
- private int missingAlias( MemMergeNode mmm ) {
+ // Every explicit slot requests that alias, including a partial allocation
+ // input whose slot still points to this bulk Phi.
+ private int missingAlias(MemMergeNode mmm) {
for( int alias=2; alias<mmm.nIns(); alias++ )
if( mmm.in(alias)!=null &&
mmm.alias(alias)!=mmm.in(1) &&
@@ -201,7 +161,6 @@ 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);
@@ -216,7 +175,8 @@ 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
- Node bulk = bphi.init();
+ bphi.setType(TypeMem.BOT);
+ Node bulk = bphi;
CodeGen.CODE.add(bulk);
CodeGen.CODE.add(mem);
return aggregate(bulk,alias,mem);
@@ -226,19 +186,11 @@ 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(null,bulk);
+ MemMergeNode mmm = new MemMergeNode(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;
}
@@ -250,14 +202,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.BOT.makeFrom(alias));
+ mphi.setType(TypeMem.make(alias,Type.BOTTOM));
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().outs() )
+ for( Node use : region()._outputs )
if( use instanceof MemPhiNode mphi && mphi._alias==alias )
return mphi;
return null;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
index 4f225fbe..6805c1ef 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CFGNode.java
@@ -1,11 +1,13 @@
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.IdentityHashMap;
+import java.util.HashMap;
+import java.util.HashSet;
/** Control Flow Graph Nodes
* <p>
@@ -101,79 +103,11 @@ 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 CLInitNode init when init.bypass() -> init.cfg0()._safeCFG();
- case CallNode call when call.cend()._folding -> call.cfg0()._safeCFG();
- case CallEndNode cend when cend._folding -> cend.cfg(1)._safeCFG();
- case FunNode fun when fun._folding -> fun.cfg(1)._safeCFG();
- 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) ) {
- if( cfg==null ) return null;
+ while( !(cfg instanceof FunNode fun) )
cfg = cfg.idom();
- }
return fun;
}
@@ -184,13 +118,13 @@ public abstract class CFGNode extends Node {
public LoopTree _ltree;
public int _pre; // Pre-order numbers for loop tree finding
- public static class LoopTree {
- public LoopTree _par;
- public final LoopNode _head;
- public int _depth;
+ static class LoopTree {
+ LoopTree _par;
+ final LoopNode _head;
+ int _depth;
LoopTree(LoopNode head) { _head = head; }
- @Override public String toString() { return label(_head); }
- public int depth() {
+ @Override public String toString() { return "LOOP"+_head._nid; }
+ int depth() {
return _depth==0 ? (_par==null ? 0 : (_depth = _par.depth()+1)) : _depth;
}
}
@@ -199,29 +133,23 @@ 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( 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);
- }
- };
+ 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; }
}
- for( int i=0; i<work._len; i++ )
- pre = work.at(i)._bltWalk(pre,work.at(i),stop, post, work);
+ _ltree = stop._ltree = Parser.XCTRL._ltree = new LoopTree((StartNode)this);
+ _bltWalk(2,null,stop, new BitSet());
}
-
- int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post, Ary<FunNode> work ) {
+ int _bltWalk( int pre, FunNode fun, StopNode stop, BitSet post ) {
// Pre-walked?
if( _pre!=0 ) return pre;
_pre = pre++;
@@ -235,12 +163,8 @@ public abstract class CFGNode extends Node {
}
// Pre-walk
for( Node use : _outputs )
- 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 );
- }
+ if( use instanceof CFGNode usecfg && !skip( usecfg ) )
+ pre = usecfg._bltWalk( pre, use instanceof FunNode fuse ? fuse : fun, stop, post );
// Post-order work: find innermost loop
LoopTree inner = null, ltree;
@@ -283,7 +207,7 @@ public abstract class CFGNode extends Node {
return pre;
}
- public boolean skip(CFGNode usecfg) {
+ private 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 ||
@@ -291,13 +215,8 @@ public abstract class CFGNode extends Node {
(this instanceof ReturnNode && usecfg instanceof CallEndNode);
}
- 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;
- };
- }
+ public String label( CFGNode target ) {
+ return (target instanceof LoopNode ? "LOOP" : "L")+target._nid;
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
deleted file mode 100644
index 7bc17c75..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CLInitNode.java
+++ /dev/null
@@ -1,172 +0,0 @@
-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.CompUnit;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
-import java.util.*;
-
-// Track class initializers - the code just after this point expects the named
-// class to be initialized. If this code path dies, then the dependency dies
-// also - dead code does not force class init.
-//
-public class CLInitNode extends CFGNode implements MultiNode {
- public Parser.Lexer _loc; // For error reporting
-
- // Inputs: control, whole memory, class pointer, loaded $init flag
- // Results: control, whole memory, known-true flag alias memory.
- // For field references, the ptr might be the dst clz ptr or not.
- public CLInitNode(Parser.Lexer loc, Node ctrl, Node mem, Node ptr, Node ready) {
- super(ctrl,mem,ptr,ready);
- _loc = loc;
- }
-
- CLInitNode(BAOS bais, String[] strs) { super(null,null,null,null); _loc=Parser.Lexer.unpack(bais,strs); }
- @Override public Tag serialTag() { return Tag.CLInit; }
- @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
- Parser.Lexer.packed(_loc,baos,strs);
- }
- @Override public void gather(HashMap<String,Integer> strs) { Parser.Lexer.gather(_loc,strs); }
-
- public CLInitNode(CLInitNode init) {
- super(init);
- _loc = init._loc;
- }
-
- public Node mem() { return in(1); }
- public Node ptr() { return in(2); }
- public Node ready() { return in(3); }
-
- // The field/alias can be unresolved until forward references are loaded.
- public Field flag() {
- return ptr()._type instanceof TypeMemPtr tmp && Parser.startsClzPrefix(tmp._obj._name)
- ? tmp._obj.field("$init") : null;
- }
- @Override public String label() { return "CLInit"; }
-
- // Best effort dst compunit, based on ptr - or null
- public CompUnit cu() {
- if( !(ptr()._type instanceof TypeMemPtr ptr && Parser.startsClzPrefix(ptr._obj._name)) )
- return null;
- // Convert "class:sys.ary" into "sys/ary" and lookup CompUnit
- String fname = ptr._obj._name.substring(6).replace(".","/");
- return CodeGen.CODE._compunits.get(fname);
- }
-
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- FunNode fun = fun();
- CompUnit cu = cu();
- return p.p("CLInit ").p(fun._name).p(" -> ").p(cu==null ? "---" : cu._cname);
- }
-
- // Block head is Start, Region, CProj, but not e.g. If, Return, Stop
- public boolean blockHead() { return true; }
-
- private boolean instance() {
- return ptr()._type==Type.NIL ||
- (ptr()._type instanceof TypeMemPtr tmp && tmp._obj!=TypeStruct.BOT &&
- !tmp._obj.isHigh() && !tmp._obj._open && !tmp._obj._fref &&
- !Parser.startsClzPrefix(tmp._obj._name));
- }
-
- public boolean bypass() { return instance() || ready()._type==TypeInteger.TRUE; }
-
- @Override public Type compute() {
- if( in(0)._type.isHigh() || ptr()._type.isHigh() )
- return TypeTuple.make(Type.XCONTROL,TypeMem.TOP,TypeMem.TOP);
- if( instance() )
- return TypeTuple.make(in(0)._type,mem()._type,mem()._type);
- Field flag = flag();
- Type slice = flag==null ? TypeMem.BOT : TypeMem.make(flag._alias,TypeInteger.TRUE);
- // The initializer may write arbitrary public memory. Only its flag
- // has a guaranteed value on the normal continuation.
- Type bulk = ready()._type.isHigh() ? TypeMem.TOP :
- ready()._type==TypeInteger.TRUE ? mem()._type : TypeMem.BOT;
- return TypeTuple.make(in(0)._type,bulk,slice);
- }
-
- @Override public Node pcopy(int idx, Node dep) {
- dep.addDep(ptr());
- dep.addDep(ready());
- if( instance() ) return idx==0 ? in(0) : mem();
- if( ready()._type!=TypeInteger.TRUE ) return null;
- if( idx==0 ) return in(0);
- if( idx==1 ) return mem();
- // Already established on the incoming path; this is a memory fact,
- // not a new store to the class object.
- return CodeGen.CODE.con(((TypeTuple)_type)._types[2]);
- }
-
- @Override public Node idealize() {
- if( CodeGen.CODE._phase!=CodeGen.Phase.Lower || bypass() || in(0)._type.isHigh() ) return null;
- if( CodeGen.CODE._midAssert ) return this;
- // Until entry arguments become arrays, ordinary initializers receive zero.
- return expand(CodeGen.CODE.con(TypeInteger.ZERO));
- }
-
- // Called only after cycle/type checking. The initialized arm skips the
- // call; both arms rejoin with whole memory and a known-true flag slice.
- private Node expand(Node arg) {
- FunNode target = CodeGen.CODE.link(cu());
- assert target!=null && !target.isDead();
- keep();
- Node ctrl = in(0);
- Node memory = mem();
-
- // A child body may assume its parent has finished. Establish that
- // requirement first, then test the child's flag in the resulting memory.
- CompUnit parent = cu()._par;
- if( parent!=null ) {
- Node ptr = CodeGen.CODE.con(TypeMemPtr.make((byte)2,parent._clz,true));
- Field flag = parent._clz.field("$init");
- Node off = CodeGen.CODE.con(parent._clz.offset(parent._clz.find("$init")));
- Node parentReady = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag._alias,ctrl,memory,ptr,off).init());
- CLInitNode init = CodeGen.CODE.add(new CLInitNode(_loc,ctrl,memory,ptr,parentReady).init());
- ctrl = CodeGen.CODE.add(new CProjNode(init,0,ScopeNode.CTRL).init());
- memory = CodeGen.CODE.add(new ProjNode(init,1,ScopeNode.MEM0).init());
- }
-
- Node childOff = CodeGen.CODE.con(cu()._clz.offset(cu()._clz.find("$init")));
- // Read the child's flag after establishing its parent's memory effects.
- Node ready = CodeGen.CODE.add(new LoadNode(_loc,"$init",flag()._alias,ctrl,memory,ptr(),childOff).init());
-
- IfNode iff = CodeGen.CODE.add(new IfNode(ctrl,ready).init());
- Node done = CodeGen.CODE.add(new CProjNode(iff,0,"True").init());
- Node cold = CodeGen.CODE.add(new CProjNode(iff,1,"False").init());
-
- Node fptr = new FunPtrNode(target.sig(),CodeGen.CODE._start,target.ret()).peephole();
- CallNode call = CodeGen.CODE.add(new CallNode(_loc,cold,memory,ptr(),arg,fptr).init());
- CallEndNode end = new CallEndNode(call,TypeRPC.constant(CodeGen.CODE.rpc(_loc._cu._cname)));
- // This is a lowered call, after interprocedural analysis. Do not add
- // new parameter flows to a body already optimized by SCCP.
- CodeGen.CODE.add(end.init());
- Node called = CodeGen.CODE.add(new CProjNode(end,0,ScopeNode.CTRL).init());
- Node after = CodeGen.CODE.add(new ProjNode(end,1,ScopeNode.MEM0).init());
- RegionNode region = CodeGen.CODE.add(new RegionNode(_loc,null,done,called).init());
- Node merged = CodeGen.CODE.add(new BulkMemPhiNode(ScopeNode.MEM0,region,memory,after).init());
-
- // Replace the individual outputs; no tuple-pack node or second
- // representation of CLInit is needed while the projections collapse.
- for( Node use : _outputs.asAry() ) {
- if( use==null ) continue; // Our temporary keep.
- assert use instanceof Proj;
- int idx = ((Proj)use).idx();
- use.subsume(idx==0 ? region : idx==1 ? merged : CodeGen.CODE.con(((TypeTuple)_type)._types[2]));
- }
- CodeGen.CODE.invalidateIDepthCaches();
- return unkeep(); // Iter removes this now-unused node.
- }
-
- // Resolve a precise flag slice even when the parser did not yet know the
- // class/alias and could only install the whole-memory projection.
- static Node slice(Node mem, int alias, Node dep) {
- if( !(mem instanceof ProjNode proj && proj._idx==1 && proj.in(0) instanceof CLInitNode init) )
- return mem;
- dep.addDep(init.ptr());
- Field flag = init.flag();
- if( flag==null || flag._alias!=alias ) return mem;
- return new ProjNode(init,2,Parser.memName(alias)).peephole();
- }
-}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
index 073e4658..c62de840 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CProjNode.java
@@ -2,16 +2,13 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.Parser;
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 com.seaofnodes.simple.util.Utils;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-public class CProjNode extends CFGNode implements Proj {
+public class CProjNode extends CFGNode {
// Which slice of the incoming multipart value
public int _idx;
@@ -25,14 +22,6 @@ public class CProjNode extends CFGNode implements Proj {
_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; }
@@ -52,17 +41,17 @@ public class CProjNode extends CFGNode implements Proj {
public Node idealize() {
if( ctrl()._type instanceof TypeTuple tt ) {
if( tt._types[_idx]==Type.XCONTROL )
- return CodeGen.CODE.XCTRL; // We are dead
+ return Parser.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 && iff.pred() != null && addDep(iff.pred()) instanceof NotNode not )
+ if( ctrl() instanceof IfNode iff && 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
- return ((MultiNode)ctrl()).pcopy(_idx,this);
+ return ((MultiNode)ctrl()).pcopy(_idx);
}
// Only called during basic-block layout, inverts a T/F CProj
@@ -77,9 +66,4 @@ public class CProjNode extends CFGNode implements Proj {
@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/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
index 05707132..d26ea33b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndMach.java
@@ -14,15 +14,13 @@ 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 = (call().external() ? code._cCallerSave : code._callerSave).copy();
+ RegMaskRW kills = 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 = call().external()
- ? code._mach.maxArgSlot(call().tfp(),code._cCallingConv)
- : code._mach.maxArgSlot(call().tfp());
+ int xslot = code._mach.maxArgSlot(call().tfp());
_xslot = (maxReg+fslot)+xslot;
for( int i=0; i<xslot; i++ )
kills.set((maxReg+fslot)+i);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
index 36a012e1..aa586b17 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallEndNode.java
@@ -2,13 +2,9 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.*;
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
@@ -16,48 +12,17 @@ import java.util.IdentityHashMap;
public class CallEndNode extends CFGNode implements MultiNode {
// When set true, this Call/CallEnd/Fun/Return is being trivially inlined
- boolean _folding;
- // +2: must clone, so await all cleanup but OK
- // +1: trivial now, so can inline without cloning and without waiting for cleanup
- // 0: take a look,
- // -1: somethings wrong, but might improve
- // -2: never and stop asking,
- private byte _inline;
+ private boolean _folding;
+ public boolean folding() { return _folding; }
public final TypeRPC _rpc;
- public CallEndNode(CallNode call, TypeRPC rpc) {
- super(new Node[]{call});
- _rpc = rpc;
- }
+ public CallEndNode(CallNode call) { super(new Node[]{call}); _rpc = TypeRPC.constant(_nid); }
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(Serialize.nIns(this,anodes));
- // 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
@@ -76,261 +41,192 @@ public class CallEndNode extends CFGNode implements MultiNode {
@Override
public Type compute() {
if( !(in(0) instanceof CallNode call) || call._type != Type.CONTROL )
- return TypeTuple.STATE.dual();
+ return TypeTuple.RET.dual();
+ // Mid-fold, just take the one single callers' return type
+ if( _folding ) return in(1)._type;
// 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;
- 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;
- // The signature bounds widening (e.g. an i32 result stays i32).
- // Use the callee signature, not FunPtrNode's live recursive range.
- Type bound = tfp.ret();
- if( !XInt.isHigh(tfp.fidxs()) ) {
- bound = Type.TOP;
- for( int fidx = XInt.next(tfp.fidxs(),0); fidx >= 0; fidx = XInt.next(tfp.fidxs(),fidx) ) {
- if( CodeGen.CODE.externFunc(fidx)!=null )
- externs++;
- FunNode fun = CodeGen.CODE._link(fidx);
- bound = bound.meet(fun==null ? tfp.ret() : addDep(fun).sig().ret());
+ 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);
+ }
+ // Recursion is a loop in disguise. For f(x-1)+1, the result
+ // flows Return -> CallEnd -> Add -> Phi -> Return, without
+ // crossing ParmNode.compute's widening point. Widen here too,
+ // using the loop rule in PhiNode.compute. Keep constants and
+ // above-center ranges narrow, and stay within the return type.
+ Type oldret = _type instanceof TypeTuple tt ? tt.ret() : Type.TOP;
+ if( ret instanceof TypeInteger ti && ret != oldret &&
+ !ti.isHigh() && !ti.isConstant() &&
+ (!(oldret instanceof TypeInteger oldi) || ti._widen <= oldi._widen) )
+ ret = ti.same_but_slightly_wider_than(TypeInteger.BOT).join(tfp.ret());
}
}
- 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.
- // Lowered calls deliberately have no links into optimized bodies.
- boolean opto = CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
- CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Lower.ordinal();
- Type tmem = opto ? TypeMem.TOP : TypeMem.BOT;
- Type ret = opto ? Type.TOP : tfp.ret();
- // Use the same widening before and after linking a callee.
- return TypeTuple.make(Type.CONTROL,tmem,widenResult(ret,bound));
- }
-
- // 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 before/during SCCP.
- // If the call remains out-of-line, its continuation can conservatively
- // contribute class-init dependencies even though it never executes.
- if( state.ctl()==Type.XCONTROL )
- return TypeTuple.make(Type.CONTROL,TypeMem.TOP,Type.TOP);
- // Join before widening: FunPtrNode follows the live Return type, so
- // joining it afterward would undo the widening.
- return state.makeFrom(2,widenResult(state.ret().join(tfp.ret()),bound));
- }
-
- private static Type widenResult(Type ret, Type bound) {
- // Recursion is a loop in disguise. For f(x-1)+1, the result
- // flows Return -> CallEnd -> Add -> Phi -> Return, without
- // crossing ParmNode.compute's widening point. Widen here too,
- // using the loop rule in PhiNode.compute. Keep constants and
- // above-center ranges narrow.
- if( ret instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
- ret = ti.same_but_slightly_wider_than().join(bound);
- return ret;
+ return TypeTuple.make(call._type,TypeMem.BOT,ret);
}
@Override
public Node idealize() {
- return null;
- }
- public byte maybeInline() {
- byte inline = inlineCandidate();
- if( CodeGen.CODE._midAssert )
- return inline;
- _inline = inline;
- if( _inline > 0 )
- doInline();
+ // 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);
+ }
+ }
- return _inline;
+ return null;
}
- // 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
+ // 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;
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;
+ }
- // Preserve initializer boundaries until post-Opto cycle validation.
- if( fun.isClz() ) return -2;
-
- // Heuristic forced inlining turned 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, but 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
- // and await cleanup to shrink the body.
- if( fun._approxUIDs >= maxSize ) {
- if( !smallBody(fun) ) return -1;
- } else if( fun.body().cardinality() >= maxSize ) // Expensive size check
- 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 trivial candidate right now
- return 1;
+ 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.
}
- // 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.
+ // 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.
private static final Node[] TINYBODY = new Node[15]; // Tiny worklist
- private boolean smallBody(FunNode fun) {
+ private int smallBody(FunNode fun) {
ReturnNode ret = fun.ret();
for( Node in : ret._inputs )
if( in!=null ) addDep(in);
- if( ret.ctrl()!=fun ) return false;
+ if( ret.ctrl()!=fun ) return 0;
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 false;
+ if( n instanceof ParmNode parm && parm.region()!=fun ) return 0;
if( n!=fun && !(n instanceof ParmNode) &&
!(n instanceof StoreNode) && !(n instanceof MemMergeNode) )
- return false; // Something unexpected
- if( n.nOuts()>TINYBODY.length ) return false; // Too big
+ return 0;
+ if( n.nOuts()>TINYBODY.length ) return 0;
for( Node use : n._outputs ) {
if( use==null ) continue;
int j=0;
while( j<len && TINYBODY[j]!=use ) j++;
- if( j<len ) continue; // Already in TINYBODY
- if( len==TINYBODY.length ) { addDep(use); return false; } // Too big
- TINYBODY[len++] = use; // Add to worklist
+ if( j<len ) continue;
+ if( len==TINYBODY.length ) { addDep(use); return 0; }
+ TINYBODY[len++] = use;
}
}
- 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;
+ return len;
}
-
- // 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, Node dep) {
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
index 71f6d146..b2708531 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CallNode.java
@@ -2,17 +2,12 @@ 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
@@ -22,18 +17,10 @@ public class CallNode extends CFGNode {
// Source location for late reported errors
public final Parser.Lexer _loc;
- public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc=loc; }
+ public CallNode(Parser.Lexer loc, Node... nodes) { super(nodes); _loc = loc; }
public CallNode(CallNode call) { super(call); _loc = call._loc; }
- @Override public Tag serialTag() { return Tag.Call; }
- public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
- baos.packed1(nIns());
- Parser.Lexer.packed(_loc,baos,strs);
- }
- @Override public void gather(HashMap<String,Integer> strs) { Parser.Lexer.gather(_loc,strs); }
- static Node make( BAOS bais, String[] strs ) {
- int nIns = bais.packed1();
- return new CallNode(Parser.Lexer.unpack(bais,strs),new Node[nIns]);
- }
+
+ @Override public String label() { return "Call"; }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
String fname = name();
@@ -48,7 +35,7 @@ public class CallNode extends CFGNode {
if( fptr()._type instanceof TypeFunPtr tfp && tfp._isConstant() && tfp.notNull() ) {
FunNode fun = CodeGen.CODE._link(tfp);
if( fun !=null ) return fun._name;
- if( fptr() instanceof ExternNode ex ) return ex._extern;
+ return CodeGen.CODE.externFunc(tfp);
}
return null;
}
@@ -101,9 +88,6 @@ public class CallNode extends CFGNode {
@Override
public Node idealize() {
- // Lowering may introduce calls after the call graph has been analyzed.
- // These use the callee interface without reopening its parameter flow.
- if( CodeGen.CODE._phase.ordinal() >= CodeGen.Phase.Lower.ordinal() ) return null;
CallEndNode cend = cend();
if( cend==null ) return null; // Still building
@@ -113,11 +97,14 @@ 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
- 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);
+ 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);
if( fun!=null && !fun._folding && !linked(fun) )
progress = link(fun);
}
@@ -138,58 +125,35 @@ public class CallNode extends CFGNode {
// Link so this calls fun
public Node link( FunNode fun ) {
assert !linked(fun);
- CodeGen.CODE.add(fun).addDef(this);
+ fun.addDef(this);
for( Node use : fun._outputs )
- 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);
- }
+ if( use instanceof ParmNode parm )
+ parm.addDef(parm._idx==0 ? new ConstantNode(cend()._rpc).peephole() : arg(parm._idx));
// Call end points to function return
CodeGen.CODE.add(cend()).addDef(fun.ret());
return this;
}
- // Unlink a single function
- public void unlink( FunNode fun, int path ) {
- assert linked(fun);
- for( Node use : fun._outputs )
- if( use instanceof ParmNode )
- use.delDef(path);
- fun.delDef(path);
- CodeGen.CODE.add(fun);
- CallEndNode cend = cend();
- ReturnNode ret = fun.ret();
- 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 ) {
- int path = fun._inputs.find(this);
- unlink(fun,path);
+ 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);
i--;
}
}
@Override
public Parser.ParseException err() {
- if( fptr()._type == Type.BOTTOM )
- return null; // Wrong, but failing earlier
if( !(fptr()._type instanceof TypeFunPtr tfp) )
- return Parser.error("Expected a function, but found " + fptr()._type, _loc);
+ throw Utils.TODO();
if( !tfp.notNull() )
return Parser.error( "Might be null calling "+tfp, _loc);
if( nargs() != tfp.nargs() )
@@ -198,13 +162,11 @@ 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)) ) {
- // 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 );
+ 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 );
}
- if( tfp.isHigh() )
+ if( tfp.fidxs() < 0 )
throw Utils.TODO(); // Infinite unknown TFPs? Should be fairly precise CG
return null;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java
new file mode 100644
index 00000000..e45898f5
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CastMach.java
@@ -0,0 +1,19 @@
+package com.seaofnodes.simple.node;
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.codegen.Encoding;
+import com.seaofnodes.simple.codegen.RegMask;
+import com.seaofnodes.simple.util.SB;
+
+/**
+ * Cast a pointer to read-only
+ */
+public class CastMach extends CastNode implements MachNode {
+ public CastMach( CastNode n ) { super(n); }
+ @Override public String op() { return label(); }
+ @Override public RegMask regmap(int i) { assert i==1; return RegMask.FULL; }
+ @Override public RegMask outregmap() { return RegMask.FULL; }
+ @Override public int twoAddress( ) { return 1; }
+ @Override public void encoding( Encoding enc ) { }
+ @Override public void asm(CodeGen code, SB sb) { sb.p(code.reg(in(1))); }
+}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java
new file mode 100644
index 00000000..fa7c207d
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CastNode.java
@@ -0,0 +1,79 @@
+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 boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ Type t = _t.upgradeType(types);
+ if( t==_t ) return false;
+ unlock();
+ _t = t;
+ return true;
+ }
+
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
index 284220ea..afdd29be 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConFldOffNode.java
@@ -2,14 +2,11 @@ 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
@@ -18,61 +15,27 @@ import java.util.HashMap;
*/
public class ConFldOffNode extends ConstantNode {
- public TypeStruct _ts; // Struct holding field
+ public final String _name; // Struct name
public final String _fname; // Field name
- public ConFldOffNode( TypeStruct ts, String fname ) { super(TypeInteger.BOT); _ts = ts; _fname = fname; }
- @Override public Tag serialTag() { throw Utils.TODO("should not reach here"); }
+ public ConFldOffNode( String name, String fname ) { super(TypeInteger.BOT); _name = name; _fname = fname; }
@Override public String label() {
- return _fname == " len" ? "sizeof("+_ts._name+")" : "#"+_fname;
+ return _fname == " len" ? "sizeof("+_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() {
- 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();
+ public Node asOffset(TypeStruct ts) {
+ return new ConstantNode(TypeInteger.constant(ts.offset(_fname==" len" ? ts._fields.length : ts.find(_fname)))).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 _ts._name==off._ts._name && _fname.equals(off._fname) && super.eq(n);
- }
- @Override int hash() { return super.hash() * _ts._name.hashCode() * _fname.hashCode(); }
-
- @Override public Parser.ParseException err() {
- return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",null) : null;
+ return _name.equals(off._name) && _fname.equals(off._fname) && super.eq(n);
}
+ @Override int hash() { return super.hash() * _name.hashCode() * _fname.hashCode(); }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
index 0569f352..8d783be7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConstantNode.java
@@ -5,10 +5,7 @@ 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.BAOS;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
+import com.seaofnodes.simple.util.SB;
/**
* A Constant node represents a constant value.
@@ -20,60 +17,29 @@ import java.util.IdentityHashMap;
* The Constant's value is the value stored in it.
*/
-public class ConstantNode extends TypeNode {
+public class ConstantNode extends Node {
+ public Type _con;
public ConstantNode( 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); }
- @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
+ super(new Node[]{CodeGen.CODE._start});
+ _con = _type = type;
}
- static Node make( BAOS bais, Type[] types) { return raw(types[bais.packed2()]); }
+ public ConstantNode( ConstantNode con ) { super(con); _con = con._type; }
- // Semantic constant construction through Lower. A unique internal
- // function identity carries a lifetime edge to the function's Return.
public static Node make( Type type ) {
if( type==Type. CONTROL ) return new CtrlNode();
if( type==Type.XCONTROL ) return new XCtrlNode();
- if( CodeGen.CODE._phase != null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.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);
+ 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());
}
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) {
@@ -88,4 +54,14 @@ public class ConstantNode extends TypeNode {
@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(); }
+
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ Type next = _con.upgradeType(types);
+ if( next==_con ) return false;
+ unlock();
+ _con=next;
+ return true;
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
index cdc890a7..c5be0123 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java
@@ -77,13 +77,7 @@ public class ConvertNode extends Node {
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 ||
@@ -98,4 +92,12 @@ public class ConvertNode extends Node {
@Override public boolean eq(Node n) { return _dst==((ConvertNode)n)._dst; }
@Override int hash() { return _dst.hashCode(); }
+
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ Type next = _dst.upgradeType(types);
+ if( next==_dst ) return false;
+ unlock();
+ _dst=next;
+ return true;
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
index 62140873..cf630ad4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/CtrlNode.java
@@ -8,7 +8,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java
new file mode 100644
index 00000000..df1dae4e
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/DivFNode.java
@@ -0,0 +1,27 @@
+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); }
+ public DivFNode(ArithNode n) { super(n); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
index f173f0ef..d973ffa7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/DivNode.java
@@ -9,7 +9,6 @@ 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 op() { return _mode==1 ? "//" : "/"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
deleted file mode 100644
index 3e95fffb..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/EscapeNode.java
+++ /dev/null
@@ -1,116 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java
index 978298fc..7a26e2e9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternNode.java
@@ -1,38 +1,25 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.Type;
-import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.type.TypeFunPtr;
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;
- // 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()]);
+ public ExternNode(Type t, String ex) {
+ super(t);
+ _extern = ex;
+ if( t instanceof TypeFunPtr tfp ) CodeGen.CODE.externFunc(tfp,ex);
}
@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/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
deleted file mode 100644
index 409ce5d1..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ExternOffsetNode.java
+++ /dev/null
@@ -1,19 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
index 644f1434..e7333c23 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FRefNode.java
@@ -10,32 +10,17 @@ 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 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;
- }
+ public final Var _n;
+ public FRefNode( Var n ) { super(FREF_TYPE); _n = n; }
- @Override public String label() { return "FRef"+_name; }
+ @Override public String label() { return "FRef"+_n; }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p("FRef_").p(_name);
+ return p.p("FRef_").p(_n);
}
@Override public Node idealize() {
@@ -43,8 +28,8 @@ public class FRefNode extends ConstantNode {
return nIns()==1 ? null : in(1);
}
- public Parser.ParseException err() { return Parser.error("Undefined name '"+_name+"'",_loc); }
+ public Parser.ParseException err() { return Parser.error("Undefined name '"+_n._name+"'",_n._loc); }
@Override public boolean eq(Node n) { return this==n; }
- @Override int hash() { return _name.hashCode(); }
+ @Override int hash() { return _n._idx; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
index a9f8d692..7605d74f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FldOffNode.java
@@ -1,54 +1,24 @@
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;
- }
+/** A field name whose receiver's shape is not known while parsing. */
+public class FldOffNode extends Node {
+ public final String _name;
+ public FldOffNode(Node ptr, String name) { super(null,ptr); _name=name; }
+ @Override public String label() { return "#"+_name; }
+ @Override public Type compute() { return TypeInteger.BOT; }
@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);
+ if( in(1)._type instanceof TypeMemPtr ptr && !ptr._obj._open ) {
+ if( _name.equals("<<") && ptr._obj.isAry() ) return con(ptr._obj.aryScale());
+ if( ptr._obj.field(_name)!=null ) return new ConFldOffNode(ptr._obj._name,_name);
+ }
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(); }
-
@Override public Parser.ParseException err() {
- if( in(0)._type instanceof TypeMemPtr tmp && tmp._obj._fref )
- return Parser.error("Unknown struct type '"+tmp._obj._name+"'",null);
- return null;
+ return Parser.error("Cannot resolve field '"+_name+"'",null);
}
+ @Override public boolean eq(Node n) { return _name.equals(((FldOffNode)n)._name); }
+ @Override int hash() { return _name.hashCode(); }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
index edde247a..1ba57e85 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunNode.java
+++ b/chapter25/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.*;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
+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.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
- public boolean _folding;
+ boolean _folding;
public boolean folding() { return _folding; }
private TypeFunPtr _sig; // Initial signature
@@ -23,45 +23,20 @@ public class FunNode extends RegionNode {
public String _name; // Debug name
- 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( Parser.Lexer loc, TypeFunPtr sig, String name, Node... nodes ) { super(loc,nodes); _name=name; _sig = sig; }
public FunNode( FunNode fun ) {
- 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(Serialize.nIns(this,anodes)); // Object-local linked calls
- baos.packed2(types.get(_sig)); // NPE if fails lookup
- baos.packed2(_name==null ? 0 : strs.get(_name));
- baos.packed2(_approxUIDs);
- // _compunit set from context on loading, do not write
- }
- 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();
- // _compunit set from context on loading, do not read
- return fun;
+ super( fun, fun==null ? null : fun._loc );
+ if( fun!=null ) {
+ _sig = fun.sig();
+ _name = fun._name;
+ } else {
+ _sig = TypeFunPtr.BOT;
+ _name = "";
+ }
}
- @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.
@@ -73,63 +48,42 @@ public class FunNode extends RegionNode {
return null;
}
- public ParmNode parm(int idx) {
- ParmNode pidx = null;
+ public ParmNode rpc() {
+ ParmNode rpc = null;
for( Node n : _outputs )
- if( n instanceof ParmNode parm && parm._idx==idx )
- { assert pidx==null; pidx=parm; }
- return pidx;
+ if( n instanceof ParmNode parm && parm._idx==0 )
+ { assert rpc==null; rpc=parm; }
+ return rpc;
}
- 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) || resolvesForwardSelf(sig,_sig);
+ assert sig.isa(_sig);
if( _sig != sig ) {
CODE.add(this);
- // Changing the signature can allow more inlining
- if( _ret != null )
- for( Node use : ret().outs() )
- if( use instanceof CallEndNode || use instanceof FunPtrNode )
- CODE.add(use);
- 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 = _compunit==null || _compunit._cname==null || _compunit._cname.indexOf('.')==-1
- ? name
- : (_compunit._cname+"."+name).intern();
+ if( _name==null ) _name=name;
+ else _name += "."+name;
}
@Override
@@ -138,10 +92,10 @@ public class FunNode extends RegionNode {
if( unknownCallers() )
return Type.CONTROL;
Type t = Type.XCONTROL;
- 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.
+ 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
if( !(in(i) instanceof StartNode) )
t = t.meet(in(i)._type);
}
@@ -151,38 +105,28 @@ public class FunNode extends RegionNode {
@Override
public Node idealize() {
- // Some linked path dies, except unknown caller hooks never kill Start.
+ // Some linked path dies, except main never kills Start
Node progress = deadPath(unknownCallers());
if( progress!=null ) {
- if( nIns()==2 && in(1) instanceof CallNode call )
- CODE.add(call.cend()); // If no Start and one call, check for inline
+ if( nIns()==3 && in(2) instanceof CallNode call )
+ CODE.add(call.cend()); // If 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() && tt.ret().isa(_sig.ret()) ) {
+ if( _ret!=null && _ret._type instanceof TypeTuple tt && tt.ret() != _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
- // Opaque CLInit obligations need their bodies even without Call edges.
- if( !isClz() && in(1) instanceof StartCUNode start ) {
- boolean preOpto = CodeGen.CODE._phase == null ||
- CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
- if( !preOpto && !start.escapedFIDX(_sig.fidx()) && nIns()<=2 && _ret!=null ) {
- _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
@@ -201,230 +145,62 @@ public class FunNode extends RegionNode {
}
// Bypass Region idom, always assume depth == 1, one more than Start,
- // unless folding
+ // unless folding then just a ID on input#1
@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 ? cfg(1) : null;
- }
+ @Override public CFGNode idom(Node dep) { return _folding && nIns()==3 ? cfg(2) : (nIns()>1 ? cfg(1) : null); }
// Always in-progress until we run out of unknown callers
public boolean unknownCallers() { return nIns()>=2 && in(1) instanceof StartNode; }
- 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;
- }
+ @Override public boolean inProgress() { return unknownCallers(); }
- // Function is public (callable from Start directly).
- public boolean isPublic( ) {
- // Never true for anonymous functions
- if( _name == null ) return false;
- // Only public for <clinit> and <init>, all others
- // are found via field loads. The <init> Start hook also supplies its
- // conservative pre-Opto construction inputs; StopCU separately keeps
- // its private-memory return from escaping as a public result.
- if( !isInit() ) return false;
- // Private class, has to be found via program text, not linker
- if( _name.charAt(0)=='_' ) return false;
-
- // No instances of "...clz1._clz2..." where the class name path
- // includes a private class.
- if( _name.indexOf("._") != -1 ) return false;
-
- // Public class <cl/init>
- return true;
- }
+ // Add a new function exit point.
+ public void addReturn(Node ctrl, Node mem, Node rez) { _ret.addReturn(ctrl,mem,rez); }
- @Override public boolean inProgress() {
- return unknownCallers() && CodeGen.CODE._phase.ordinal() < CodeGen.Phase.Opto.ordinal();
- }
-
- // Build the function body set
+ // Build the function body
public BitSet body() {
- // Iter drains ordinary peeps between inlines; body discovery precedes
- // this inline's folding, so function boundaries are stable.
- assert !_folding;
- // Forward control includes paths that never reach Return.
+
+ // Reverse up (stop to start) CFG only, collect bitmap.
BitSet cfgs = new BitSet();
- walkCFG(this,cfgs);
- cfgs.set(ret()._nid);
+ cfgs.set(_nid);
+ walkUp(ret(),cfgs );
// Top down (start to stop) all flavors. CFG limit to bitmap.
// 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();
- // The function owns its Return even when it has no executable exit.
- body.set(ret()._nid);
- BitSet visit = new BitSet();
- int old;
- do {
- old = body.cardinality();
- visit.clear();
- walkDown(this, cfgs, body, visit);
- } while( old != body.cardinality() );
+ walkDown(this, cfgs, body, new BitSet());
return body;
}
- private static void walkCFG(CFGNode n, BitSet cfgs) {
+ private static void walkUp(CFGNode n, BitSet cfgs) {
if( cfgs.get(n._nid) ) return;
cfgs.set(n._nid);
- if( n instanceof ReturnNode ) return;
- for( Node use : n._outputs ) {
- if( !(use instanceof CFGNode cfg) || cfg instanceof FunNode ) continue;
- if( cfg instanceof RegionNode ? cfg._inputs.find(n)>0 : cfg.in(0)==n )
- walkCFG(cfg,cfgs);
- }
+ if( n instanceof RegionNode r )
+ for( int i=1; i<n.nIns(); i++ )
+ walkUp(n.cfg(i),cfgs);
+ else walkUp(n.cfg0(),cfgs);
}
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);
- // Visit self as CFG outside the function
+
if( n instanceof CFGNode && !cfgs.get(n._nid) )
return false;
- // 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) )
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 ) {
- // 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
+ for( Node use : n._outputs )
in |= walkDown(use,cfgs,body,visit);
- }
if( in ) body.set(n._nid);
return in;
}
- // 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 );
- bodyCopy( visit, body, map, _ret ); // May be disconnected from executable control.
- visit.clear();
- bodyEdge( visit, body, map, this );
- bodyEdge( visit, body, map, _ret );
- 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
+ // FunNodes must match signature (equivalent: no 2 FunNodes are ever GVN'able)
@Override public boolean eq( Node n ) {
- return this==n;
+ return _sig == ((FunNode)n)._sig;
}
@Override public int hash() {
@@ -478,7 +254,12 @@ public class FunNode extends RegionNode {
return slotRotate*8;
}
- @Override public void gather( HashMap<String,Integer> strs ) {
- Serialize.gather(strs,_name);
+
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ TypeFunPtr next = (TypeFunPtr)_sig.upgradeType(types);
+ if( next==_sig ) return false;
+ unlock();
+ _sig=next;
+ return true;
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
index 82053fd8..43242c32 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/FunPtrNode.java
@@ -1,68 +1,12 @@
package com.seaofnodes.simple.node;
-import com.seaofnodes.print.ExprPrinter;
+import com.seaofnodes.simple.type.TypeFunPtr;
-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);
+// 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);
}
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/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java
index 0ce504d9..f808c276 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/GuardNode.java
@@ -4,7 +4,6 @@ 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;
@@ -25,11 +24,7 @@ public class GuardNode extends Node {
_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)"; }
@@ -51,6 +46,7 @@ public class GuardNode extends Node {
// 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();
+ if( t2!=null ) t2 = t.join(t2);
return t2==null ? (_type==null ? Type.TOP : _type) : t2; // No sane answer for e.g. not-zero of a zero
}
@@ -58,6 +54,8 @@ public class GuardNode extends Node {
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;
+ Type t = in(1)._type;
+ if( _nonZero ? !t.makeZero().isa(t) : t==t.makeZero() ) return in(1);
if( _type.isHighOrConst() && in(1)._type.isa(_type) )
return in(1);
return null;
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java
index c83c81d0..3f21417e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/IfNode.java
@@ -13,7 +13,9 @@ 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 Tag serialTag() { return Tag.If; }
+
+ @Override
+ public String label() { return "If"; }
@Override
protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
@@ -44,7 +46,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== XInt.EMPTY) ) ? TypeTuple.IF_FALSE : TypeTuple.IF_TRUE;
+ return (t==Type.NIL || t==TypeInteger.ZERO || (t instanceof TypeFunPtr tfp && tfp._fidxs==0) ) ? 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;
@@ -57,14 +59,17 @@ 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 &&
- 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) ) ) {
+ (pred() instanceof GuardNode guard && guard._nonZero && iff.pred()==guard.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/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
index c58bba39..24a2f9b8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoadNode.java
@@ -2,11 +2,9 @@ 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.GlobalBits;
+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;
@@ -24,14 +22,11 @@ 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, Node ctrl, Node mem, Node ptr, Node off) {
- super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off);
+ public LoadNode(Parser.Lexer loc, String name, int alias, Node mem, Node ptr, Node off) {
+ // The field declaration will supply storage width once the receiver
+ // is known. A parser guess must not bound the value of this Load.
+ super(loc, name, alias, true, Type.BOTTOM, 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);
@@ -47,8 +42,7 @@ public class LoadNode extends MemOpNode {
dep.addDepForwards(use);
if( use instanceof StoreNode || use instanceof PhiNode ||
use instanceof MemMergeNode ||
- use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ||
- use instanceof CLInitNode ) return true;
+ use instanceof NewNode || use instanceof CallNode ) return true;
}
return false;
}
@@ -60,105 +54,61 @@ public class LoadNode extends MemOpNode {
@Override
public Type compute() {
- 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 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( 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
- 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) {
+ Type p = ptr()._type;
+ Type m = mem()._type;
+ if( p.isHigh() || m.isHigh() ) return TypeScalar.TOP;
+ if( p==Type.NIL || p==Type.BOTTOM ) return Type.BOTTOM;
+ if( !(p instanceof TypeMemPtr ptr) ) return TypeScalar.BOT;
+ if( ptr.nullable() ) return Type.BOTTOM;
+ Field field = ptr._obj.field(_name);
+ if( field==null ) return TypeScalar.BOT;
+ Type t = field._t;
+ if( t instanceof TypeConAry ary ) t=ary.elem();
+ if( _alias!=1 ) t=t.join(MemMergeNode.contents(mem(),_alias,this));
+ if( ptr.isFinal() ) t=t.makeRO();
return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t;
}
@Override
public Node idealize() {
- 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( ptr()._type instanceof TypeMemPtr ptr && !ptr._obj._open ) {
+ Field field = ptr._obj.field(_name);
+ // Storage width comes from the declaration, not from a constant
+ // or range inferred for this particular object's current value.
+ Type declared = Parser.TYPES.get(ptr._obj._name);
+ if( declared instanceof TypeMemPtr shape && shape._obj.field(_name)!=null )
+ field=shape._obj.field(_name);
+ Type storage = field==null ? Type.BOTTOM : field._t instanceof TypeConAry ary ? ary.elem() : field._t;
+ if( field!=null && (_alias!=field._alias || _declaredType!=storage) ) {
+ unlock();
+ _alias=field._alias;
+ _declaredType=storage;
+ return this;
+ }
}
+ if( _alias==1 ) return null;
- // 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;
+ if( mem() instanceof CastNode cast ) {
+ setDef(1,cast.in(1));
return this;
}
- // Must sharpen alias first
- if( _alias == 1 )
- return null;
-
- Node slice = CLInitNode.slice(mem,_alias,this);
- if( slice!=mem ) { setDef(1,slice); return this; }
-
- if( mem instanceof MemMergeNode merge ) {
- setDef(1,merge.alias(_alias));
- // In support of uplifting control to a prior dominating load, if
- // 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);
-
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,CodeGen.CODE.add(merge.alias(_alias)));
return this;
}
+ Node ptr = ptr();
+ Node mem = mem();
+
+ Node win = find(mem,ptr,null,null);
+ if( win!=null ) return folded(win);
// 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
- addDep(ld)._type.isa(_type) ) // and not rolling backwards
+ cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier
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
@@ -183,17 +133,13 @@ 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) ) {
- // 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);
+ phi.setType(_type); // Backedge searches may return this value Phi.
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));
}
- CodeGen.CODE.add(phi.unkeep());
- return _type instanceof TypeInteger ? new ConvertNode(declaredType(),phi) : phi;
+ return CodeGen.CODE.add(phi.unkeep());
}
}
@@ -202,7 +148,8 @@ public class LoadNode extends MemOpNode {
// Load a flavored zero from a New
private Node zero(NewNode nnn) {
- Type zero = declaredType().makeZero();
+ TypeStruct ts = nnn._ptr._obj;
+ Type zero = ts._fields[ts.findAlias(_alias)]._t.makeZero();
assert zero.isa(_type); // Catch an uninitialized non-null field
return castRO(new ConstantNode(zero).peephole());
}
@@ -211,40 +158,29 @@ public class LoadNode extends MemOpNode {
// the unchanged loop memory, or a merge whose every arm folds. Null fails.
// 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;
+ ptr = address(ptr);
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.
+ // A symbolic Store may write this field. Wait for its alias;
+ // skipping it can make a compound assignment read the old value.
if( st._alias==1 ) return null;
addDep(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())) &&
+ if( _alias==st._alias && ptr==address(st.ptr()) && off()==st.off() ) return st;
+ if( _alias==st._alias && !neverAlias(ptr,address(st.ptr())) &&
!addDep(off())._type.join(addDep(st.off())._type).isHigh() ) return null;
mem=st.mem();
break;
case ProjNode proj:
- // 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;
+ 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;
case MemPhiNode phi:
addDep(phi.region());
// Only the original loop closes a successful cycle. Another
@@ -262,6 +198,13 @@ public class LoadNode extends MemOpNode {
return null;
}
+ // Guards and read-only views keep the same address. Retain the Load's
+ // guarded input for its null check, but compare actual addresses here.
+ private static Node address(Node ptr) {
+ while( ptr instanceof ReadOnlyNode || ptr instanceof GuardNode ) ptr=ptr.in(1);
+ return ptr;
+ }
+
private Node folded(Node win) {
return win instanceof StoreNode st ? extend(castRO(st.val())) : zero((NewNode)win);
}
@@ -275,12 +218,12 @@ public class LoadNode extends MemOpNode {
if( win instanceof MemPhiNode mp ) {
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 CodeGen.CODE.add(phi.unkeep().peephole());
+ return phi.unkeep().peephole();
}
- Node fold=folded(win);
- if( fold!=null ) return fold.peephole();
+ return folded(win).peephole();
}
- LoadNode ld=new LoadNode(_loc,_name,_alias,in(0),mem,ptr,off());
+ LoadNode ld=new LoadNode(_loc,_name,_alias,mem,ptr,off());
+ ld.setDef(0,in(0));
return ld.peephole();
}
@@ -312,7 +255,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;
}
@@ -320,18 +263,16 @@ 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( shift==0 ) {
- if( !val._type.isa(_type) )
- { addDep(val); return null; }
+ if( shf._type==TypeInteger.ZERO )
return val;
- }
Node shl = new ShlNode(null,val,shf.keep()).peephole();
return new SarNode(null,shl,shf.unkeep());
}
+
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java
index df686bcf..1d493db7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/LoopNode.java
@@ -3,22 +3,23 @@ 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.BAOS;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
+import com.seaofnodes.simple.util.Utils;
+import java.util.BitSet;
+import java.util.HashSet;
public class LoopNode extends RegionNode {
- public LoopNode( Parser.Lexer loc, Node xctrl, Node entry ) { super(loc,xctrl,entry,null); }
+ public LoopNode( Parser.Lexer loc, Node entry ) { super(loc,null,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;
@@ -45,7 +46,7 @@ public class LoopNode extends RegionNode {
x = x.idom();
}
// Found a no-exit loop. Insert an exit
- NeverNode iff = (NeverNode)new NeverNode(1.2f,back()).peephole(); // Ideal never-branch
+ NeverNode iff = (NeverNode)new NeverNode(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
@@ -55,13 +56,12 @@ 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 = ConstantNode.make(Type.TOP).peephole();
- Node memout = new MemMergeNode();
- memout.addDef(f); // placeholder for control
- memout.addDef(top);
+ Node top = new ConstantNode(Type.TOP).peephole();
+ MemMergeNode memout = new MemMergeNode(new ConstantNode(TypeMem.BOT).peephole());
+ memout.setDef(0,f);
for( Node u : _outputs )
- if( u instanceof PhiNode phi && phi._type.isa(TypeMem.BOT) )
- memout.addDef(phi);
+ if( u instanceof BulkMemPhiNode ) memout.setDef(1,u);
+ else if( u instanceof MemPhiNode phi ) memout.alias(phi._alias,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(r,mem).init();
+ mem = new BulkMemPhiNode(ScopeNode.MEM0,r,mem).init();
expr = new PhiNode(r,expr).init();
}
// Append new Never exit
@@ -88,12 +88,8 @@ public class LoopNode extends RegionNode {
ret.setDef(0,ctrl.unkeep());
ret.setDef(1,mem .unkeep());
ret.setDef(2,expr.unkeep());
- // 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);
+ ret._type = null; // This may LIFT the Return type, from [XCtrl,mem,expr] to [Ctrl,mem,expr]
+ ret.setType(ret.compute());
return fun;
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
index b25fcaaf..9c0755b9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MachConcreteNode.java
@@ -3,6 +3,7 @@ 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
@@ -13,8 +14,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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+ @Override public Type compute () { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
@Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
p.open().p(op()).p(",");
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java
index 025b5dc4..c0b3af66 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MachNode.java
+++ b/chapter25/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/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
index dfe8794d..7a329117 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemMergeNode.java
@@ -4,154 +4,99 @@ 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.*;
-/**
- * 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.
+/** A default slice and explicit alias overrides. A null default means partial
+ * memory: absent aliases are not covered, rather than having unknown contents.
*/
public class MemMergeNode extends Node {
-
- 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; }
- @Override public String label() { return "ALLMEM"; }
- @Override public boolean isMem() { return true; }
+ 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;
+ }
- @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(" ");
- }
- return p.unchar(' ').p("]");
+ public void alias(int alias, Node mem) {
+ assert alias > 1;
+ while( nIns()<=alias ) addDef(null);
+ setDef(alias,mem);
}
+ // 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());
+ }
+ // 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;
+ }
- @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);
- }
+ @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(" ");
}
- return TypeMem.makePrivate(ts);
+ return p.p("]");
+ }
- }
- // 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 Type compute() {
+ for( Node n : _inputs )
+ if( n != null && !n._type.isHigh() )
+ return TypeMem.BOT;
+ return TypeMem.TOP;
}
@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(1) == in(i) ) { setDef(i,null); progress=true; }
- else { allDefault=false; }
+ 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( 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 ) {
- // 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));
- }
- }
+ for( int i=2; i<mem.nIns(); i++ )
+ if( mem.in(i)!=null && (i>=nIns() || in(i)==null) )
+ alias(i,mem.in(i));
CodeGen.CODE.add(mem.in(1));
setDef(1,mem.in(1));
- return unkeep();
+ return this;
}
-
+ 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/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
index bfbd46c3..1f0fc4d3 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemOpNode.java
@@ -3,12 +3,12 @@ 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.GlobalBits;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeMemPtr;
+import com.seaofnodes.simple.util.Utils;
import java.lang.StringBuilder;
-import java.util.*;
+import java.util.BitSet;
+import java.util.ArrayList;
/**
* Convenience common base for Load and Store.
@@ -19,102 +19,93 @@ import java.util.*;
* 3 - Offset, integer types
* 4 - Value, for Stores only
*/
-public abstract class MemOpNode extends TypeNode {
+public abstract class MemOpNode extends Node {
// The equivalence alias class
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-dependencies on prior Stores, and never cause an
+ // Loads might pick up anti-dependences 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;
- // Field name.
+ // 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
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 decl, Node ctrl, Node mem, Node ptr, Node off) {
- super(decl, ctrl, mem, ptr, off);
+ public MemOpNode(Parser.Lexer loc, String name, int alias, boolean isLoad, Type glb, Node mem, Node ptr, Node off) {
+ super(null, 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 decl, Node ctrl, Node mem, Node ptr, Node off, Node value) {
- this(loc, name, alias, isLoad, decl, ctrl, mem, ptr, off);
+ 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);
addDef(value);
}
- public MemOpNode( Node ideal, MemOpNode mop ) {
- super(ideal,mop instanceof LoadNode ld ? ld.declaredType() : mop._con);
- _name = mop._name;
- _alias = mop._alias;
- _loc = mop._loc;
- _isLoad= mop._isLoad;
- }
-
- MemOpNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases, boolean isLoad ) {
- this(null,
- strs[bais.packed2()],
- mapAlias(bais,fileAliases,aliases),isLoad,
- types[bais.packed2()],null,null,null,null);
- }
-
- private static int mapAlias( BAOS bais, GlobalBits fileAliases, GlobalBits aliases ) {
- int alias = bais.packed2();
- return alias < GlobalBits.RESERVED ? alias : aliases.map(fileAliases,alias);
+ 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");
}
- @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
+ // 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;
}
- static String mlabel(String name) { return "[]"==name ? "ary" : ("#"==name ? "len" : name); }
+ static String mlabel(String name) { return "[]".equals(name) ? "ary" : ("#".equals(name) ? "len" : name); }
String mlabel() { return mlabel(_name); }
+ @Override public boolean isMem() { return !_isLoad; }
+
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>1 && _alias==other._alias && declaredType()==other.declaredType() && err()==null;
+ return _alias==other._alias && _declaredType==other._declaredType && err()==null;
}
@Override
public boolean eq(Node n) {
MemOpNode mem = (MemOpNode)n; // Invariant
- return _alias==mem._alias && super.eq(mem);
+ return _alias==mem._alias; // When comparing types error to use "equals"; always use "=="
}
@Override
- int hash() { return _alias ^ super.hash(); }
+ int hash() { return _alias; }
@Override
public Parser.ParseException err() {
@@ -123,37 +114,41 @@ public abstract class MemOpNode extends TypeNode {
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 Parser.error( "Might be null accessing '" + _name + "'",_loc);
- if( tmp._obj.field(_name)==null &&
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
- return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
- // Sane field
- if( off() instanceof ConFldOffNode && CodeGen.CODE._phase.ordinal() > CodeGen.Phase.FinalizeIR.ordinal() )
- return Parser.error("Accessing unknown field '"+_name+"' from '*"+tmp._obj._name+"'",_loc);
- return null;
+ if( ptr instanceof TypeMemPtr tmp && tmp.notNull() )
+ return null;
+ return Parser.error( "Might be null accessing '" + _name + "'",_loc);
}
-
// Memory writers which must follow this read. A packaging node is not a
- // write; follow this alias through aggregates to its writers.
- public Ary<Node> antiDeps( Ary<Node> antideps ) {
- antideps.clear();
- return antiDeps(mem(),antideps);
+ // write, but can hide New's input, so follow this alias through aggregates.
+ public ArrayList<Node> antiDeps() {
+ ArrayList<Node> deps = new ArrayList<>();
+ antiDeps(mem(),deps,new BitSet());
+ return deps;
}
- private Ary<Node> antiDeps( Node mem, Ary<Node> antideps ) {
+ private void antiDeps(Node mem, ArrayList<Node> deps, BitSet visit) {
+ if( visit.get(mem._nid) ) return;
+ visit.set(mem._nid);
for( Node use : mem._outputs ) {
if( use instanceof MemMergeNode merge ) {
- if( merge.alias(_alias)==mem )
- antiDeps(merge,antideps);
- } else if( use instanceof CallNode || use instanceof CLInitNode ||
+ Node slice = _alias<merge.nIns() ? merge.in(_alias) : null;
+ if( (slice==mem) || (slice==null && merge.in(1)==mem) )
+ antiDeps(merge,deps,visit);
+ } else if( use instanceof CallNode ||
(use instanceof MemOpNode st && !st._isLoad && st._alias==_alias) ||
- (use instanceof EscapeNode esc && esc.fld()._alias==_alias) ||
+ (use instanceof NewNode nnn && nnn.field(_alias)!=null) ||
(use instanceof MemPhiNode phi && phi._alias==_alias) ||
(use instanceof BulkMemPhiNode phi && !phi.isSplit(_alias)) ) {
- if( !antideps.contains(use) ) antideps.add(use);
+ if( !deps.contains(use) ) deps.add(use);
}
}
- return antideps;
+ }
+
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ Type next = _declaredType.upgradeType(types);
+ if( next==_declaredType ) return false;
+ unlock();
+ _declaredType=next;
+ return true;
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
index aaaedb89..ba53cfd8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MemPhiNode.java
@@ -1,69 +1,42 @@
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;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/** A Phi for one precise memory alias. */
+/** A Phi for one field alias, parallel to a bulk Phi at the same Region. */
public class MemPhiNode extends PhiNode {
-
public final int _alias;
public MemPhiNode(String label, int alias, Node... inputs) {
- super(label, inputs);
+ super(label,inputs);
assert alias > 1;
_alias = alias;
}
- public MemPhiNode(MemPhiNode phi) {
- super(phi,phi._label);
- _alias = phi._alias;
- }
-
- @Override public Tag serialTag() { return Tag.MemPhi; }
-
- @Override
- public void packed(BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types,
- IdentityHashMap<Node,Integer> anodes) {
- super.packed(baos,strs,types,anodes);
- baos.packed2(_alias);
- }
+ public MemPhiNode(MemPhiNode phi) { super(phi); _alias = phi._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 boolean isMem() { return true; }
+ @Override public String label() { return "MemPhi_"+_alias; }
- @Override
- public Type compute() {
- Type t = super.compute();
+ @Override public Type compute() {
assert BulkMemPhiNode.checkMem(this,_alias);
- if( !(t instanceof TypeMem tmem) ) return t;
- assert tmem._alias==1 || tmem._alias==_alias;
- // This Phi structurally knows its alias, but not whether the field is
- // final. Do not infer slice finality from evolving input types.
- return TypeMem.make(_alias,tmem._t,tmem._one,tmem._clz,false,tmem._escFs,tmem._escAs);
+ if( !(region() instanceof RegionNode r) )
+ return (region()._type==Type.XCONTROL || region()._type==Type.TOP) ? TypeMem.TOP : _type;
+ if( r.inProgress() ) return r instanceof LoopNode ? TypeMem.make(_alias,Type.BOTTOM) : 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);
}
- @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++ ) {
+ @Override public Node idealize() {
+ for( int i=1; i<nIns(); i++ )
if( in(i) instanceof MemMergeNode mem ) {
- setDef(i,mem.alias(_alias));
+ setDef(i,CodeGen.CODE.add(mem.alias(_alias)));
return this;
}
- Node slice = CLInitNode.slice(in(i),_alias,this);
- if( slice!=in(i) ) { setDef(i,slice); return this; }
- }
if( nIns()==3 && region() instanceof LoopNode loop && !loop.inProgress() &&
in(2) instanceof StoreNode st && st.mem()==this ) {
Node sink = st.sink(this);
@@ -72,9 +45,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
new file mode 100644
index 00000000..c3c27883
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusFNode.java
@@ -0,0 +1,32 @@
+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); }
+ public MinusFNode(MinusNode n) { super(n); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
index e84eb611..4d7e3ddb 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MinusNode.java
@@ -4,7 +4,6 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.simple.Parser;
import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.Utils;
import java.util.HashMap;
import java.util.IdentityHashMap;
@@ -15,10 +14,7 @@ public class MinusNode extends Node implements ModeNode {
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 byte mode() { return _mode; }
@Override public Node setMode(byte mode) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java
new file mode 100644
index 00000000..1126319b
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MulFNode.java
@@ -0,0 +1,37 @@
+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); }
+ public MulFNode(ArithNode n) { super(n); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
index bb99cad5..37d6a2bc 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MulNode.java
@@ -9,7 +9,6 @@ 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 op() { return "*"; }
@@ -36,7 +35,7 @@ public class MulNode extends ArithNode {
// canonicalize to (x*1)
long c = i.value();
if( c==1 ) return lhs._type.isa(compute()) ? lhs : super.idealize();
- if( c==0 ) return CodeGen.CODE.ZERO;
+ if( c==0 ) return com.seaofnodes.simple.Parser.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.
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
index 152d525a..b4d6d78e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/MultiNode.java
@@ -25,5 +25,5 @@ public interface MultiNode {
// Return not-null if this projection index is a ideal copy.
// Called by ProjNode ideal and used to collapse Multis.
- default Node pcopy(int idx, Node dep) { return null; }
+ default Node pcopy(int idx) { return null; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java
index ba1dab8c..66879b66 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NeverNode.java
@@ -11,8 +11,7 @@ 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(float ignore, Node ctrl) { super(ctrl,null); }
- @Override public Tag serialTag() { return Tag.Never; }
+ public NeverNode(Node ctrl) { super(ctrl,null); }
@Override public String label() { return "Never"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java
index fe80f690..bdcdfdfc 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NewNode.java
@@ -1,73 +1,79 @@
package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.Parser;
+
import com.seaofnodes.simple.codegen.*;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
import com.seaofnodes.simple.util.SB;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-/**
- * Allocation! Allocate a chunk of memory, no init.
- * Takes in ctrl and size, and TypeStruct.
- * Produces a ptr and private memory.
-*/
+import com.seaofnodes.simple.util.Utils;
+
+
+/** Allocate a zeroed object. Inputs {ctrl, $mem, size};
+ * results {ptr, $mem}.
+ * The memory input and result cover only the aliases in the allocated struct.
+ */
public class NewNode extends Node implements MultiNode {
- public TypeStruct _ts;
- public NewNode( TypeStruct ts, Node ctrl, Node size ) { super(ctrl,size); _ts = ts; }
- public Node ctrl() { return in(0); }
- public Node size() { return in(1); }
+ public TypeMemPtr _ptr;
- // --- Serialization
- public NewNode(NewNode nnn) { super(nnn); _ts = nnn._ts; }
- @Override public Tag serialTag() { return Tag.New; }
- @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
- baos.packed2(types.get(_ts));
- }
- static Node make( BAOS bais, Type[] types) {
- TypeStruct ts = (TypeStruct)types[bais.packed2()];
- return new NewNode(ts,null,null);
+ public NewNode(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;
}
- @Override public String label() { return "new_"+(_ts.isAry() ? "ary_"+_ts._fields[1]._t.str() : _ts.str()); }
+ public Node mem() { return in(1); }
+ public Node size() { return in(2); }
- @Override protected ExprPrinter<Node> _print1(ExprPrinter<Node> p) {
- return p.p("new ").p(_ts.str());
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ TypeMemPtr ptr = (TypeMemPtr)_ptr.upgradeType(types);
+ if( ptr==_ptr ) return false;
+ unlock();
+ _ptr = ptr;
+ return true;
}
- @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));
+ public Field field(int alias) {
+ for( Field f : _ptr._obj._fields )
+ if( !f._one && f._alias==alias ) return f;
+ return null;
}
- @Override public Node idealize() { return null; }
+ public NewNode(NewNode nnn) { super(nnn); _ptr = nnn._ptr; }
- @Override boolean _upgradeType( HashMap<String,Type> TYPES) {
- Type old = _ts; _ts = (TypeStruct)_ts.upgradeType(TYPES); return old != _ts;
- }
- // Two NewNodes are never equal; NewNodes always make independent pointers
- // so they are not functional - they internally bump-ptr allocate always a
- // new pointer.
- @Override public boolean eq(Node n) { return this==n; }
+ @Override public 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());
+ }
- @Override int hash() { return _ts.hashCode(); }
+ @Override
+ public TypeTuple compute() {
+ return in(0)._type.isHigh()
+ ? TypeTuple.make(_ptr.makeHigh(),TypeMem.TOP)
+ : TypeTuple.make(_ptr,TypeMem.BOT);
+ }
- @Override public Parser.ParseException err() {
- return _ts._fref ? Parser.error("Unknown struct type '"+_ts._name+"'",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 boolean eq(Node n) { return this == n; }
+
+ @Override
+ int hash() { return _ptr.hashCode(); }
// ------------
// MachNode specifics, shared across all CPUs
@@ -76,31 +82,30 @@ 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,code._cCallingConv).firstReg();
- _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0,code._cCallingConv);
+ _arg2Reg = code._mach.callArgMask(TypeFunPtr.CALLOC,2,0).firstReg();
+ _arg3Mask = code._mach.callArgMask(TypeFunPtr.CALLOC,3,0);
// 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._cCallerSave.copy();
+ RegMaskRW kills = 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 = cfg0().fun()._maxArgSlot;
// Killed slots for this calls outgoing args
- int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC,code._cCallingConv);
+ int xslot = code._mach.maxArgSlot(TypeFunPtr.CALLOC);
_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==1 ? _arg3Mask : null; }
+ public RegMask regmap(int i) { return i==2 ? _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("#malloc, ").p(code.reg(size()));
+ sb.p("#calloc, ").p(code.reg(size()));
}
-
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
index 77b43ba8..230593a8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java
@@ -4,11 +4,13 @@ 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.*;
-import com.seaofnodes.simple.util.*;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeFloat;
+import com.seaofnodes.simple.type.TypeInteger;
import java.util.*;
import java.util.function.Function;
import static com.seaofnodes.simple.codegen.CodeGen.CODE;
@@ -68,84 +70,8 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
_hash = 0;
}
- // Disk/serialized opcode tags
- public enum Tag {
- Add,And,BulkMemPhi,EQ,NE,LT,LE,ULT,
- CallEnd,Call,ConFldOff,Con,CLInit,CProj,
- Div,Escape,Extern,ExternOffset,Fun,FunPtr,Guard,
- If,Load,Loop,MemMerge,MemPhi,Minus,Mul,
- New,Never,Not,Or,Parm,Phi,Proj,PtrToInt,
- ReadOnly,Return,Region,RoundF32,
- Sar,Shl,Shr,Start,StartCU,StopCU,Stop,Store,Sub,
- ToInteger,ToFloat,XCtrl,Xor;
- 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 Guard -> GuardNode.make(bais);
- 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 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,strs);
- case CallEnd-> CallEndNode.make(bais ,types);
- case CLInit -> new CLInitNode(bais,strs);
- 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 String label() { return serialTag().toString(); }
+ public String label() { return getClass().getSimpleName().replace("Node",""); }
public String p(int depth) { return IRPrinter.prettyPrint(this,depth); }
@@ -252,8 +178,7 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
}
// Breaks the edge invariants, used temporarily
- @SuppressWarnings("unchecked")
- protected <N extends Node> N addUse(Node n) { _outputs.add(n); return (N)this; }
+ protected <N extends Node> void addUse(N n) { _outputs.add(n); }
// 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.
@@ -275,12 +200,6 @@ 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>
@@ -317,13 +236,11 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
// Shortcuts to stop DCE mid-parse
// Add bogus null use to keep node alive
- public <N extends Node> N keep() { return addUse(null); }
+ @SuppressWarnings("unchecked")
+ public <N extends Node> N keep() { addUse(null); return (N)this; }
// 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() {
@@ -343,6 +260,7 @@ 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);
@@ -413,13 +331,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 = CODE._midAssert ? null : CODE._obs;
+ public final Node peephole() {
+ var obs = CodeGen.CODE._midAssert ? null : CodeGen.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; // Cannot return null for no-progress
+ 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;
}
/**
@@ -445,9 +363,11 @@ 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( !isConst() && !(this instanceof ParmNode parm && parm.inProgress()) &&
- _type.isHighOrConst() )
+ // 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() )
return ConstantNode.make(_type).peephole();
// Global Value Numbering
@@ -515,8 +435,7 @@ 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: "+this+"#"+_nid+" old="+old+" new="+type;
- //assert !type.hasClosedBuilder0(); // New types should not have closed builders
+ assert old == null || type.isa(old) : "Monotonicity test failed";
if( old == type ) return old;
_type = type; // Set _type late for easier assert debugging
CODE.addAll(_outputs);
@@ -581,33 +500,28 @@ 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.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;
- }
+ public Node dep(int idx) { return _deps.get(idx); }
/**
* 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.
*/
- 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;
- }
+ <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 addDepForwards( N dep ) {
- if( !addDepImpl(dep) ) 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;
dep._deps.add(this);
return dep;
}
@@ -671,6 +585,26 @@ 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
@@ -722,7 +656,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)ConstantNode.make(TypeInteger.constant(x)).peephole(); }
+ public static ConstantNode con(long x) { return (ConstantNode)(new ConstantNode(TypeInteger.constant(x)).peephole()); }
// Utility to walk the entire graph applying a function; return the first
// not-null result.
@@ -743,20 +677,6 @@ 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
*/
@@ -770,4 +690,16 @@ public abstract class Node extends BaseNode<Node> implements Cloneable {
for( Node use : _outputs ) if( use != null && (x = use._find(nid,bs)) != null ) return x;
return null;
}
+
+ boolean _upgradeType(java.util.HashMap<String,Type> types) { return false; }
+ public final void upgradeType(java.util.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);
+ moveDepsToWorklist();
+ }
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java
index 2328c287..775b069b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/NotNode.java
@@ -6,7 +6,8 @@ import com.seaofnodes.simple.util.Utils;
public class NotNode extends Node {
public NotNode(Node in) { super(null, in); }
- @Override public Tag serialTag() { return Tag.Not; }
+
+ @Override public String label() { return "Not"; }
@Override protected String format() { return "(!%1)"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java
index 9d50d1a9..618c5340 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/OrNode.java
@@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger;
public class OrNode extends ArithNode {
public OrNode( Parser.Lexer loc, Node lhs, Node rhs) { super(loc,lhs, rhs, (byte)1); }
- @Override public Tag serialTag() { return Tag.Or; }
@Override public String label() { return "Or"; }
@Override public String op() { return "|"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
index 91c471c0..52fe5704 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ParmNode.java
@@ -2,55 +2,31 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.codegen.Serialize;
-import com.seaofnodes.simple.util.BAOS;
-
-import java.util.HashMap;
-import java.util.IdentityHashMap;
+import com.seaofnodes.simple.Parser;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.type.TypeInteger;
+import com.seaofnodes.simple.type.TypeMemPtr;
public class ParmNode extends PhiNode {
// Argument indices are mapped one-to-one on CallNode inputs
+ private Type _declaredType;
public final int _idx; // Argument index
- public Type _declaredType;
public ParmNode(String label, int idx, Type declaredType, Node... inputs) {
- super(label, 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(Serialize.nIns(this,anodes)); // Object-local linked calls
- baos.packed2(_label==null ? 0 : strs.get(_label));
- baos.packed2(types.get(_declaredType)); // NPE if fails lookup
- baos.packed1(_idx);
- }
- 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._declaredType ); _idx = parm._idx; _declaredType = parm._declaredType; }
@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( fun()._name!=null && fun()._name.endsWith("<clinit>") && _label.equals("arg") )
+ if( "main".equals(fun()._name) && _label.equals("arg") )
return p.p("arg");
p.p("Parm_").p(_label).open();
for( Node in : _inputs ) p.n(in).p(",");
@@ -63,42 +39,34 @@ public class ParmNode extends PhiNode {
@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);
- }
- }
+ Type t = inProgress() ? _declaredType : super.compute().join(_declaredType);
// Recursion is a loop in disguise. A Parm merges the first argument
// with those from recursive calls, just as a loop Phi merges its entry
// and backedge. Use the widening rule in PhiNode.compute here too.
// CallEndNode.compute handles the separate cycle through return values;
// widening this Parm alone cannot stop f(x-1)+1 from growing its result.
- if( t instanceof TypeInteger ti && !ti.isHigh() && !ti.isConstant() )
- t = ti.same_but_slightly_wider_than();
- return t.join(_declaredType);
+ if( t instanceof TypeInteger ti && t != _type &&
+ !ti.isHigh() && !ti.isConstant() &&
+ (!(_type instanceof TypeInteger oldi) || ti._widen <= oldi._widen) )
+ return ti.same_but_slightly_wider_than(_declaredType);
+ return t;
}
@Override
public Node idealize() {
if( !(region() instanceof FunNode) )
- // 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.
+ return 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( _declaredType instanceof TypeMemPtr tmp && _declaredType.isFRef() ) {
+ TypeMemPtr tmp2 = (TypeMemPtr) Parser.TYPES.get(tmp._obj._name);
+ if( tmp2!=null && tmp2 != _declaredType ) {
+ _declaredType = tmp2;
+ return this;
+ }
+ }
// Skip most phi optimizations on parms
return null;
}
@@ -106,4 +74,12 @@ public class ParmNode extends PhiNode {
@Override public boolean eq( Node n ) {
return ((ParmNode)n)._idx==_idx && super.eq(n);
}
+
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ Type next = _declaredType.upgradeType(types);
+ if( next==_declaredType ) return false;
+ unlock();
+ _declaredType=next;
+ return true;
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
index 3cf6f854..064852e4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PhiNode.java
@@ -3,31 +3,24 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
import com.seaofnodes.simple.*;
-import com.seaofnodes.simple.codegen.Serialize;
+import com.seaofnodes.simple.codegen.CodeGen;
import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.*;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
+import com.seaofnodes.simple.util.Utils;
public class PhiNode extends Node {
public final String _label;
- public PhiNode( String label, Node... inputs) {
+ int lattice_drop;
+
+ public PhiNode(String label, Node... inputs) {
super(inputs);
_label = label;
}
- 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) { super(phi); _label = label; }
+ public PhiNode(PhiNode phi, String label, Type type) { super(phi); _label = label; _type = type; }
// Used by instruction Selection
- public PhiNode(PhiNode phi) { this(phi,phi._label); }
+ public PhiNode(PhiNode phi) { this(phi,phi._label,phi._type ); }
// Used by the infinite-loop exit breaker
public PhiNode(RegionNode r, Node sample) {
super(new Node[]{r});
@@ -36,14 +29,9 @@ public class PhiNode extends Node {
while( nIns() < r.nIns() )
addDef(sample);
}
- @Override public Tag serialTag() { return Tag.Phi; }
- @Override public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) {
- baos.packed1(nIns());
- baos.packed2(_label==null ? 0 : strs.get(_label));
- }
- static Node make( BAOS bais, String[] strs, Type[] types) {
- Node[] ins = new Node[bais.packed1()];
- return new PhiNode(strs[bais.packed2()], ins);
+
+ public static PhiNode make(String label, Type type, Node... inputs) {
+ return type instanceof TypeMem ? new BulkMemPhiNode(label,inputs) : new PhiNode(label,inputs);
}
@Override public String label() { return "Phi_"+MemOpNode.mlabel(_label); }
@@ -59,17 +47,20 @@ public class PhiNode extends Node {
public CFGNode region() { return (CFGNode)in(0); }
@Override public boolean isMem() { return _type instanceof TypeMem; }
+ boolean isRPC() { return false; }
@Override
public Type compute() {
if( !(region() instanceof RegionNode r) )
- return Type.TOP;
+ 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 )
+ // The backedge has not been parsed. Declarations belong on
+ // Convert/Parm nodes; an unfinished value Phi knows no bound.
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;
@@ -79,6 +70,7 @@ public class PhiNode extends Node {
t = t.meet(in(i)._type);
}
}
+ Type newt = t;
// Widen loop Phis so integer ranges cannot grow one step per trip forever.
// Recursion is a loop in disguise: ParmNode.compute widens arguments,
@@ -86,75 +78,98 @@ public class PhiNode extends Node {
// recursive result can feed itself without passing through a Parm.
// Above-center ranges may still fall to unwidened constants.
// Widen only below-center ranges to preserve monotonicity.
- 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();
+ 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(TypeInteger.BOT);
}
- return t;
+ return newt;
}
@Override
public Node idealize() {
- RegionNode r = (RegionNode)region();
- if( r.inProgress() || nOuts()==0 )
+ if( !(region() instanceof RegionNode r ) )
+ return in(1); // Input has collapse to e.g. starting control.
+ if( r.inProgress() || inProgress() || r.nIns()<=1 )
return null; // Input is in-progress
// If we have only a single unique input, become it.
Node live = singleUniqueInput();
- if( live != null )
- return 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;
- // Generic "pull down op"
- Node progress;
- if( same_op() && (progress = drop_same_op()) != null )
- return progress;
+ // 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;
- // 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;
+ if( nIns()==3 ) {
+ Node merge = matchingGuardMerge(r,1);
+ if( merge!=null ) return merge;
+ merge = matchingGuardMerge(r,2);
+ if( merge!=null ) return merge;
+ }
+
+ // 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);
+ }
+ }
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 )
+ if( !(in(nzIdx) instanceof GuardNode guard && guard._nonZero) ||
+ zero==null || zero._type.makeZero()!=zero._type || guard.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;
+ Node n=guard.in(1), tested=null;
while( true ) {
- if( iff.pred()==n ) tested = n;
- if( !(n instanceof GuardNode guard && guard._nonZero) ) break;
- n = guard.in(1);
+ if( iff.pred()==n ) tested=n;
+ if( !(n instanceof GuardNode g && g._nonZero) ) break;
+ n=g.in(1);
}
- CFGNode early = CFGNode.earlyCFG(n);
- return tested!=null && (early==null || early.dominates(r,this)) ? n : null;
+ // The source must exist before the join, not just on its guarded arm.
+ return tested!=null && ScopeNode.availableAt(n,r,new java.util.BitSet()) ? 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 EscapeNode || op instanceof CFGNode || op instanceof ConstantNode || op instanceof PhiNode ||
+ if( 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;
@@ -206,7 +221,7 @@ public class PhiNode extends Node {
ins[i] = in(i).in(j);
t = t.meet(ins[i]._type);
}
- // Do not introduce an operand Phi mixing incompatible type families.
+ // Not accepts both pointers and integers, but a Phi cannot mix them.
if( t==Type.BOTTOM ) {
for( int i=1; i<ins.length; i++ ) addDep(ins[i]);
cp.kill();
@@ -214,7 +229,7 @@ public class PhiNode extends Node {
}
PhiNode phi = j==1 && op instanceof MemOpNode mem
? new MemPhiNode(_label,mem._alias,ins)
- : PhiNode.make(_label,t,ins);
+ : PhiNode.make(_label,t.glb(false),ins);
x = phi.peephole();
}
cp.addDef(x);
@@ -261,27 +276,31 @@ public class PhiNode extends Node {
}
// Never equal if inProgress.
- @Override public boolean eq( Node n ) {
- return !inProgress() && super.eq(n);
- }
+ // Also, joins
+ @Override public boolean eq(Node n) { return !inProgress(); }
@Override
public Parser.ParseException err() {
+ if( inProgress() ) return null;
+ // A scalar union is useful for truth tests, e.g. !(ptr || integer).
+ // A typed consumer reports an invalid use of that union itself.
if( _type != Type.BOTTOM ) return null;
- // Global BOTTOM retains the same role for non-scalar families.
+ // BOTTOM means we mixed e.g. int and ptr
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;
- 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);
+ // 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);
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java b/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java
deleted file mode 100644
index c8b9378f..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/Proj.java
+++ /dev/null
@@ -1,2 +0,0 @@
-package com.seaofnodes.simple.node;
-public interface Proj { int idx(); }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
index fcc113a8..92c6c2be 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ProjNode.java
@@ -2,15 +2,12 @@ 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 implements Proj {
+
+public class ProjNode extends Node {
// Which slice of the incoming multipart value
public final int _idx;
@@ -24,14 +21,6 @@ public class ProjNode extends Node implements Proj {
_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; }
@@ -55,17 +44,11 @@ public class ProjNode extends Node implements Proj {
return t instanceof TypeTuple tt ? tt._types[_idx] : t.oob();
}
- @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx,this); }
+ @Override public Node idealize() { return ((MultiNode)in(0)).pcopy(_idx); }
@Override
public boolean eq( Node n ) { return _idx == ((ProjNode)n)._idx; }
@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/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
index d3d38060..55c0fc02 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/PtrToIntNode.java
@@ -18,7 +18,6 @@ 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() {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
index 0a720d5b..94c1b6f6 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ReadOnlyNode.java
+++ b/chapter25/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 Tag serialTag() { return Tag.ReadOnly; }
+ @Override public String label() { return "ReadOnly"; }
@Override protected String format() { return "(const)%1"; }
@Override public Type compute() {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java
index 8883ba8e..59030dee 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/RegionNode.java
+++ b/chapter25/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 Tag serialTag() { return Tag.Region; }
- public void packed( BAOS baos, HashMap<String,Integer> strs, HashMap<Type,Integer> types, IdentityHashMap<Node, Integer> anodes ) { baos.packed1(nIns()); }
- static Node make( BAOS bais ) { return new RegionNode(null,new Node[bais.packed1()]); }
+
+ @Override
+ public String label() { return "Region"; }
@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,CodeGen.CODE.XCTRL);
+ setDef(i,Parser.XCTRL);
return this;
}
}
@@ -112,8 +112,7 @@ public class RegionNode extends CFGNode {
CodeGen.CODE.addAll(phi.delDef(path)._outputs);
}
}
- if( !isDead() ) delDef(path);
- return isDead() ? CodeGen.CODE.XCTRL : this;
+ return isDead() ? Parser.XCTRL : delDef(path);
}
private int findDeadInput(int startIdx) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
index 0431f7d5..8fea803d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ReturnNode.java
@@ -10,15 +10,7 @@ import com.seaofnodes.simple.type.*;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-/**
- * 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.
- */
+/** Keeps all preceding memory effects and the return value alive: {ctrl, $mem, value, rpc}. */
public class ReturnNode extends CFGNode {
public FunNode _fun;
@@ -28,7 +20,6 @@ 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); }
@@ -37,6 +28,9 @@ 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("----");
@@ -49,10 +43,13 @@ public class ReturnNode extends CFGNode {
@Override
public Type compute() {
- return TypeTuple.make(ctrl()._type,mem()._type,expr()._type);
+ 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);
}
@Override public Node idealize() {
+ if( inProgress () ) return null;
if( _fun.isDead() ) return null;
// Upgrade signature based on return type
@@ -61,47 +58,104 @@ 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.isMem();
+ PhiNode rez = (PhiNode)r.out(1); assert !rez.isMem();
+ // 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 &&
- 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);
- }
+ if( ctrl()._type != Type.CONTROL ) return null; // Exit path is dead
+ if( expr()._type==TypeScalar.BOT ) {
+ int kinds = returnKinds(expr(),new java.util.BitSet());
+ return mixerr((kinds&1)!=0,(kinds&2)!=0,(kinds&4)!=0,(kinds&8)!=0,_fun._loc);
}
- return null;
+ return expr()._type==Type.BOTTOM || expr()._type==Type.TOP || expr()._type==TypeScalar.BOT ? mixerr(ti,tf,tp,tn,_fun._loc) : null;
+ }
+
+ private static int returnKinds(Node n, java.util.BitSet visit) {
+ if( n==null || visit.get(n._nid) ) return 0;
+ visit.set(n._nid);
+ Type t=n._type;
+ if( t instanceof TypeInteger ) return 1;
+ if( t instanceof TypeFloat ) return 2;
+ if( t instanceof TypeMemPtr || t instanceof TypeFunPtr ) return 4;
+ if( t==Type.NIL ) return 8;
+ int kinds=0;
+ if( n instanceof PhiNode phi )
+ for( int i=1; i<n.nIns(); i++ )
+ if( phi.region().in(i)._type==Type.CONTROL ) kinds |= returnKinds(n.in(i),visit);
+ return kinds;
}
- @Override public boolean eq( Node n ) {
- return !_fun.isDead() && !((ReturnNode)n)._fun.isDead();
+ 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);
}
+
// ------------
// MachNode specifics, shared across all CPUs
public String op() {
- return _fun._frameAdjust > 0 ? "epilog" : "ret ";
+ return _fun._frameAdjust > 0 ? "addi" : "ret";
}
// Correct Nodes outside the normal edges
public void postSelect(CodeGen code) {
- _fun.setRet(this);
+ FunNode fun = (FunNode)rpc().in(0);
+ _fun = fun;
+ fun.setRet(this);
}
public RegMask regmap(int i) {
return i==2
@@ -113,14 +167,13 @@ public class ReturnNode extends CFGNode {
public void asm(CodeGen code, SB sb) {
int frameAdjust = fun()._frameAdjust;
if( frameAdjust>0 )
- sb.p("\n").p("rsp += #").p(frameAdjust).p("\nret ");
+ sb.p("rsp += #").p(frameAdjust).p("\nret");
// Post code-gen, just print the "ret"
- if( code._phase.ordinal() < CodeGen.Phase.RegAlloc.ordinal() ||
- (code._phase.ordinal() == CodeGen.Phase.RegAlloc.ordinal() && !code._regAlloc.done()) )
+ if( code._phase.ordinal() <= CodeGen.Phase.RegAlloc.ordinal() )
// 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))).p(" ");
+ sb.p(code.reg(in(i),fun())).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/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
index 65f10a79..10a0e4aa 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/RoundF32Node.java
@@ -7,7 +7,8 @@ import com.seaofnodes.simple.type.TypeFloat;
public class RoundF32Node extends Node {
public RoundF32Node(Node lhs) { super(null, lhs); }
- @Override public Tag serialTag() { return Tag.RoundF32; }
+
+ @Override public String label() { return "RoundF32"; }
@Override protected String format() { return "((f32)%1)"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
index 737b531f..54611b0d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SarNode.java
@@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger;
public class SarNode extends ArithNode {
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 op() { return ">>"; }
@Override long doOp( long x, long y ) { return x >> y; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
index 21d60d53..3c1e9776 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ScopeNode.java
@@ -1,21 +1,22 @@
package com.seaofnodes.simple.node;
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.CompUnit;
-import com.seaofnodes.simple.codegen.ParseAll;
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 variables.
+ * stack of variable records.
*/
-public class ScopeNode extends MemMergeNode {
+public class ScopeNode extends Node {
/**
* The control is a name that binds to the currently active control
@@ -25,38 +26,40 @@ public class ScopeNode extends MemMergeNode {
public static final String ARG0 = "arg";
public static final String MEM0 = "$mem";
- // All active/live variables in all nested scopes, all run together.
- // Maps 1-to-1 to the _inputs array.
+ 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
public final Ary<Var> _vars;
// Lexical scope is typed:
public static class Kind {
// Basic block scoping
- 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;
- }
+ public static class Block extends Kind { }
// Function scope
- public static class Func extends Kind { public Func(String name) { _name=name;} public final String _name; }
+ 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 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 cast results
+ // Extra guards; tested predicates and casted results
private final Ary<Node> _guards;
// A new ScopeNode
public ScopeNode() {
- super();
- _vars = new Ary<>(Var .class);
- _kinds = new Ary<>(Kind.class);
- _guards = new Ary<>(Node.class);
+ _type = Type.BOTTOM;
+ _vars = new Ary<>(Var .class);
+ _kinds = new Ary<>(Kind .class);
+ _guards = new Ary<>(Node .class);
}
@Override public String label() { return "Scope"; }
@@ -67,7 +70,7 @@ public class ScopeNode extends MemMergeNode {
int j=1;
for( int i=0; i<nIns(); i++ ) {
if( j < depth() && i == _kinds.at(j)._lexSize ) { p.p("| "); j++; }
- Var v = var(i);
+ Var v = _vars.at(i);
p.p(v._type());
p.p(" ");
if( v._final ) p.p("!");
@@ -86,7 +89,7 @@ public class ScopeNode extends MemMergeNode {
public Node ctrl() { return in(0); }
- public Node mem() { return in(1); }
+ public Node mem() { return in(lookup(MEM0)); }
public Var var(int i) { return _vars.at(i); }
/**
@@ -100,7 +103,7 @@ public class ScopeNode extends MemMergeNode {
* @return Node that was bound
*/
public <N extends Node> N ctrl(N n) { return setDef(0,n); }
- public Node mem(Node n) { return setDef(1,n); }
+ public Node mem(Node n) { update(MEM0,n); return n; }
public void push( Kind kind ) {
kind._lexSize = _vars.size();
@@ -109,11 +112,7 @@ public class ScopeNode extends MemMergeNode {
// Pop a lexical scope
public void pop() {
- promote(null); // Promote forward references to the next outer scope
- _pop();
- }
- // Pop a scope, no promote FRefs
- public void _pop() {
+ promote(); // Promote forward references to the next outer scope
int n = _kinds.pop()._lexSize;
popUntil(n); // Pop off inputs going out of scope
_vars.setLen(n); // Pop off variables going out of scope
@@ -122,86 +121,69 @@ public class ScopeNode extends MemMergeNode {
// Look for forward references in the last lexical scope and promote to the
// next outer lexical scope. At the last scope declare them an error.
- public void promote(CodeGen code) {
- Kind kind = klast();
+ public void promote() {
+ Kind kind = _kinds.last();
int n = kind._lexSize;
for( int i=n; i<nIns(); i++ ) {
Var v = var(i);
- Node vn = in(i);
if( !v.isFRef() ) continue;
- // 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;
- }
+ if( depth()==1 )
+ throw Parser.error("Undefined name '" + v._name + "'",v._loc);
+ _vars .swap(n,i);
+ _inputs.swap(n,i);
+ v._idx = n;
kind._lexSize = ++n;
}
}
- 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;
+ // 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;
}
- // Find nearest enclosing function scope
- public int enclosingFunction() {
- for( int i=depth()-1; i>=0; i-- )
- if( _kinds.at(i) instanceof Kind.Func func && !FunNode.isInstance(func._name) )
- return i;
- throw Utils.TODO("Should not reach here");
- }
+ 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 ; }
- // 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");
- }
-
- // Which lexical scope contains this Var?
- public int kindx( Var v ) {
+ public Kind kind( Var v ) {
for( int i=depth()-1; i>=0; i-- )
if( v._idx >= _kinds.at(i)._lexSize )
- return i;
- return -1;
+ return _kinds.at(i);
+ throw Utils.TODO();
}
- // Which lexical scope contains this Var?
- public Kind kind( Var v ) {
- int kx = kindx(v);
- return kx == -1 ? null : _kinds.at(kx);
- }
+ // 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.
- 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");
- }
+ // 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.
+
+ // - Walking out of a method and into the wrong struct is an error;
+ // including nested structs containing (no nested classes yet)
- // Which function scope contains this Var?
- public Kind kindFcn( Var v ) {
- int kx = kindFcnx(v);
- return kx == -1 ? null : _kinds.at(kx);
+ // Returns error, forward-ref, ok containing struct, ok containing function/block.
+
+ 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();
}
+
// Find name in reverse, return an index into _vars or -1. Linear scan
// instead of hashtable, but probably doesn't matter until the scan
// typically hits many dozens of variables.
@@ -216,9 +198,9 @@ public class ScopeNode extends MemMergeNode {
* Create a new variable name in the current scope
*/
public boolean define( String name, Type declaredType, boolean xfinal, Node init, Parser.Lexer loc ) {
- assert _kinds.isEmpty() || name!=MEM0 ; // Later scopes do not define memory
+ assert _kinds.isEmpty() || name.charAt(0)!='$' ; // Later scopes do not define memory
if( depth() > 0 )
- for( int i=_vars.size()-1; i>=klast()._lexSize; i-- ) {
+ for( int i=_vars.size()-1; i>=_kinds.last()._lexSize; i-- ) {
Var n = var(i);
if( n._name.equals(name) ) {
if( !n.isFRef() ) return false; // Double define
@@ -226,45 +208,17 @@ public class ScopeNode extends MemMergeNode {
if( !xfinal || !declaredType.isConstant() ) throw fref.err(); // Must be a final constant
n.defFRef(declaredType,xfinal,loc); // Declare full correct type, final, source location
setDef(n._idx,fref.addDef(init)); // Set FRef to defined; tell parser also
- return true;
}
}
- Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==CodeGen.CODE.XCTRL);
+ Var v = new Var(nIns(),name,declaredType,xfinal,loc,init==Parser.XCTRL);
_vars.add(v);
// Creating a forward reference
- if( init==CodeGen.CODE.XCTRL )
- //init = new FRefNode(v).init();
- throw Utils.TODO();
+ if( init==Parser.XCTRL )
+ init = new FRefNode(v).init();
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.
*
@@ -300,7 +254,7 @@ public class ScopeNode extends MemMergeNode {
// Set real Phi in the loop head
// The phi takes its one input (no backedge yet) from a recursive
// lookup, which might have insert a Phi in every loop nest.
- : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).init());
+ : loop.setDef(v._idx,PhiNode.make(v._name, v.type(), loop.ctrl(), loop.in(loop.update(v,null)._idx),null).peephole());
setDef(v._idx,old);
}
//assert !v._final || st==null;
@@ -338,15 +292,9 @@ public class ScopeNode extends MemMergeNode {
n.keep();
dup.addDef(ctrl()); // Control input is just copied
- // 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));
+ // 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));
return dup;
}
@@ -360,17 +308,14 @@ public class ScopeNode extends MemMergeNode {
*/
public RegionNode mergeScopes(ScopeNode that, Parser.Lexer loc) {
RegionNode r = ctrl(new RegionNode(loc,null,ctrl(), that.ctrl()).keep());
- this._merge(that,r);
+ _merge(that,r);
that.kill(); // Kill merged scope
CodeGen.CODE.add(r);
return r.unkeep();
}
- 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++)
+ private void _merge(ScopeNode that, RegionNode r) {
+ for( int i = 1; i < nIns(); 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
@@ -383,38 +328,18 @@ public class ScopeNode extends MemMergeNode {
// 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 ) {
- 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++ ) {
+ for( int i = nIns(); i < scope.nIns(); i++ ) {
Var n = scope.var(i);
- if( i<nIns() && var(i)==n ) continue;
if( n.isFRef() ) { // RHS has forward refs
- _vars.insert(n,i);
- insertDef(i,scope.in(i));
+ _vars.add(n); // Copy to LHS
+ addDef(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 ) {
@@ -422,108 +347,49 @@ public class ScopeNode extends MemMergeNode {
assert ctrl instanceof LoopNode loop && loop.inProgress();
ctrl.setDef(2,back.ctrl());
- this._endLoop( this, back, exit);
+ _endLoop(this,back,exit);
back.kill(); // Loop backedge is dead
// Now one-time do a useless-phi removal
- this._useless();
+ _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( 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;
+ 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);
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,n);
+ 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
+ }
+ }
}
}
- // ------------------------------------------------
// 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.
- //
- // 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 ) {
+ public void addGuards( Node ctrl, Node pred, boolean invert ) {
+ assert ctrl instanceof CFGNode;
_guards.add(ctrl); // Marker between guard sets
// add pred & its cast to the normal input list, with special Vars
if( pred==null || pred.isDead() )
@@ -531,11 +397,10 @@ public class ScopeNode extends MemMergeNode {
_addGuards(ctrl,pred,invert);
}
- private void _addGuards( CFGNode ctrl, Node pred, boolean invert ) {
+ private void _addGuards( Node ctrl, Node pred, boolean invert ) {
if( pred==null || pred.isDead() )
return;
- CFGNode early = CFGNode.earlyCFG(pred);
- if( early != null && !early.dominates(ctrl,null) )
+ if( !availableAt(pred,(CFGNode)ctrl,new BitSet()) )
return;
// A single negation is handled below. For !!p (including p != null)
// or a negated short-circuit Phi, also discover the underlying facts.
@@ -545,10 +410,8 @@ public class ScopeNode extends MemMergeNode {
_addGuards(ctrl,not.in(1),!invert);
pred.unkeep();
}
- // 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.
+ // A false || or true && proves both operands. Only guard values
+ // available at this control, not ones confined to the RHS branch.
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 ) {
@@ -562,19 +425,31 @@ public class ScopeNode extends MemMergeNode {
// Invert the If conditional
if( invert )
pred = pred instanceof NotNode not ? not.in(1) : CodeGen.CODE.add(new NotNode(pred).peephole());
- Node zeroPred = pred instanceof NotNode not ? not.in(1) : null;
// This is a zero/null test.
// Compute the positive test type.
_addGuard(true,ctrl,pred);
-
- // Compute the negative test type.
- if( zeroPred != null )
- _addGuard(false,ctrl,zeroPred);
+ if( pred instanceof NotNode not )
+ _addGuard(false,ctrl,not.in(1));
+ }
+
+ // A Phi's value is available at its Region. Other data nodes require all
+ // their inputs, including pinned control, to dominate the proposed guard.
+ 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;
}
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 )
@@ -587,19 +462,16 @@ public class ScopeNode extends MemMergeNode {
}
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(guard);
- replace(pred,guard,nonZero);
- }
+ Node guard = new GuardNode(nonZero,ctrl,pred.keep()).peephole();
+ if( guard==pred ) { pred.unkeep(); return; }
+ guard.keep();
+ _guards.add(pred);
+ _guards.add(guard);
+ replace(pred,guard);
}
-
// Remove matching pred/cast pairs from this guarded region.
- public void removeGuards( Node ctrl ) {
+ public ScopeNode removeGuards( Node ctrl ) {
assert ctrl instanceof CFGNode;
// Pop the guards up to this region's marker.
while( true ) {
@@ -609,6 +481,7 @@ public class ScopeNode extends MemMergeNode {
g .unkill(); // Pop/kill cast
_guards.pop().unkill(); // Pop/kill pred
}
+ return this;
}
// If we find a guarded instance of pred, replace with the upcasted version
@@ -638,65 +511,11 @@ public class ScopeNode extends MemMergeNode {
}
- private void replace( Node old, Node cast, boolean nonZero ) {
+ private void replace( Node old, Node cast ) {
assert old!=null && old!=cast;
for( int i=0; i<nIns(); i++ )
- if( in(i)==old || nonZero && nullCheckedPhi(in(i),old) )
+ if( 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/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
index f657cf51..7d6e8e2e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShlNode.java
@@ -7,7 +7,6 @@ import com.seaofnodes.simple.type.TypeInteger;
public class ShlNode extends ArithNode {
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 op() { return "<<"; }
@Override long doOp( long x, long y ) { return x << y; }
@@ -37,7 +36,7 @@ public class ShlNode extends ArithNode {
if( lhs instanceof AddNode add && addDep(add).in(2)._type instanceof TypeInteger c && c.isConstant() ) {
long sum = c.value() << shl.value();
if( Integer.MIN_VALUE <= sum && sum <= Integer.MAX_VALUE )
- return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), CodeGen.CODE.con(sum) );
+ return new AddNode(new ShlNode(_loc,add.in(1),rhs).peephole(), com.seaofnodes.simple.Parser.con(sum) );
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
index e93ea0e8..4ce5f5b4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ShrNode.java
@@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger;
public class ShrNode extends ArithNode {
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 op() { return ">>>"; }
@Override long doOp( long x, long y ) { return x >>> y; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
index 3042f857..a7fe6c5f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SplitNode.java
@@ -3,16 +3,13 @@ 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; // Debugging info for which RegAlloc round
- public SplitNode(LRG lrg,String kind, byte round, Node[] nodes) { super(nodes); _lrg=lrg; _kind = kind; _round = round; }
+ public final byte _round;
+ public SplitNode(String kind, byte round, Node[] nodes) { super(nodes); _kind = kind; _round = round; }
// A register-only transfer may need another split to reach stack storage.
public boolean isNonStackSplit() { return false; }
@Override public String op() { return "mov"; }
@@ -22,7 +19,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
deleted file mode 100644
index cd51f603..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StartCUNode.java
+++ /dev/null
@@ -1,33 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
index 495be0a2..e13d35c0 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StartNode.java
@@ -5,76 +5,39 @@ 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;
-/**
- * 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.
- */
+/** Start supplies control, whole memory, and the argument, in that order. */
public class StartNode extends LoopNode implements MultiNode {
final Type _arg;
- public StartNode(StartNode start, StopNode stop, Type arg) { super(null,start,stop); _arg = arg; }
+ public StartNode(Type arg) { super(null,null); _arg = arg; _type = compute(); }
public StartNode(StartNode start) { super(start); _arg = start==null ? null : start._arg; }
- @Override public Tag serialTag() { return Tag.Start; }
- public Type arg() { return _arg; }
+
+ @Override public String label() { return "Start"; }
@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, it's the class init for
- // this compilation unit.
+ // IfNode or other multi-way branch. For Start, its "main"
@Override public CFGNode uctrl() {
- // Find module.<clinit>, it's the start.
+ // Find "main", it's the start.
CFGNode C = null;
for( Node use : _outputs )
- if( use instanceof FunNode fun && CodeGen.CODE.entryClinitName().equals(fun._name) )
+ if( use instanceof FunNode fun && fun.sig().isa(CodeGen.CODE._main) )
{ assert C==null; C = fun; }
- if( C==null )
- for( FunNode fun : CodeGen.CODE._linker )
- if( fun!=null && !fun.isDead() && CodeGen.CODE.entryClinitName().equals(fun._name) )
- { 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() {
- 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);
+ return TypeTuple.make(Type.CONTROL,TypeMem.BOT,_arg);
}
@Override public Node idealize() { return null; }
@@ -83,5 +46,4 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
deleted file mode 100644
index d3a356ed..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopCUNode.java
+++ /dev/null
@@ -1,124 +0,0 @@
-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.ordinal() >= CodeGen.Phase.Opto.ordinal() &&
- CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.Lower.ordinal() &&
- _type instanceof TypeTuple old && old.mem() instanceof TypeMem omem )
- xmem = xmem.makeFrom(XInt.meet(xmem._escFs,omem._escFs),
- XInt.meet(xmem._escAs,omem._escAs));
-
- 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.ordinal() < CodeGen.Phase.Opto.ordinal() ||
- CodeGen.CODE._phase.ordinal() > CodeGen.Phase.Lower.ordinal() ) return mem;
- int[] escFs = XInt.isHigh(mem._escFs)
- ? XInt.meet(prior._escFs,CodeGen.CODE.publicFIDXs())
- : mem._escFs;
- 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/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
index a7e52563..9f7a925b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StopNode.java
@@ -2,46 +2,34 @@ package com.seaofnodes.simple.node;
import com.seaofnodes.print.ExprPrinter;
-import com.seaofnodes.simple.codegen.CodeGen;
-import com.seaofnodes.simple.codegen.Serialize;
-import com.seaofnodes.simple.type.*;
-import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.type.Type;
+import com.seaofnodes.simple.util.Utils;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
-
-// A collection of function ReturnNodes for a compilation unit.
public class StopNode extends CFGNode {
- public StopNode() {
+ public final String _src;
+
+ public StopNode(String src) {
super();
+ _src = src;
_type = compute();
}
- 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(Serialize.nIns(this,anodes)); }
- static Node make( BAOS bais ) {
- StopNode stop = new StopNode();
- stop.setDefX(bais.packed1()-1,null);
- return stop;
+ public StopNode(StopNode stop) { super(stop); _src = stop==null ? null : stop._src; }
+
+ @Override
+ public String label() {
+ 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 = null;
- for( Node n : _inputs ) {
- if( n instanceof ReturnNode ret ) {
- if( ret1 != null ) { ret1=null; break; }
- ret1 = ret;
- }
- }
+ ReturnNode ret1 = ret();
if( ret1!=null ) return p.n(ret1);
-
p.p("Stop[ ");
- for( Node n : _inputs )
- if( n instanceof ReturnNode ret ) {
- String name = ret.fun()._name;
+ for( Node ret : _inputs )
+ if( ret!=null ) {
+ String name = ((ReturnNode)ret).fun()._name;
if( name== null || !name.startsWith("sys.") )
p.n(ret).p(" ");
}
@@ -54,36 +42,25 @@ public class StopNode extends CFGNode {
// If a single Return, return it.
// Otherwise, null because ambiguous.
public ReturnNode ret() {
- 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;
- }
+ 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;
}
- return ret1;
+ return ret1==this ? null : (ReturnNode)ret1;
}
@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;
-
- // 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;
+ public Type compute() {
+ return Type.BOTTOM;
}
- @Override public Node idealize() {
+ @Override
+ public Node idealize() {
int len = nIns();
for( int i=0; i<nIns(); i++ )
- if( in(i).nIns()==0 )
+ if( addDep(((ReturnNode)in(i)).fun()).isDead() )
delDef(i--);
if( len != nIns() ) return this;
return null;
@@ -93,10 +70,7 @@ public class StopNode extends CFGNode {
if( validIDepth() ) return _idepth;
int d=0;
for( Node ret : _inputs )
- // 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);
+ d = Math.max(d,((ReturnNode)ret).idepth()+1);
return cacheIDepth(d);
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
index cc958348..a4abaa1d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StoreNode.java
@@ -2,14 +2,9 @@ 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
@@ -17,10 +12,7 @@ import java.util.IdentityHashMap;
*/
public class StoreNode extends MemOpNode {
- 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;
+ private final boolean _init; // Initializing writes are allowed to write null
/**
* @param name The struct field we are assigning to
@@ -29,43 +21,20 @@ 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 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);
+ 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);
_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) {
@@ -74,119 +43,52 @@ public class StoreNode extends MemOpNode {
@Override
public Type compute() {
- 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); }
- // A singleton class's final constant field lives in the static image.
- // Its precise memory slice is a constant too; ordinary peepholes can
- // replace this Store before scheduling or instruction selection.
- // A constant runtime value alone is not enough: mutable fields (notably
- // $init) still need their writes, and instance fields need allocation.
- Field fld = ptr._obj.field(_name);
- if( _alias!=1 && ptr._one && Parser.startsClzPrefix(ptr._obj._name) &&
- fld!=null && fld._final && decl.isConstant() )
- return TypeMem.make(_alias,val,true,true,true,mem._escFs,mem._escAs).escapesFrom(val);
-
- // Allocation uses a private TypeMem and nothing else does. This
- // memory is truly private; a temporary singleton until it escapes -
- // which is never does in a constructor.
- 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.
- boolean xfinal = fld != null && fld._final;
- return TypeMem.make(_alias,t,mem._one,mem._clz,xfinal,mem._escFs,mem._escAs).escapesFrom(val);
+ if( _alias==1 ) return TypeMem.BOT;
+ Type val = val()._type;
+ assert !_declaredType.isFRef();
+ Type t = val.join(_declaredType).meet(MemMergeNode.contents(mem(),_alias,this));
+ return TypeMem.make(_alias,t);
}
@Override
public Node idealize() {
- // 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 the cached hash.
- return init();
+ if( _alias==1 ) {
+ if( !(ptr()._type instanceof TypeMemPtr ptr) || ptr._obj._open ) return null;
+ Field field = ptr._obj.field(_name);
+ if( field==null ) return null;
+ Node value = new ConvertNode(field._t,val()).peephole();
+ StoreNode store = new StoreNode(_loc,_name,field._alias,field._t,mem(),ptr(),off(),value,_init);
+ store.setDef(0,in(0));
+ return new MemMergeNode(mem(),field._alias,store.peephole());
}
-
- assert !(mem()._type instanceof TypeMem mem) || _alias == 1 || mem._alias == 1 || _alias == mem._alias;
-
- // 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));
+ if( mem() instanceof MemMergeNode merge ) {
+ setDef(1,CodeGen.CODE.add(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();
+ if( mem() instanceof CastNode cast ) {
+ setDef(1,cast.in(1));
+ return this;
}
// 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() && // Must check same object
- off()==st.off() && // And same offset (could be "same alias" but this handles arrays to same index)
+ if( mem() instanceof StoreNode st &&
+ ptr()==st.ptr() && _alias==st._alias && // Must check same object
+ off()==st.off() && // And same offset
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==st._name; // Equiv class aliasing is perfect
+ assert _name.equals(st._name); // Equiv class aliasing is perfect
setDef(1,st.mem());
return this;
}
// Value is automatically truncated by narrow store
- if( _size!=0 && val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
- int log = log_size();
+ if( val() instanceof AndNode and && and.in(2)._type.isConstant() ) {
+ int log = _declaredType.log_size();
if( log<3 ) { // And-mask vs narrow store
long mask = ((TypeInteger)and.in(2)._type).value();
long bits = (1L<<(8<<log))-1;
@@ -198,7 +100,7 @@ public class StoreNode extends MemOpNode {
}
// Store will chop high order bits off; math to change those bits can be dropped.
- if( _size!=0 && val() instanceof SarNode shr &&
+ if( val() instanceof SarNode shr &&
shr.in(1) instanceof ShlNode shl &&
shr.in(2)._type.isConstant() &&
shl.in(2)._type.isConstant() ) {
@@ -214,56 +116,15 @@ 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 || _alias==1 || _size==0 || _init ) return null;
+ if( nOuts()!=1 ) 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.
@@ -287,9 +148,9 @@ public class StoreNode extends MemOpNode {
if( ctrl==null ) return null;
Node mem = phi.in(1);
- Node init = new LoadNode(_loc,_name,_alias,null,mem,ptr(),off()).peephole();
+ Node init = new LoadNode(_loc,_name,_alias,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);
+ Node sink = new StoreNode(_loc,_name,_alias,_declaredType,mem,ptr(),off(),value,_init);
// 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();
@@ -313,12 +174,11 @@ public class StoreNode extends MemOpNode {
public Parser.ParseException err() {
Parser.ParseException err = super.err();
if( err != null ) return err;
- assert _size>0 || CodeGen.CODE._phase.ordinal() <= CodeGen.Phase.FinalizeIR.ordinal();
- if( ptr()._type == Type.TOP ) // Dead store?
- return null;
- TypeMemPtr tmp = (TypeMemPtr)ptr()._type;
- Field f = tmp._obj.field(_name);
- if( f!=null && (tmp._ro || f._final) && !_init )
+ if( !(ptr()._type instanceof TypeMemPtr tmp) )
+ return null; // Report the bad or unresolved receiver at its source.
+ Field field = tmp._obj.field(_name);
+ if( field==null ) return Parser.error("Cannot resolve field '"+_name+"'",_loc);
+ if( (tmp._ro || field._final) && !_init )
return Parser.error("Cannot modify final field '"+_name+"'",_loc);
return null;
}
@@ -331,5 +191,4 @@ public class StoreNode extends MemOpNode {
// Identity also disables generic Phi factoring of Stores.
// The closed-loop Store sink is a separate peep and still applies.
@Override public boolean eq(Node n) { return this == n; }
- @Override int hash() { return super.hash() ^ _size; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java
new file mode 100644
index 00000000..a91ee5de
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/StructNode.java
@@ -0,0 +1,56 @@
+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 TypeStruct _ts;
+ public StructNode(TypeStruct ts) { _ts=ts; assert !ts._open; }
+
+ @Override boolean _upgradeType(java.util.HashMap<String,Type> types) {
+ if( _ts==null ) return false;
+ TypeStruct ts = (TypeStruct)_ts.upgradeType(types);
+ if( ts==_ts ) return false;
+ unlock();
+ _ts = ts;
+ return true;
+ }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java
new file mode 100644
index 00000000..d6a908ca
--- /dev/null
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SubFNode.java
@@ -0,0 +1,28 @@
+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); }
+ public SubFNode(ArithNode n) { super(n); }
+
+ @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/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
index 8eb1f4cc..c47ffb3b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/SubNode.java
@@ -9,7 +9,6 @@ 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 op() { return "-"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
index 151a326e..840a95ec 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToFloatNode.java
@@ -8,7 +8,8 @@ import com.seaofnodes.simple.type.TypeInteger;
public class ToFloatNode extends Node {
public ToFloatNode(Node lhs) { super(null, lhs); }
- @Override public Tag serialTag() { return Tag.ToFloat; }
+
+ @Override public String label() { return "ToFloat"; }
@Override protected String format() { return "(flt)%1"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
index 33806300..df740202 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java
@@ -7,7 +7,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java
deleted file mode 100644
index 3ab2d6f9..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/TypeNode.java
+++ /dev/null
@@ -1,33 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
index 32e1deb9..477b9af8 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/XCtrlNode.java
+++ b/chapter25/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 Tag serialTag() { return Tag.XCtrl; }
+ @Override public String label() { return "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/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
index c825f396..e3dac2d0 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/XorNode.java
@@ -6,7 +6,6 @@ import com.seaofnodes.simple.type.TypeInteger;
public class XorNode extends ArithNode {
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 op() { return "^"; }
@Override long doOp( long x, long y ) { return x ^ y; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
index 31bbde86..127f17ff 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/BranchARM.java
@@ -9,11 +9,9 @@ import com.seaofnodes.simple.util.Utils;
// Jump on flags, uses flags
public class BranchARM extends IfNode implements MachNode, RIPRelSize {
String _bop;
- final boolean _fp;
- BranchARM(IfNode iff, String bop, boolean fp ) {
+ BranchARM(IfNode iff, String bop ) {
super(iff);
_bop = bop;
- _fp = fp;
}
@Override public String op() { return "j"+_bop; }
@Override public String label() { return op(); }
@@ -38,7 +36,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, _fp)) );
+ enc.add4( Arm64.b_cond(Arm64.OP_BRANCH, 0, Arm64.make_condition(_bop)) );
} else {
if( _bop=="!=" ) return; // Inverted, no code
enc.add4(Arm64.b(Arm64.OP_UJMP, 0));
@@ -58,7 +56,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, _fp)));
+ enc.patch4(opStart,Arm64.b_cond(Arm64.OP_BRANCH, delta, Arm64.make_condition(_bop)));
} else {
throw Utils.TODO();
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
index 4de28f3a..b6d82f09 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallARM.java
@@ -14,7 +14,8 @@ public class CallARM extends CallNode implements MachNode, RIPRelSize {
_inputs.pop(); // Pop constant target
assert tfp.isConstant();
_tfp = tfp;
- _name = CodeGen.CODE.funcName(tfp.fidx());
+ FunNode fun = CodeGen.CODE.link(tfp);
+ _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name;
}
@Override public String op() { return "call"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
index db2540f7..dc21e075 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FltARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/FloatARM.java
@@ -4,22 +4,21 @@ 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 FltARM extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- FltARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
+public class FloatARM extends ConstantNode implements MachNode, RIPRelSize {
+ FloatARM(ConstantNode con) { super(con); }
@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 FltARM copy() { return new FltARM(this,_ext); }
+ @Override public FloatARM copy() { return new FloatARM(this); }
@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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
index 72b76edd..7a9e74c6 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/IntARM.java
@@ -7,22 +7,19 @@ 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 {
- final String _ext;
- IntARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ IntARM( ConstantNode con ) { super(con); }
@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,_ext); }
+ @Override public IntARM copy() { return new IntARM(this); }
@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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
index 53494092..8fc22a67 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/LoadARM.java
+++ b/chapter25/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[_con.log_size()], this, size());
+ ldst_encode(enc, OP_LOADS[Integer.numberOfTrailingZeros(size())], OP_LOAD_RS[_declaredType.log_size()], this, size());
}
@Override public void asm(CodeGen code, SB sb) {
sb.p(code.reg(this)).p(",");
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
index 14eaf510..dfbe5e06 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/MemOpARM.java
+++ b/chapter25/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 int _bytes;
- final char _sz;
+ final char _sz = (char)('0'+(1<<_declaredType.log_size()));
MemOpARM(MemOpNode mop, Node ptr, Node idx, int off, int imm) {
super(mop,mop);
assert ptr._type instanceof TypeMemPtr && !(ptr instanceof AddNode);
@@ -22,8 +22,6 @@ 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
@@ -37,10 +35,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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
- int size() { return _bytes; }
+ int size() { return 1<<_declaredType.log_size(); }
// Wider mask to store both GPRs and FPRs
@Override public RegMask regmap(int i) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
index 8bb8983e..7110df62 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/NewARM.java
@@ -32,8 +32,7 @@ public class NewARM extends NewNode implements MachNode, RIPRelSize {
}
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("\n"); // Multi-op trigger
- sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
+ sb.p("ldi x0=#1\n");
sb.p("call #calloc");
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
index e5b06c39..afbbd3bf 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SetARM.java
@@ -9,14 +9,12 @@ 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 <,<=,==
- final boolean _fp; // FP compare (unsigned encoding)
- SetARM(Node cmp, String bop, boolean fp) {
+ SetARM(Node cmp, String bop) {
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; }
@@ -24,7 +22,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, _fp), 0b011111, enc.reg(this));
+ int body = Arm64.cset(Arm64.OP_CSET,0b11111, Arm64.make_condition(_bop), 0b011111, enc.reg(this));
enc.add4(body);
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
index dcfeb5d8..f93f0f24 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitARM.java
+++ b/chapter25/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(LRG lrg, String kind, byte round) { super(lrg,kind,round,new Node[2]);}
+ SplitARM(String kind, byte round) { super(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 = fun(enc).computeStackOffset(enc._code,dst);
+ int off = enc._fun.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 = fun(enc).computeStackOffset(enc._code,src);
+ int off = enc._fun.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,4 +57,10 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
index 25e5d501..3e045a6f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitFlagsARM.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.codegen.*;
// NZCV transfers require a GPR. Further splits can spill that GPR to the stack.
public class SplitFlagsARM extends SplitARM {
private static final RegMask MASK = new RegMask(arm.WR_BITS,1L);
- SplitFlagsARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ SplitFlagsARM(String kind,byte round) { super(kind,round); }
@Override public boolean isNonStackSplit() { return true; }
@Override public RegMask regmap(int i) { return MASK; }
@Override public RegMask outregmap() { return MASK; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
index c9b2127a..46eff4ca 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/SplitGPRARM.java
@@ -5,7 +5,7 @@ import com.seaofnodes.simple.codegen.*;
// Keep a saved NZCV value in GPRs and stack slots through subsequent spills.
public class SplitGPRARM extends SplitARM {
private static final RegMask MASK = new RegMask(arm.WR_BITS,-2L);
- SplitGPRARM(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ SplitGPRARM(String kind,byte round) { super(kind,round); }
@Override public RegMask regmap(int i) { return MASK; }
@Override public RegMask outregmap() { return MASK; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
index 0b90ac8b..2cc54f76 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/StoreARM.java
+++ b/chapter25/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[Integer.numberOfTrailingZeros(_bytes)], val(), size());
+ ldst_encode(enc, OP_STORES[Integer.numberOfTrailingZeros(size())], OP_STORE_RS[_declaredType.log_size()], val(), size());
}
@Override public void asm(CodeGen code, SB sb) {
asm_address(code,sb).p(",");
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
index a053ba46..2c41dc57 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TFPARM.java
@@ -2,30 +2,28 @@ package com.seaofnodes.simple.node.cpus.arm;
import com.seaofnodes.isa.Arm64;
import com.seaofnodes.simple.codegen.*;
-import com.seaofnodes.simple.node.*;
+import com.seaofnodes.simple.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-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; }
+public class TFPARM extends ConstantNode implements MachNode, RIPRelSize {
+ TFPARM( ConstantNode con ) { super(con); }
@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((TypeFunPtr)_con,_ext); }
+ @Override public TFPARM copy() { return new TFPARM(this); }
@Override public void encoding( Encoding enc ) {
- if( _ext==null ) enc.relo(this); // Internal patch
- else enc.external(this,_ext); // ELF-file external patch
- short dst = enc.reg(this);
+ enc.relo(this);
+ short self = enc.reg(this);
// adrp x0, 0
- enc.add4(Arm64.adrp(1,0, Arm64.OP_ADRP, 0,dst));
+ int adrp = Arm64.adrp(1,0, Arm64.OP_ADRP, 0,self);
// add x0, x0, 0
- arm.imm_inst(enc,Arm64.OPI_ADD,0, dst);
+ enc.add4(adrp);
+ arm.imm_inst(enc,Arm64.OPI_ADD,0, 0);
}
@Override public byte encSize(int delta) {
@@ -36,7 +34,16 @@ public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short rpc = enc.reg(this);
if(opLen == 8 ) {
- arm.patch_adrp_add(enc, opStart, delta, rpc);
+ // ARM encoding delta is from PC & 0xFFF
+ int target = opStart+delta;
+ int base = opStart & ~0xFFF;
+ delta = target-base;
+ int adrp_delta = delta >> 12;
+ // ADRP splits a signed page offset into two unsigned bit fields.
+ // Mask the high field too, including for an address below this PC.
+ enc.patch4(opStart, Arm64.adrp(1, adrp_delta & 0b11, 0b10000, (adrp_delta >> 2) & 0x7FFFF, rpc));
+ // low 12 bits via add
+ enc.patch4(opStart+4, Arm64.imm_inst_l(Arm64.OPI_ADD, delta & 0xfff, rpc));
} else {
// should not happen as one instruction is 4 byte, and TFP arm encodes 2.
throw Utils.TODO();
@@ -45,7 +52,7 @@ public class TFPARM extends FunPtrNode implements MachNode, RIPRelSize {
@Override public void asm(CodeGen code, SB sb) {
String reg = code.reg(this);
- _type.print(sb.p(reg).p(" #"));
+ _con.print(sb.p(reg).p(" #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
index b2531f97..d1cafa96 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/TMPARM.java
@@ -10,16 +10,14 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class TMPARM extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- TMPARM( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ TMPARM( ConstantNode con ) { super(con); }
@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,_ext); }
+ @Override public TMPARM copy() { return new TMPARM(this); }
@Override public void encoding( Encoding enc ) {
- if( _ext!=null ) enc.externalData(this,_ext);
- else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ 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));
@@ -35,7 +33,15 @@ 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.patch_adrp_add(enc, opStart, delta, dst);
+ // 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));
} else {
throw Utils.TODO();
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index 3c965da5..0d5b5440 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -212,14 +212,6 @@ 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);
@@ -233,16 +225,10 @@ 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=hidden ? 1 : 0; i<idx-2; i++ )
- if( tfp.arg(i) instanceof TypeFloat)
+ for( int i=2; i<idx; i++ )
+ if( tfp.arg(i-2) instanceof TypeFloat)
fcnt++;
// Floats up to XMMS in XMM registers
if( tfp.arg(idx-2) instanceof TypeFloat ) {
@@ -250,27 +236,24 @@ public class arm extends Machine {
return XMMS[fcnt];
} else {
RegMask[] cargs = CALLINMASK;
- if( sigidx-fcnt < cargs.length )
- return cargs[sigidx-fcnt];
+ if( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
}
// Pass on stack slot (8 and higher)
if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + sigidx);
+ return new RegMask(MAX_REG + 1 + (idx - 2));
}
// 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=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
+ for( int i=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 |
@@ -288,15 +271,15 @@ public class arm extends Machine {
@Override public int rpc() { return X30; }
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(LRG lrg, String kind, byte round) {
+ @Override public SplitNode split(String kind, byte round, LRG lrg) {
RegMask def = lrg._machDef==null ? null : lrg._machDef.outregmap(), use = lrg.useMask();
if( (def!=null && def.size1() && def.test(FLAGS)) ||
(use!=null && use.size1() && use.test(FLAGS)) )
- return new SplitFlagsARM(lrg,kind,round);
+ return new SplitFlagsARM(kind,round);
// Spilling the saved flags requires a GPR between NZCV and the stack.
if( lrg._machDef instanceof SplitFlagsARM || lrg._machDef instanceof SplitGPRARM )
- return new SplitGPRARM(lrg,kind,round);
- return new SplitARM(lrg,kind,round);
+ return new SplitGPRARM(kind,round);
+ return new SplitARM(kind,round);
}
// Return a MachNode unconditional branch
@@ -307,34 +290,30 @@ 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 PtrToIntNode ptr -> new PtrToIntMach(ptr);
- case GuardNode guard -> new GuardMach(guard);
+ case CastNode cast -> new CastMach(cast);
case CallEndNode cend-> new CallEndMach(cend);
- case CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case ConstantNode con-> con(con);
- case DivNode div -> div.mode()==2 ? new DivFARM(div) : new DivARM(div);
- case EscapeNode esc -> new EscapeNode(esc);
+ case DivFNode divf -> new DivFARM(divf);
+ case DivNode div -> new DivARM(div);
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 -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegARM(neg);
- }
- case MulNode mul -> mul.mode()==2 ? new MulFARM(mul) : new MulARM(mul);
+ case MinusNode neg -> new NegARM(neg);
+ case MulFNode mulf -> new MulFARM(mulf);
+ case MulNode 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);
@@ -342,12 +321,11 @@ 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 SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub);
+ case SubFNode subf -> new SubFARM(subf);
+ case SubNode sub -> sub(sub);
case ToFloatNode tfn -> new I2F8ARM(tfn);
case ToIntegerNode cvt -> new F8ToIARM(cvt);
case XorNode xor -> xor(xor);
@@ -360,7 +338,7 @@ public class arm extends Machine {
private Node cmp(BoolNode bool){
Node cmp = _cmp(bool);
- return new SetARM(cmp, bool.op(), cmp instanceof CmpFARM);
+ return new SetARM(cmp, bool.op());
}
private Node _cmp(BoolNode bool) {
if( bool.isFloat() )
@@ -379,18 +357,13 @@ 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 = "!=";
- boolean fp = false;
- if( iff.in(1) instanceof BoolNode bool ) { op = bool.op(); fp = bool.isFloat(); }
+ if( iff.in(1) instanceof BoolNode bool ) op = bool.op();
else if( iff.in(1)==null ) op = "=="; // Never-node cutout
- 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);
+ else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO),(byte)1));
+ return new BranchARM(iff, op);
}
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);
@@ -403,30 +376,22 @@ public class arm extends Machine {
}
private Node con( ConstantNode con ) {
- if( !con._con.isConstant() )
- return ConstantNode.raw(con); // Default unknown caller inputs
- String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
+ if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
return switch( con._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 TypeInteger ti -> new IntARM(con);
+ case TypeFloat tf -> new FloatARM(con);
+ case TypeFunPtr tfp -> new TFPARM(con);
+ case TypeMemPtr tmp -> new TMPARM(con);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntARM(con,ext) : ConstantNode.raw(con);
+ case Type t -> t==Type.NIL ? new IntARM(con) : new ConstantNode(con);
};
}
- private Node fptr( FunPtrNode con ) {
- return new TFPARM(con);
- }
-
private Node call(CallNode 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);
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallARM(call, tfp)
+ : new CallRRARM(call);
}
private Node or(OrNode or) {
@@ -468,13 +433,6 @@ public class arm extends Machine {
: new LsrARM(lsr);
}
- private Node prj(ProjNode prj) {
- if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
- TypeInteger ret = cReturn(prj);
- if( ret!=null ) return new ExtendARM(prj,ret);
- return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjARM(prj);
- }
-
private static int off;
private static Node idx;
private Node st(StoreNode st) {
@@ -500,4 +458,9 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
index 59bad64e..14581111 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/AUIPC.java
+++ b/chapter25/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 FunPtrNode implements MachNode, RIPRelSize {
- AUIPC( FunPtrNode fptr ) { super(fptr); }
+public class AUIPC extends ConstantNode implements MachNode, RIPRelSize {
+ AUIPC( Type tfp ) { super(tfp); }
@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(this); }
+ @Override public AUIPC copy() { return new AUIPC(_con); }
@Override public String op() { return "auipc"; }
@Override public void encoding( Encoding enc ) {
enc.relo(this);
@@ -37,6 +37,7 @@ public class AUIPC extends FunPtrNode implements MachNode, RIPRelSize {
@Override public void asm(CodeGen code, SB sb) {
String reg = code.reg(this);
sb.p(reg).p(" = PC+#");
- _type.print(sb);
+ if( _con == null ) sb.p("---");
+ else _con.print(sb);
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
index b1426a3f..03bedd0d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRISC.java
@@ -15,7 +15,8 @@ public class CallRISC extends CallNode implements MachNode, RIPRelSize {
assert tfp.isConstant();
_inputs.pop(); // Pop constant target
_tfp = tfp;
- _name = CodeGen.CODE.funcName(tfp.fidx());
+ FunNode fun = CodeGen.CODE.link(tfp);
+ _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name;
}
@Override public String op() { return "call"; }
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
index cda4bc79..5eee596d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java
+++ b/chapter25/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 riscv.RPC_MASK; }
+ @Override public RegMask outregmap() { return null; }
@Override public void encoding( Encoding enc ) {
short rpc = enc.reg(this);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
index a110d156..51ddc9db 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/FltRISC.java
@@ -9,13 +9,12 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class FltRISC extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- FltRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ FltRISC(ConstantNode con) { super(con); }
@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,_ext); }
+ @Override public FltRISC copy() { return new FltRISC(this); }
@Override public void encoding( Encoding enc ) {
enc.largeConstant(this,_con, 0, -1);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
index dd4a02a3..23ac8481 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/Int8RISC.java
@@ -5,23 +5,21 @@ 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 {
- final String _ext;
- Int8RISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ public Int8RISC(ConstantNode con) { super(con); }
+
@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,_ext); }
+ @Override public Int8RISC copy() { return new Int8RISC(this); }
@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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
index 35d2bc65..9088abbd 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/IntRISC.java
+++ b/chapter25/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 {
- IntRISC(ConstantNode con) { super(con); }
+ public 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
index e9f3ec02..2e3dfd8d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/LoadRISC.java
@@ -3,9 +3,7 @@ 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
@@ -24,39 +22,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
index e0bbc219..adf21848 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/MemOpRISC.java
@@ -11,7 +11,6 @@ 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;
@@ -19,8 +18,7 @@ public abstract class MemOpRISC extends MemOpNode implements MachNode {
_inputs.setX(3, null); // Never an index
_inputs.setX(4, val );
_off = off;
- _bytes = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
- _sz = (char)('0'+_bytes);
+ _sz = (char)('0'+(1<<_declaredType.log_size()));
}
@Override public String label() { return op(); }
@@ -28,11 +26,42 @@ 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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
// func3 is based on load/store size and extend
- abstract int func3();
+ 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;
+ }
// Register mask allowed on input i.
@Override public RegMask regmap(int i) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
index 40d0359c..965a3c7c 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/NewRISC.java
@@ -41,9 +41,8 @@ public class NewRISC extends NewNode implements MachNode, RIPRelSize {
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("\n"); // Multi-op trigger
- sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
+ sb.p("ldi a1=#1\n");
sb.p("auipc a2=#calloc\n");
- sb.p("call a2+#calloc");
+ sb.p("call a2+#calloc, a0");
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
index d455337b..8a16f6b7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/SplitRISC.java
+++ b/chapter25/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( LRG lrg, String kind, byte round ) { super(lrg, kind,round, new Node[2]); }
+ SplitRISC( String kind, byte round ) { super(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 = fun(enc).computeStackOffset(enc._code,dst);
+ int off = enc._fun.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 = fun(enc).computeStackOffset(enc._code,src);
+ int off = enc._fun.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,4 +58,10 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
index c3fcf4d3..48cb7c78 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/StoreRISC.java
@@ -4,7 +4,6 @@ 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;
@@ -28,7 +27,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 _bytes >= 4
+ if( i==4 ) return _sz >= '4'
? riscv.MEM_MASK // Word/dword value in GPR or FPR
: riscv.RMASK; // Byte/short stores require a GPR
return null; // Anti-dependence
@@ -43,11 +42,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
index 44ef1a95..e57f6e09 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TFPRISC.java
@@ -2,27 +2,25 @@ 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.node.ConstantNode;
+import com.seaofnodes.simple.node.MachNode;
+import com.seaofnodes.simple.node.Node;
import com.seaofnodes.simple.type.TypeFunPtr;
import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
-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; }
+public class TFPRISC extends ConstantNode implements MachNode, RIPRelSize {
+ TFPRISC(ConstantNode con) { super(con); }
@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((TypeFunPtr)_con,_ext); }
+ @Override public TFPRISC copy() { return new TFPRISC(this); }
@Override public void encoding( Encoding enc ) {
- if( _ext==null ) enc.relo(this); // Internal patch
- else enc.external(this,_ext); // ELF-file external patch
+ enc.relo(this);
// 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
@@ -35,14 +33,9 @@ public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize {
@Override public void patch( Encoding enc, int opStart, int opLen, int delta ) {
short rpc = enc.reg(this);
if( opLen==8 ) {
- int hi20 = (delta + 0x800) >> 12;
- int lo12 = delta - (hi20 << 12);
- // AUIPC (upper 20 bits)
- // opstart of add
- 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
+ // 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));
} else {
// should not happen as one instruction is 4 byte, and TFP arm encodes 2.
throw Utils.TODO();
@@ -50,7 +43,7 @@ public class TFPRISC extends FunPtrNode implements MachNode, RIPRelSize {
}
@Override public void asm(CodeGen code, SB sb) {
- _type.print(sb.p(code.reg(this)).p(" #"));
+ _con.print(sb.p(code.reg(this)).p(" #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
index a5ff3753..7bc2db7e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/TMPRISC.java
@@ -9,16 +9,14 @@ import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
public class TMPRISC extends ConstantNode implements MachNode, RIPRelSize {
- final String _ext;
- TMPRISC( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ TMPRISC(ConstantNode con) { super(con); }
@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,_ext); }
+ @Override public TMPRISC copy() { return new TMPRISC(this); }
@Override public void encoding( Encoding enc ) {
- if( _ext!=null ) enc.externalData(this,_ext);
- else enc.largeConstant(this,((TypeMemPtr)_con)._obj,0,-1);
+ 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));
@@ -31,9 +29,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);
- // AUIPC dst,#hi20_constant_pool
+ // Round the high part because ADDI's low 12 bits are signed.
enc.patch4(opStart , RiscV.u_type(RiscV.OP_AUIPC, dst, (delta+0x800)>>12));
- // ADDI's low 12 bits are signed; round the AUIPC page accordingly.
+ // ADDI dst,dst,#low12_constant_pool
enc.patch4(opStart+4, RiscV.i_type(RiscV.OP_IMM, dst, 0, dst, delta & 0xFFF));
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 0195542c..7c994624 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -15,6 +15,7 @@ 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;
@@ -33,24 +34,24 @@ public class riscv extends Machine {
public static final int F_OFFSET = 32;
static final String[] REGS = new String[] {
- //"zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
- //"s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
- //"a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
- //"s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
- //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
- //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
- //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
- //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
- // Register-based naming hella easier to debug than "official" register
- // names when single-stepping in debugger.
- "zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
- "r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
- "r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
- "r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ "zero","rpc" , "rsp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
+ "s0" , "s1" , "a0" , "a1" , "a2" , "a3" , "a4" , "a5" ,
+ "a6" , "a7" , "s2" , "s3" , "s4" , "s5" , "s6" , "s7" ,
+ "s8" , "s9" , "s10" , "s11" , "t3" , "t4" , "t5" , "t6" ,
"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
+ // Register-based naming hella easier to debug than "official" register
+ // names when single-stepping in debugger.
+ //"zero", "r1" , "rsp" , "r3" , "r4" , "r5" , "r6" , "r7" ,
+ //"r8" , "r9" , "r10" , "r11", "r12", "r13", "r14", "r15",
+ //"r16" , "r17" , "r18" , "r19", "r20", "r21", "r22", "r23",
+ //"r24" , "r25" , "r26" , "r27", "r28", "r29", "r30", "r31",
+ //"f0" , "f1" , "f2" , "f3" , "f4" , "f5" , "f6" , "f7" ,
+ //"fs0" , "fs1" , "fa0" , "fa1" , "fa2" , "fa3" , "fa4" , "fa5" ,
+ //"fa6" , "fa7" , "fs2" , "fs3" , "fs4" , "fs5" , "fs6" , "fs7" ,
+ //"fs8" , "fs9" , "fs10", "fs11", "ft8" , "ft9" , "ft10", "ft11"
};
@Override public String[] regs() { return REGS; }
@@ -91,7 +92,7 @@ public class riscv extends Machine {
public static RegMask FA6_MASK = new RegMask(FA6);
public static RegMask FA7_MASK = new RegMask(FA7);
- // Int/ptr arguments calling convention
+ // Int arguments calling conv
static RegMask[] CALLINMASK = new RegMask[] {
A0_MASK,
A1_MASK,
@@ -137,16 +138,10 @@ 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=hidden ? 1 : 0; i<idx-2; i++ )
- if( tfp.arg(i) instanceof TypeFloat )
+ for( int i=2; i<idx; i++ )
+ if( tfp.arg(i-2) instanceof TypeFloat )
fcnt++;
// Floats up to XMMS in XMM registers
if( tfp.arg(idx-2) instanceof TypeFloat ) {
@@ -154,26 +149,23 @@ public class riscv extends Machine {
return XMMS[fcnt];
} else {
RegMask[] cargs = CALLINMASK;
- if( sigidx-fcnt < cargs.length )
- return cargs[sigidx-fcnt];
+ if( idx-2-fcnt < cargs.length )
+ return cargs[idx-2-fcnt];
}
// Pass on stack slot(8 and higher)
if( maxArgSlot>0 ) throw Utils.TODO();
- return new RegMask(MAX_REG + 1 + sigidx);
+ return new RegMask(MAX_REG + 1 + (idx - 2));
}
@Override public short maxArgSlot( TypeFunPtr tfp ) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
+ for( int i=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) |
@@ -194,7 +186,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(LRG lrg, String kind, byte round ) { return new SplitRISC(lrg, kind,round); }
+ @Override public SplitNode split(String kind, byte round, LRG lrg) { return new SplitRISC(kind,round); }
// True if signed 12-bit immediate
public static boolean imm12(TypeInteger ti) {
@@ -211,34 +203,30 @@ 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 PtrToIntNode ptr -> new PtrToIntMach(ptr);
- case GuardNode guard -> new GuardMach(guard);
+ case CastNode cast -> new CastMach(cast);
case CallEndNode cend -> new CallEndMach(cend);
- case CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case ConstantNode con -> con(con);
- case DivNode div -> div.mode()==2 ? new DivFRISC(div) : new DivRISC(div);
- case EscapeNode esc -> new EscapeNode(esc);
+ case DivFNode divf -> new DivFRISC(divf);
+ case DivNode div -> new DivRISC(div);
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 -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegRISC(neg);
- }
- case MulNode mul -> mul.mode()==2 ? new MulFRISC(mul) : new MulRISC(mul);
+ case MinusNode neg -> new NegRISC(neg);
+ case MulFNode mulf -> new MulFRISC(mulf);
+ case MulNode 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);
@@ -246,12 +234,11 @@ 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 SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub);
+ case SubFNode subf -> new SubFRISC(subf);
+ case SubNode sub -> sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
case ToIntegerNode cvt -> new F8ToIRISC(cvt);
case XorNode xor -> xor(xor);
@@ -262,9 +249,11 @@ 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);
@@ -282,10 +271,8 @@ public class riscv extends Machine {
}
private Node call(CallNode call) {
- return call.fptr() instanceof FunPtrNode fptr
- ? new CallRISC(call, (TypeFunPtr)fptr._type)
- : call.fptr() instanceof ConstantNode con
- ? new CallRISC(call, (TypeFunPtr)con._type)
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallRISC(call, tfp)
: new CallRRISC(call);
}
private Node nnn(NewNode nnn) {
@@ -324,13 +311,9 @@ public class riscv extends Machine {
}
private Node con( ConstantNode con ) {
- if( !con._con.isConstant() )
- return ConstantNode.raw(con); // Default unknown caller inputs
- String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
+ if( !con._con.isConstant() ) return new ConstantNode( con ); // Default unknown caller inputs
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);
@@ -343,22 +326,18 @@ 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,null);
+ yield new Int8RISC(con);
}
// Load from constant pool
- case TypeFloat tf -> new FltRISC(con,ext);
- case TypeMemPtr tmp -> new TMPRISC(con,ext);
- case TypeFunPtr tfp -> new TFPRISC(tfp,ext);
+ case TypeFloat tf -> new FltRISC(con);
+ case TypeFunPtr tfp -> new TFPRISC(con);
+ case TypeMemPtr tmp -> new TMPRISC(con);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t==Type.NIL ? new IntRISC(con) : ConstantNode.raw(con);
+ case Type t -> t==Type.NIL ? new IntRISC(con) : new ConstantNode(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
@@ -413,10 +392,9 @@ public class riscv extends Machine {
}
private Node prj(ProjNode prj) {
- if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendRISC(prj,ret);
- return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjRISC(prj);
+ return new ProjRISC(prj);
}
private Node ld(LoadNode ld) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
index 74af9266..974f690f 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/AddFMemX86.java
+++ b/chapter25/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( AddNode add, LoadNode ld , Node base, Node idx, int off, int scale, Node val ) {
+ AddFMemX86( AddFNode 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
index 6bafbf37..cd4d2452 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallX86.java
@@ -9,27 +9,21 @@ 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;
- _name = CodeGen.CODE.funcName(tfp.fidx());
- _extern = CodeGen.CODE.externFunc(tfp.fidx()) != null;
+ FunNode fun = CodeGen.CODE.link(tfp);
+ _name = fun==null ? CodeGen.CODE.externFunc(tfp) : fun._name;
}
@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 _extern
- ? x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot,CodeGen.CODE._cCallingConv)
- : x86_64_v2.callInMask(_tfp,i,fun()._maxArgSlot);
- }
+ @Override public RegMask regmap(int i) { return 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
index 4014045b..7b7d98d5 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CmpMemX86.java
+++ b/chapter25/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,_con.log_size(),_imm,ptr,idx,_off,_scale);
- } else encVal(enc,_con,ptr,idx,src,_off,_scale,_swap);
+ X86.cmpImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale);
+ } else encVal(enc,_declaredType,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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
index 59f06341..da9bdce7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FltX86.java
@@ -4,20 +4,17 @@ 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;
- final String _ext;
- FltX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ FltX86(ConstantNode con ) { super(con); }
@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,_ext); }
+ @Override public Node copy() { return new FltX86(this); }
@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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
index 0e44aee7..c7a20e4d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/FunX86.java
@@ -11,7 +11,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
index 2ffaa3d1..656b1543 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/IntX86.java
@@ -6,17 +6,15 @@ 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 {
- final String _ext;
- IntX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ IntX86( ConstantNode con ) { super(con); }
@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,_ext); }
+ @Override public Node copy() { return new IntX86(this); }
@Override public RegMask regmap(int i) { return null; }
@Override public RegMask outregmap() { return x86_64_v2.WMASK; }
// Zero-set uses XOR kills flags
@@ -25,7 +23,6 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
index 5c472dbe..d4f1413e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/LoadX86.java
+++ b/chapter25/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 _con instanceof TypeFloat ? x86_64_v2.XMASK : x86_64_v2.WMASK; }
+ @Override public RegMask outregmap() { return x86_64_v2.MEM_MASK; }
@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, _con, dst, ptr, idx, _off, _scale);
+ enc(enc, _declaredType, 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
index fcca7f8f..309e62d9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/MemOpX86.java
@@ -6,6 +6,7 @@ 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.
@@ -41,8 +42,7 @@ public abstract class MemOpX86 extends MemOpNode implements MachNode {
_off = off;
_scale = scale;
_imm = imm;
- int size = mop instanceof StoreNode st ? st.storeSize() : 1<<_con.log_size();
- _sz = (char)('0'+size);
+ _sz = (char)('0'+(1<<_declaredType.log_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 new AssertionError("Do not call compute on "+getClass().getSimpleName()); }
- @Override public Node idealize() { throw new AssertionError("Do not call idealize on "+getClass().getSimpleName()); }
+ @Override public Type compute() { throw Utils.TODO(); }
+ @Override public Node idealize() { throw Utils.TODO(); }
// Register mask allowed on input i.
@Override public RegMask regmap(int i) {
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
index d6a06c2f..1cfc1c42 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/NewX86.java
@@ -16,8 +16,7 @@ public class NewX86 extends NewNode implements MachNode {
}
// General form: "alloc #bytes PC"
@Override public void asm(CodeGen code, SB sb) {
- sb.p("\n"); // Multi-op trigger
- sb.p("ldi ").p(code._mach.regs()[_arg2Reg]).p(" = #1\n");
+ sb.p("ldi rcx = #1\n");
sb.p("call #calloc");
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
index cb7694f9..4650b535 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitFlagsX86.java
@@ -6,7 +6,7 @@ import com.seaofnodes.simple.codegen.*;
// Flag snapshots can use GPRs or stack slots, but never XMM registers.
public class SplitFlagsX86 extends SplitX86 {
private static final RegMask MASK = new RegMask(x86_64_v2.WR_BITS | (1L<<x86_64_v2.FLAGS) | x86_64_v2.SPILLS,-1L);
- SplitFlagsX86(LRG lrg,String kind,byte round) { super(lrg,kind,round); }
+ SplitFlagsX86(String kind,byte round) { super(kind,round); }
@Override public RegMask regmap(int i) { return MASK; }
@Override public RegMask outregmap() { return MASK; }
@@ -17,7 +17,7 @@ public class SplitFlagsX86 extends SplitX86 {
boolean toFlags = dst==x86_64_v2.FLAGS;
int reg = toFlags ? src : dst;
if( reg>=x86_64_v2.MAX_REG )
- X86.flagsStack(enc,fun(enc).computeStackOffset(enc._code,reg),toFlags);
+ X86.flagsStack(enc,enc._fun.computeStackOffset(enc._code,reg),toFlags);
else
X86.flags(enc,reg,toFlags);
} else super.encoding(enc);
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
index 46f2cb16..4e9eda7d 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/SplitX86.java
@@ -12,21 +12,22 @@ import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
public class SplitX86 extends SplitNode {
- SplitX86( LRG lrg, String kind, byte round ) { super(lrg,kind,round, new Node[2]); }
+ SplitX86( String kind, byte round ) { super(kind,round, new Node[2]); }
+ @Override public String op() { return "mov"; }
@Override public RegMask regmap(int i) { return x86_64_v2.SPLIT_MASK; }
@Override public RegMask outregmap() { return x86_64_v2.SPLIT_MASK; }
- // Moves among GPRs, XMM registers, and stack slots.
+ // Ordinary GPR, XMM, and stack moves. Flags use SplitFlagsX86.
@Override public void encoding( Encoding enc ) {
short dst=enc.reg(this), src=enc.reg(in(1));
assert dst!=x86_64_v2.FLAGS && src!=x86_64_v2.FLAGS;
boolean dstX=dst>=x86_64_v2.XMM_OFFSET, srcX=src>=x86_64_v2.XMM_OFFSET;
// Stack spills
if( dst >= x86_64_v2.MAX_REG ) {
- int off = fun(enc).computeStackOffset(enc._code,dst);
+ int off = enc._fun.computeStackOffset(enc._code,dst);
if( src >= x86_64_v2.MAX_REG ) {
// Rare stack-stack move. push [RSP+soff]; pop [RSP+doff]
- int soff = fun(enc).computeStackOffset(enc._code,src);
+ int soff = enc._fun.computeStackOffset(enc._code,src);
X86.stackCopy(enc,soff,off);
return;
}
@@ -34,7 +35,7 @@ public class SplitX86 extends SplitNode {
return;
}
if( src >= x86_64_v2.MAX_REG ) {
- int off = fun(enc).computeStackOffset(enc._code,src);
+ int off = enc._fun.computeStackOffset(enc._code,src);
LoadX86.enc(enc, dstX ? TypeFloat.F64 : TypeInteger.BOT, dst, (short)x86_64_v2.RSP, (short)-1, off, 0);
return;
}
@@ -42,4 +43,10 @@ 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
index 9ffa25fc..c83dfd44 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/StoreX86.java
+++ b/chapter25/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,Integer.numberOfTrailingZeros(_sz-'0'),_imm,ptr,idx,_off,_scale);
- else encVal(enc,_con,ptr,idx,src,_off,_scale);
+ if( src==-1 ) X86.storeImm(enc,_declaredType.log_size(),_imm,ptr,idx,_off,_scale);
+ else encVal(enc,_declaredType,ptr,idx,src,_off,_scale);
}
// Non-immediate encoding
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
index e3563713..fff2fb04 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TFPX86.java
+++ b/chapter25/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 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(); }
+public class TFPX86 extends ConstantNode implements MachNode, RIPRelSize {
+ TFPX86( ConstantNode con ) { super(con); }
+ @Override public String op() {
+ return _con == Type.NIL ? "xor" : "ldx";
+ }
@Override public boolean isClone() { return true; }
- @Override public TFPX86 copy() { return new TFPX86(this); }
+ @Override public Node 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( _ext==null ) enc.relo(this);
- else enc.external(this,_ext);
- _opLen=X86.rip(enc,0,0x8D,true,enc.reg(this));
+ if( _con==Type.NIL ) { X86.constant(enc,enc.reg(this),0); return; }
+ enc.relo(this);
+ X86.rip(enc,0,0x8D,true,enc.reg(this));
}
- @Override public byte encSize(int delta) { return _opLen; }
+ @Override public byte encSize(int delta) {
+ return 7;
+ }
- // Delta is from opcode start, X86 expects it from opcode end
+ // 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 + 3, delta - 7);
}
// Human-readable form appended to the SB. Things like the encoding,
@@ -43,9 +43,11 @@ public class TFPX86 extends FunPtrNode 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) {
- sb.p(code.reg(this));
- if( _ext == null ) _type.print(sb.p(" #"));
- else sb.p(_ext);
+ String reg = code.reg(this);
+ if( _con == Type.NIL )
+ sb.p(reg).p(",").p(reg);
+ else
+ _con.print(sb.p(reg).p(" = #"));
}
@Override public boolean eq(Node n) { return this==n; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
index 682597f4..c78ac6de 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/TMPX86.java
@@ -7,29 +7,30 @@ import com.seaofnodes.simple.type.TypeMemPtr;
import com.seaofnodes.simple.util.SB;
public class TMPX86 extends ConstantNode implements MachNode, RIPRelSize{
- private byte _opLen;
- final String _ext;
- TMPX86( ConstantNode con, String ext ) { super(con); _ext = ext; }
+ TMPX86(ConstantNode con ) { super(con); }
@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,_ext); }
+ @Override public Node copy() { return new TMPX86(this); }
@Override public void encoding( Encoding enc ) {
short dst = enc.reg(this);
- // 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*/);
+ 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*/);
}
// Delta is from opcode start
- @Override public byte encSize(int delta) { return _opLen; }
+ @Override public byte encSize(int delta) { return (byte)7; }
// 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/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
index 3dc93bf6..f0598422 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java
@@ -97,29 +97,22 @@ 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( conv ) {
+ return switch( CodeGen.CODE._callingConv ) {
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 maxArgSlot(tfp,CodeGen.CODE._callingConv);
- }
- @Override public short maxArgSlot( TypeFunPtr tfp, String conv ) {
- return switch( conv ) {
+ return switch( CodeGen.CODE._callingConv ) {
case "SystemV" -> maxArgSlotSys5 (tfp);
- case "win64" -> maxArgSlotWin64(tfp);
+ case "Win64" -> maxArgSlotWin64(tfp);
default -> throw Utils.TODO();
};
}
@@ -152,22 +145,14 @@ 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).
- int sigidx = idx-2;
- if( hiddenSelf(tfp) ) {
- if( sigidx==0 ) return null;
- sigidx--;
- }
- if( sigidx >= 4 )
- return new RegMask(MAX_REG+maxArgSlot+sigidx);
+ if( idx >= 6 )
+ return new RegMask(MAX_REG+maxArgSlot+(idx-2));
return tfp.arg(idx-2) instanceof TypeFloat
- ? XMMS8 [sigidx]
- : WIN64_CALL[sigidx];
+ ? XMMS8 [idx-2]
+ : WIN64_CALL[idx-2];
}
static short maxArgSlotWin64(TypeFunPtr tfp) {
- 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);
+ return (short)tfp.nargs();
}
// Sys5: max 6 GPRs and 8 FPRS filled first. Extra args land in increasing
@@ -201,14 +186,9 @@ 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=hiddenSelf(tfp) ? 1 : 0; i<idx-2; i++ ) {
- if( tfp.arg(i) instanceof TypeFloat ) fcnt++;
+ for( int i=2; i<idx; i++ ) {
+ if( tfp.arg(i-2) instanceof TypeFloat ) fcnt++;
else icnt++;
}
int nstk = Math.max(icnt-6,0)+Math.max(fcnt-8,0);
@@ -218,16 +198,13 @@ public class x86_64_v2 extends Machine {
}
static short maxArgSlotSys5(TypeFunPtr tfp) {
int icnt=0, fcnt=0; // Count of ints, floats
- for( int i=hiddenSelf(tfp) ? 1 : 0; i<tfp.nargs(); i++ ) {
+ for( int i=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) |
@@ -246,18 +223,12 @@ public class x86_64_v2 extends Machine {
(1L<<XMM4) | (1L<<XMM5) ;
@Override public long callerSave() {
- return callerSave(CodeGen.CODE._callingConv);
- }
- @Override public long callerSave(String conv) {
- return switch (conv) {
+ return switch (CodeGen.CODE._callingConv) {
case "SystemV" -> SYSTEM5_CALLER_SAVE;
- case "win64" -> WIN64_CALLER_SAVE;
- default -> throw new IllegalArgumentException("Unknown calling convention: " + conv);
+ case "Win64" -> WIN64_CALLER_SAVE;
+ default -> throw new IllegalArgumentException("Unknown calling convention: " + CodeGen.CODE._callingConv);
};
}
- @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;
@@ -266,10 +237,10 @@ public class x86_64_v2 extends Machine {
// Create a split op; any register to any register, including stack slots
- @Override public SplitNode split(LRG lrg, String kind, byte round ) {
+ @Override public SplitNode split(String kind, byte round, LRG lrg) {
return lrg._machDef!=null && lrg._machDef.outregmap().test(FLAGS)
- ? new SplitFlagsX86(lrg,kind,round)
- : new SplitX86(lrg,kind,round);
+ ? new SplitFlagsX86(kind,round)
+ : new SplitX86(kind,round);
}
// Return a MachNode unconditional branch
@@ -281,34 +252,30 @@ 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 CLInitNode init -> new CLInitNode(init);
case CProjNode c -> new CProjNode(c);
case CallEndNode cend -> new CallEndMach(cend);
case CallNode call -> call(call);
- case PtrToIntNode ptr -> new PtrToIntMach(ptr);
- case GuardNode guard -> new GuardMach(guard);
+ case CastNode cast -> new CastMach(cast);
case ConstantNode con -> con(con);
- case DivNode div -> div.mode()==2 ? new DivFX86(div) : new DivX86(div);
- case EscapeNode esc -> new EscapeNode(esc);
+ case DivFNode divf -> new DivFX86(divf);
+ case DivNode div -> new DivX86(div);
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 -> {
- if( neg.mode()==2 ) throw Utils.TODO();
- yield new NegX86(neg);
- }
- case MulNode mul -> mul.mode()==2 ? new MulFX86(mul) : mul(mul);
+ case MinusNode neg -> new NegX86(neg);
+ case MulFNode mulf -> new MulFX86(mulf);
+ case MulNode 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);
@@ -316,15 +283,14 @@ public class x86_64_v2 extends Machine {
case SarNode sar -> sar(sar);
case ShlNode shl -> shl(shl);
case ShrNode shr -> shr(shr);
- case StartCUNode start -> new StartCUNode(start);
- case StopCUNode stop -> new StopCUNode(stop);
case StartNode start -> new StartNode(start);
case StopNode stop -> new StopNode(stop);
case StoreNode st -> st(st);
- case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub);
+ case SubFNode subf -> new SubFX86(subf);
+ case SubNode sub -> sub(sub);
case ToFloatNode tfn -> i2f8(tfn);
case ToIntegerNode cvt -> new F8ToIX86(cvt);
- case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL);
+ case XCtrlNode x -> new ConstantNode(Type.XCONTROL);
case XorNode xor -> xor(xor);
case LoopNode loop -> new LoopNode(loop);
@@ -339,12 +305,14 @@ 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 ) {
@@ -367,6 +335,17 @@ 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) )
@@ -390,10 +369,8 @@ public class x86_64_v2 extends Machine {
}
private Node call(CallNode call) {
- 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)
+ return call.fptr() instanceof ConstantNode con && con._con instanceof TypeFunPtr tfp
+ ? new CallX86(call, tfp)
: new CallRX86(call);
}
@@ -418,13 +395,12 @@ 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()) &&
- (!(lhs instanceof ConstantNode con && con._con instanceof TypeInteger ti && imm32(ti.value())) ||
- bool.op()=="==" || bool.op()=="!=") )
+ if( rhs instanceof LoadNode ld && ld.nOuts() == 1 && lhs._type.isa(ld._declaredType) &&
+ (val!=null || bool.op()=="==" || bool.op()=="!=") )
return new CmpMemX86(bool, address(ld), ld.ptr(), idx, off, scale, imm(lhs), val, true );
// Vs immediate
@@ -441,23 +417,18 @@ public class x86_64_v2 extends Machine {
private Node con( ConstantNode con ) {
if( !con._con.isConstant() )
- return ConstantNode.raw(con); // Default unknown caller inputs
- String ext = con instanceof ExternNode ext0 ? ext0._extern : null;
+ return new ConstantNode(con); // Default unknown caller inputs
return switch (con._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 TypeInteger ti -> new IntX86(con);
+ case TypeFloat tf -> new FltX86(con);
+ case TypeFunPtr tfp -> new TFPX86(con);
+ case TypeMemPtr tmp -> new TMPX86(con);
case TypeNil tn -> throw Utils.TODO();
// TOP, BOTTOM, XCtrl, Ctrl, etc. Never any executable code.
- case Type t -> t == Type.NIL ? new IntX86(con,null) : ConstantNode.raw(con);
+ case Type t -> t == Type.NIL ? new IntX86(con) : new ConstantNode(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);
@@ -470,9 +441,7 @@ 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),
- ConstantNode.raw(TypeInteger.ZERO),
- (byte)(iff.in(1)._type instanceof TypeFloat ? 2 : 1)));
+ else iff.setDef(1, new BoolNode.NE(iff.in(1), new ConstantNode(TypeInteger.ZERO),(byte)1));
return new JmpX86(iff, op);
}
@@ -493,10 +462,9 @@ public class x86_64_v2 extends Machine {
}
private Node prj( ProjNode prj ) {
- if( prj.in(0) instanceof CLInitNode ) return new ProjNode(prj);
TypeInteger ret = cReturn(prj);
if( ret!=null ) return new ExtendX86(prj,ret);
- return prj.in(0) instanceof StartNode ? new ProjNode(prj) : new ProjX86(prj);
+ return new ProjX86(prj);
}
private Node sar(SarNode sar) {
@@ -583,7 +551,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 && ti.isConstant() && imm32(ti.value()) ) {
+ if( xval instanceof ConstantNode con && con._con instanceof TypeInteger ti) {
val = null;
imm = (int) ti.value();
assert imm == ti.value(); // In 32-bit range
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
index e8218afd..48296ce7 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/ASMPrinter.java
@@ -9,8 +9,6 @@ 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> {
@@ -26,7 +24,7 @@ public final class ASMPrinter extends AssemblyAdapter<Node> {
}
@Override public ArrayList<Node> blocks() {
var blocks=new ArrayList<Node>();
- var cfg=_enc==null ? _code._cfg : _enc._cfg;
+ var cfg=_code._cfg;
if( cfg!=null ) for( Node n : cfg ) blocks.add(n);
return blocks;
}
@@ -52,24 +50,26 @@ 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() ||
- (_code._phase==CodeGen.Phase.RegAlloc && _code._regAlloc!=null && _code._regAlloc.done()); }
+ @Override public boolean postAlloc() { return _code._phase.ordinal()>CodeGen.Phase.RegAlloc.ordinal(); }
@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(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) {
+ @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) {
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,24 +78,8 @@ public final class ASMPrinter extends AssemblyAdapter<Node> {
var pools=new ArrayList<Pool>();
if( !encoded() ) return pools;
int base=(codeEnd+15)&-16;
- 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)));
+ if( !_enc._bigCons.isEmpty() && base<_enc._bits.size() )
+ pools.add(new Pool("Constant Pool",_enc._bits.buf(),base,base,entries(_enc._bigCons.values())));
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/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
index 531a549a..c912ab77 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/IRPrinter.java
@@ -14,8 +14,6 @@ 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;
@@ -49,12 +47,15 @@ public final class IRPrinter extends IRAdapter<Node> {
public static String prettyPrint(CodeGen code) {
var units=new ArrayList<com.seaofnodes.print.IRPrinter.Unit<Node>>();
- 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));
- }
+ 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));
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/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
index 9ca0ec51..8dc6ea47 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphAdapter.java
@@ -2,30 +2,22 @@ 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 25's view of the IR, including partially constructed parse-time graphs. */
+/** Chapter 24's view of the IR; browser, transport and layout are shared. */
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.var(i)._name : null;
+ ? scope._vars.get(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 Proj p ? p.idx() : -1; }
+ @Override protected int projectionIndex(Node n) { return n instanceof ProjNode p ? p._idx : n instanceof CProjNode p ? p._idx : -1; }
@Override protected boolean folding(Node n) {
if( n instanceof FunNode fun ) return fun.folding();
@@ -40,41 +32,26 @@ 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;
- // Start inherits Loop for whole-program analysis, but is not a source loop.
- if( n instanceof StartNode ) return Kind.START;
+ if( n instanceof StartNode ) return Kind.CTRL;
if( n instanceof LoopNode ) return Kind.LOOP;
if( n instanceof RegionNode ) return Kind.REGION;
if( n instanceof PhiNode ) return Kind.PHI;
- if( n instanceof CFGNode ) return Kind.CTRL;
- return isMem(n) ? Kind.MEM : Kind.DATA;
+ return n instanceof CFGNode ? Kind.CTRL : isMem(n) ? Kind.MEM : Kind.DATA;
}
@Override protected Role role(Node n, int i) {
- 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 )
+ 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 )
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 EscapeNode && i >= 2) ) return Role.MEM;
+ if( n instanceof MemMergeNode || (i == 1 && (n instanceof MemOpNode || n instanceof ReturnNode || n instanceof CallNode || n instanceof NewNode)) ) 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) {
- // These Phi subclasses are memory even before their first type computation.
- return n instanceof MemPhiNode || n instanceof BulkMemPhiNode || n.isMem() || n._type instanceof TypeMem;
- }
+ private boolean isMem(Node n) { return n.isMem() || n._type instanceof TypeMem; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java b/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
index f2ed2f72..b2c5492e 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/print/SimpleGraphObserver.java
+++ b/chapter25/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 25's compilation context; capture and event bookkeeping are shared. */
+/** Chapter 24'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/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
index beaa719c..1396f1de 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Field.java
@@ -1,7 +1,6 @@
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.*;
@@ -9,7 +8,6 @@ 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.
@@ -19,28 +17,29 @@ 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 is only written in the constructor
+ // Field must be written to exactly once, no more, no less
public boolean _final;
- // Native C storage, outside the containing object's layout.
- public boolean _extern;
+ // Field is final in the declaration; value is the same for all instances
+ // and will be moved to the class object
+ public boolean _one;
private static final Ary<Field> FREE = new Ary<>(Field.class);
- private Field(String fname, Type type, int alias, boolean xfinal, boolean external ) { super(TFLD); init(fname,type,alias,xfinal,external); }
- private Field init(String fname, Type type, int alias, boolean xfinal, boolean external ) {
+ 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 ) {
_fname = fname;
_t = type;
_alias = alias;
_final = xfinal;
- _extern = external;
+ _one = one;
return this;
}
// Return a filled-in Field; either from free list or alloc new.
- private static Field malloc(String fname, Type type, int alias, boolean xfinal, boolean external ) {
- return FREE.isEmpty() ? new Field(fname,type,alias,xfinal,external) : FREE.pop().init(fname,type,alias,xfinal,external);
+ 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);
}
// Malloc-from
- public Field malloc( ) { return malloc(_fname,null,_alias,_final,_extern); }
+ public Field malloc( ) { return malloc(_fname,null,_alias,_final,_one); }
@Override Field free(Type t) {
Field f = (Field)t;
assert !f.isFree() && !f._terned;
@@ -53,31 +52,27 @@ 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 ) {
- 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);
+ public static Field make( String fname, Type type, int alias, boolean xfinal, boolean one ) {
+ Field f = malloc(fname,type,alias,xfinal,one);
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,_extern);
+ return type == _t ? this : make(_fname,type,_alias,_final,_one);
}
public Field makeFrom( boolean xfinal ) {
- return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_extern);
+ return xfinal == _final ? this : make(_fname,_t,_alias,xfinal,_one);
}
// Cycle-making-breaking
void setType(Type t) { assert _t ==null; _t = t; }
- 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); }
+ 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); }
@Override Field xmeet( Type that ) {
Field fld = (Field)that; // Invariant
@@ -86,17 +81,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;
- if( _extern!=fld._extern ) return BOT;
- return make(_fname, _t.meet(fld._t ), _alias, _final | fld._final,_extern);
+ assert _one ==fld._one ;
+ return make(_fname, _t.meet(fld._t ),_alias,_final | fld._final, _one);
}
@Override
- Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_extern); }
+ Field xdual() { return malloc(_fname, _t.dual(),_alias,!_final,_one); }
@Override Field rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- Field d = malloc(_fname,null,_alias,!_final,_extern);
+ Field d = malloc(_fname,null,_alias,!_final,_one);
(_dual = d)._dual = this; // Cross link duals
d._t = _t._terned ? _t.dual() : _t.rdual();
return d;
@@ -104,51 +99,38 @@ public class Field extends Type {
@Override boolean _isConstant() { return _t._isConstant(); }
@Override boolean _isFinal() { return _final && _t._isFinal(); }
- @Override Field _makeRO() { return _final ? this : make(_fname, _t._makeRO(),_alias,true,_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 _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 _close(java.util.HashMap<String,Type> types) { return makeFrom( _t._close(types)); }
// Override in subclasses
- int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_extern ? 2048 : 0); }
+ int hash() { return _fname.hashCode() ^ _t.hashCode() ^ _alias ^ (_final ? 1024 : 0) ^ (_one ? 2048 : 0); }
private boolean static_eq( Field f ) {
- return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _extern==f._extern && _t !=null && f._t !=null;
+ return _fname.equals(f._fname) && _alias==f._alias && _final==f._final && _one==f._one && _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 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 int nkids() { return 1; }
+ @Override Type at( int idx ) { return _t; }
+ @Override void set( int idx, Type t ) { _t = t; }
@Override
public SB _print( SB sb, BitSet visit, boolean html ) {
- sb.p(_extern ? "extern C " : "").p(_final?"":"!").p(_fname).p(":").p(_alias).p(" : ");
+ sb.p(_final?"":"!").p(_one?"$":"").p(_fname).p(":").p(_alias).p(" : ");
return _t ==null ? sb.p("---") : _t.print(sb,visit,html);
}
- @Override public String str() { return (_final?"":"!")+_fname; }
+ @Override public String str() { return (_final?"":"!")+(_one?"$":"")+_fname; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
index 980daf0f..9fad2767 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/Type.java
@@ -1,6 +1,5 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.codegen.GlobalBits;
import com.seaofnodes.simple.util.*;
import java.util.*;
@@ -57,38 +56,35 @@ 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 = 5; // End of the Simple Types
+ static final byte TSIMPLE = 6; // End of the Simple Types
+ static final byte TSCALAR = 6; // All register/field values, excluding control and memory
- 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 TPTR = 7; // All nil-able scalar 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 =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 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 TSTRUCT =16; // Structs; tuples with named fields
- static final byte TTUPLE =17; // Tuples; finite collections of unrelated Types, kept in parallel
-
- static final byte TMAX =18;
+ static final byte TFLD =17; // Named fields in structs
// 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_nokids() { return _type < TCYCLIC; }
+ public boolean is_simple() { return _type < TSIMPLE; }
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
@@ -141,7 +137,7 @@ public class Type /*implements Cloneable*/ {
// Factory method which interns "this"
@SuppressWarnings("unchecked")
- public <T extends Type> T intern() {
+ <T extends Type> T intern() {
//assert check();
assert !_terned; // Do not ask for already-interned
T t2 = (T)INTERN.get(this);
@@ -222,16 +218,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 is_nokids(); return _type==t._type && eq(t); }
+ boolean cycle_eq(Type t) { assert _type < TCYCLIC; 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
- 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
+ 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
// Clear and re-insert the basic Type INTERN table
@@ -260,7 +256,6 @@ 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
@@ -271,14 +266,12 @@ 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);
- // TypeConAry meet its element type is OK
+ // Constant arrays meet their integer element types.
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);
- // 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;
+ return Type.BOTTOM; // Mixing 2 unrelated types
}
// Compute meet right now. Overridden in subclasses.
@@ -293,7 +286,7 @@ public class Type /*implements Cloneable*/ {
// RHS TypeNil vs NIL/XNIL
if( _type==TNIL || _type==TXNIL ) {
if( t instanceof TypeNil ptr ) return _type==TNIL ? ptr.meet0() : ptr.meetX();
- if( t instanceof TypeScalar scalar ) {
+ if( t instanceof TypeScalar ) {
if( t._type==TSCALAR ) return t.isHigh() ? this : t;
return TypeScalar.BOT;
}
@@ -322,7 +315,7 @@ public class Type /*implements Cloneable*/ {
case TXCTRL-> TCTRL;
case TNIL -> TXNIL;
case TXNIL -> TNIL;
- default -> throw Utils.TODO("Should not reach here: unknown simple type");
+ default -> throw Utils.TODO(); // Should not reach here
});
}
@@ -336,13 +329,24 @@ public class Type /*implements Cloneable*/ {
}
// True if this "isa" t; e.g. 17 isa TypeInteger.BOT
- public boolean isa( Type t ) { return meet(t)==t; }
+ // Applies for pessimistic case
+ 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 this==NIL ? TypePtr.XNPTR : TypePtr.NPTR; }
+ public Type nonZero() { return 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; }
@@ -350,7 +354,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 accumulate types without
+ // recursive descent visit of all types; this will accumilate 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
@@ -363,8 +367,7 @@ public class Type /*implements Cloneable*/ {
// "C0<->D0" cycle.
Type recurOpen() { assert VISIT.isEmpty(); VISIT.put(0L,BOTTOM); return this; }
- Type recurClose() { return recurClose(null); }
- Type recurClose(Type[] types) {
+ Type recurClose() {
VISIT.remove(0L); // Just ignore the sentinel
Ary<Type> ts = new Ary<>(Type.class);
ts.addAll(VISIT.values());
@@ -388,9 +391,6 @@ 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 )
@@ -432,12 +432,10 @@ public class Type /*implements Cloneable*/ {
free(this);
}
- Type free(Type free) {
- return this;
- }
+ Type free(Type free) { return this; }
boolean isFree() { return false; }
- final <T extends Type> T delayFree(Type free) {
+ <T extends Type> T delayFree(Type free) {
assert !free._terned;
FREES.push(free);
return (T)this;
@@ -446,7 +444,7 @@ public class Type /*implements Cloneable*/ {
final Type install() {
if( this instanceof TypeStruct ) {
Type x = _intern(); // Stop the recursion
- if( x!=this ) return x.delayFree(this);
+ assert x==this;
}
int nkids = nkids();
for( int i=0; i<nkids; i++ ) {
@@ -459,7 +457,7 @@ public class Type /*implements Cloneable*/ {
}
}
}
- return this instanceof TypeStruct || _terned ? this : _intern();
+ return this instanceof TypeStruct ? this : _intern();
}
// Strict constant values, things on the lattice centerline.
@@ -468,12 +466,8 @@ 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 is_nokids(); return true; }
+ boolean _isFinal() { assert _type < TCYCLIC; return true; }
public final Type makeRO() {
if( isFinal() ) return this;
@@ -481,154 +475,30 @@ public class Type /*implements Cloneable*/ {
}
Type _makeRO() { return this; }
- /** Declared shape used when a value is published into mutable memory. */
- public final Type makeStorage() {
- return recurOpen()._makeStorage().recurClose();
- }
- 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 _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");
+ // 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();
};
}
-
- // 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;
+ public final Type glb(boolean mem) {
+ if( isGLB(mem) ) return this;
+ Type glb = recurOpen()._glb(mem).recurClose();
+ assert this.isa(glb);
+ return glb;
}
+ Type _glb(boolean mem) { assert is_simple(); return Type.BOTTOM; }
+ Type _close(HashMap<String,Type> types) { return this; }
+ public Type widen() { return this; }
// ----------------------------------------------------------
@@ -663,4 +533,11 @@ public class Type /*implements Cloneable*/ {
// This is used by error messages, and is a shorted print.
public String str() { return STRS[_type]; }
+
+ // Closing declarations changes the meaning of forward shapes. Upgrade
+ // the whole graph together before starting the pessimistic fixed point.
+ public final Type upgradeType(HashMap<String,Type> types) {
+ return recurOpen()._upgradeType(types).recurClose();
+ }
+ Type _upgradeType(HashMap<String,Type> types) { return this; }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
index c9093e3b..d7ce8503 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAry.java
@@ -1,9 +1,8 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.BAOS;
+import java.io.ByteArrayOutputStream;
import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
-import java.util.HashMap;
/**
* Represents a constant array of primitives
@@ -56,7 +55,8 @@ public abstract class TypeConAry<A> extends TypeScalar {
@Override public boolean isHigh() { return this==TOP; }
@Override boolean _isConstant() { return true; }
- @Override Type _makeStorage() { return this; }
+ @Override Type _glb(boolean mem) { return this; }
+ @Override boolean _isGLB(boolean mem) { return true; }
// Meet-over-elements type
public Type elem() {
@@ -74,17 +74,6 @@ public abstract class TypeConAry<A> extends TypeScalar {
public abstract long at8(int idx);
public abstract int len();
@Override public abstract int log_size();
- public void write( BAOS baos ) { throw Utils.TODO("Should not reach here: abstract constant array cannot be written"); }
+ public void write( ByteArrayOutputStream 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
index 5877c309..095cdeca 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeConAryB.java
@@ -1,6 +1,6 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.BAOS;
+import java.io.ByteArrayOutputStream;
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( BAOS baos ) { baos.write(_ary); }
+ @Override public void write( ByteArrayOutputStream baos ) { baos.write(_ary,0,_ary.length); }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
index d410e9cf..bb00ee91 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFloat.java
@@ -1,11 +1,9 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
-import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.Ary;
import java.util.ArrayList;
-import java.util.HashMap;
/**
* Float Type
@@ -53,7 +51,7 @@ public class TypeFloat extends TypeScalar {
case 0 -> ""+_con+((float)_con==_con ? "f" : "");
case 32 -> "f32";
case 64 -> "flt";
- default -> throw Utils.TODO("Should not reach here: unknown float size");
+ default -> throw Utils.TODO();
};
}
private boolean isF32() { return ((float)_con)==_con; }
@@ -90,36 +88,16 @@ public class TypeFloat extends TypeScalar {
return _isConstant() ? this : new TypeFloat((byte)-_sz,0); // Constants are a self-dual
}
- @Override boolean _isConstant() { return _sz==0; }
-
- @Override Type _makeStorage() {
+ @Override boolean _isGLB(boolean mem) { return _glb(mem)==this; }
+ @Override Type _glb(boolean mem) {
+ if( !mem ) return F64;
if( _isConstant() ) return isF32() ? F32 : F64;
return isHigh() ? dual() : this;
}
- @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 boolean _isConstant() { return _sz==0; }
+ @Override public Type makeZero() { return FZERO; }
@Override
int hash() { return (int)(Double.hashCode(_con) ^ _sz ^ (1<<17)); }
@Override
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
index 56f47bff..602aaaae 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeFunPtr.java
@@ -1,6 +1,8 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import com.seaofnodes.simple.util.Ary;
import java.util.*;
/**
@@ -19,25 +21,27 @@ public class TypeFunPtr extends TypeNil {
// A TypeFunPtr is Signature and a set of functions.
public Type[] _sig;
public Type _ret;
- 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;
+ // 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
private static final Ary<TypeFunPtr> FREE = new Ary<>(TypeFunPtr.class);
- private TypeFunPtr(byte nil) { super(TFUNPTR,nil); }
- private TypeFunPtr init(byte nil, boolean open, Type[] sig, Type ret, int[] fidxs ) {
+ 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 ) {
assert sig != null;
_nil = nil;
_open = open;
_sig = sig;
_ret = ret;
- assert _fidxs == null;
- _fidxs= XInt.intern(fidxs);
+ _fidxs= fidxs;
return this;
}
- 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);
+ 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);
}
@Override TypeFunPtr free(Type t) {
TypeFunPtr fun = (TypeFunPtr)t;
@@ -46,107 +50,72 @@ 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, 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);
- }
- // 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;
+ 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 ? 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));
+ sig = nargs == 0 ? TEMPTY : Arrays.copyOf(sig,nargs);
+ 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); }
@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 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 TypeFunPtr makeFrom( int fidx ) { return make((byte)2, _open, _sig,_ret,1L<<fidx ); }
+
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, 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 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 void gather(ArrayList<Type> ts) {
- 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));
+ ts.add(TEST); ts.add(TEST0); ts.add(BOT); ts.add(MAIN); ts.add(CALLOC);
// Explicit trailing defaults must agree with the implicit argument tail.
for( boolean open : new boolean[]{false,true} ) {
- 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));
+ 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));
}
}
- private static final Type[] ARG_EMPTY = new Type[0];
@Override
Type xmeet(Type t) {
TypeFunPtr that = (TypeFunPtr) t;
- 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));
+ // 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);
}
-
static Type[] xmeet( Type[] ts0, Type[] ts1) {
if( ts0==ts1 ) return ts0;
assert ts0.length==ts1.length;
@@ -156,7 +125,7 @@ public class TypeFunPtr extends TypeNil {
return ts;
}
- @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), XInt.dual(_fidxs)); }
+ @Override TypeFunPtr xdual() { return malloc(dual0(), !_open, xdual(_sig), _ret.dual(), ~_fidxs); }
static Type[] xdual(Type[] ts) {
Type[] dual = new Type[ts.length];
for( int i=0; i<ts.length; i++ )
@@ -168,7 +137,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, XInt.dual(_fidxs));
+ TypeFunPtr d = malloc(dual0(),!_open,sigs,null,~_fidxs);
(_dual = d)._dual = this; // Cross link duals
// Return rdual
d._ret = _ret._terned ? _ret.dual() : _ret.rdual();
@@ -178,96 +147,53 @@ public class TypeFunPtr extends TypeNil {
return d;
}
- // 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);
+ // 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); }
- return make((byte)3,_open,_sig,_ret,_fidxs);
- }
-
- @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs== XInt.EMPTY); }
+ @Override public boolean isHigh() { return _nil <= 1 || (_nil==2 && _fidxs==0); }
- @Override public boolean _isConstant() { return (_nil==2 && XInt.isConstant(_fidxs)) || (_nil==3 && _fidxs== XInt.EMPTY); }
+ @Override public boolean _isConstant() { return (_nil==2 && Long.bitCount(_fidxs)==1) || (_nil==3 && _fidxs==0); }
@Override boolean _isFinal() { return true; }
- @Override TypeFunPtr _makeStorage() { return this; }
-
- @Override TypeFunPtr _close( String name, HashMap<String, Type> TYPES ) {
+ @Override boolean _isGLB(boolean mem) { return true; }
+ @Override TypeFunPtr _glb(boolean mem) { return this; }
+ @Override TypeFunPtr _close(java.util.HashMap<String,Type> types) {
Type[] sig = new Type[_sig.length];
- TypeFunPtr fun = malloc(_nil,true,sig,null,_fidxs);
+ TypeFunPtr fun = malloc(_nil,false,sig,null,_fidxs);
// Now start the recursion
- fun._ret = _ret._close(name, TYPES );
+ fun._ret = _ret._close(types);
for( int i=0; i<sig.length; i++ )
- sig[i] = _sig[i]._close(name, TYPES );
- return fun;
- }
+ sig[i] = _sig[i]._close(types);
- // 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 int[] fidxs() { return _fidxs; }
+ public long fidxs() { return _fidxs; }
public Type ret() { return _ret; }
public int nargs() { return _sig.length; }
- public int fidx() { return XInt.onlyBit(_fidxs); }
+ public int fidx() { assert Long.bitCount(_fidxs)==1; return Long.numberOfTrailingZeros(_fidxs); }
public int nfcns() {
- if( XInt.isHigh(_fidxs) ) return Integer.MAX_VALUE; // Infinite choices
- return XInt.bitCount(_fidxs);
+ if( _fidxs < 0 ) return Integer.MAX_VALUE; // Infinite choices
+ return Long.bitCount(_fidxs);
}
// Used to walk over type structures
- @Override public int nkids() { return _sig.length+1; }
- @Override public Type at( int idx ) {
+ @Override int nkids() { return _sig.length+1; }
+ @Override Type at( int idx ) {
return idx == _sig.length ? _ret : _sig[idx];
}
- @Override public void set( int idx, Type t ) {
+ @Override 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() ^ XInt.hash(_fidxs) ^ super.hash();
+ long hash = _ret.hashCode() ^ _fidxs ^ super.hash();
for( Type arg : _sig )
hash = hash*19 ^ arg.hashCode();
return Utils.fold(hash);
@@ -316,26 +242,39 @@ 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==null ? "---" : t.str()).p(" "); // Short form in signature
- if( !_open ) sb.p("^ ");
+ sb.p(t.str()).p(" "); // Short form in signature
_ret.print(sb.p(html ? "&rarr; " : "-> "),visit,html).p(" #");
// Print fidxs
return sb.p(printFIDX()).p("}").p(q());
}
String printFIDX() {
String tilde = isHigh() ? "~" : "";
- String fidx = XInt.str(_fidxs);
+ 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
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); }
+
+ @Override Type _upgradeType(java.util.HashMap<String,Type> types) {
+ TypeFunPtr old = (TypeFunPtr)VISIT.get(_uid);
+ if( old!=null ) return old;
+ Type[] sig = new Type[_sig.length];
+ TypeFunPtr fun = malloc(_nil,_open,sig,null,_fidxs);
+ VISIT.put(_uid,fun);
+ fun._ret = _ret._upgradeType(types);
+ for( int i=0; i<sig.length; i++ ) sig[i] = _sig[i]._upgradeType(types);
+ return fun;
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
index a0c2266d..1b0019d4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeInteger.java
@@ -1,11 +1,9 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.AryInt;
-import com.seaofnodes.simple.util.BAOS;
+import com.seaofnodes.simple.util.SB;
import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
-import java.util.HashMap;
/**
* Integer Type
@@ -52,8 +50,8 @@ public class TypeInteger extends TypeScalar {
return this;
}
- public Type same_but_slightly_wider_than( ) {
- return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : BOT;
+ public Type same_but_slightly_wider_than( Type minType ) {
+ return _widen < 3 ? make(_min,_max, (byte)(_widen+1)) : minType;
}
// Force max widen
@Override public Type widen() {
@@ -123,17 +121,14 @@ public class TypeInteger extends TypeScalar {
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() ) 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;
+ 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();
}
public long value() { assert _isConstant(); return _min; }
@@ -162,7 +157,7 @@ public class TypeInteger extends TypeScalar {
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;
@@ -173,42 +168,13 @@ public class TypeInteger extends TypeScalar {
}
@Override public Type makeZero() { return ZERO; }
@Override boolean _isConstant() { return _min == _max; }
- @Override Type _makeStorage() {
+ @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
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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
index 9ae68bcf..02cfc57b 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMem.java
@@ -1,9 +1,10 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
import java.util.ArrayList;
import java.util.BitSet;
-import java.util.HashMap;
/**
@@ -17,35 +18,13 @@ 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() { super(TMEM); CNT++; }
- private TypeMem init(int alias, Type t, boolean one, boolean clz, boolean xfinal, int[] fidxs, int[] aliases) {
+ 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);
_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) {
@@ -53,62 +32,25 @@ 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, 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 malloc(int alias, Type t) {
+ return FREE.isEmpty() ? new TypeMem(alias,t) : FREE.pop().init(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);
+ public static TypeMem make(int alias, Type t) {
+ TypeMem f = malloc(alias,t);
TypeMem f2 = f.intern();
if( f2==f ) return f;
return VISIT.isEmpty() ? f2.free(f) : f2.delayFree(f);
}
- public static TypeMem make(int alias, Type t) { return make(alias,t,false,false,false,null,null); }
- // Make a private memory
- public static TypeMem makePrivate(Type t) { return make(1,t,true,false,false,escapeFIDX(XInt.EMPTY,t), escapeAlias(XInt.EMPTY,t)); }
- public TypeMem makeFrom(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 final TypeMem TOP = make(1, Type.TOP );
+ public static final TypeMem BOT = make(1, Type.BOTTOM);
- public static final TypeMem TOP = make(1, Type.TOP ,true ,true, true , XInt.EMPTY, XInt.EMPTY);
- public static final TypeMem BOT = make(1, Type.BOTTOM,false,false,true , XInt.FULL , XInt.FULL );
- public static final TypeMem START= make(1,Type.BOTTOM,false,false,false, XInt.EMPTY, XInt.EMPTY);
- public static final TypeMem SELF_MEM = make(1, TypeStruct.BOT);
-
- public static void gather(ArrayList<Type> ts) {
- ts.add(make(2,Type.NIL));
- ts.add(make(2,TypeInteger.ZERO));
- ts.add(BOT);
- 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));
- }
+ public static void gather(ArrayList<Type> ts) { ts.add(make(2,Type.NIL)); ts.add(make(2,TypeInteger.ZERO)); ts.add(BOT); }
@Override
TypeMem xmeet(Type t) {
@@ -117,137 +59,59 @@ public class TypeMem extends Type {
if( that==TOP ) return this;
if( this==BOT ) return BOT;
if( that==BOT ) return BOT;
- // 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;
+ // Different slices lose their alias and contents together. Their
+ // scalar meet alone cannot describe the unsplit bulk memory.
+ if( _alias!=that._alias ) return BOT;
+ int alias = _alias;
Type mt = _t.meet(that._t);
- 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);
+ return make(alias,mt);
}
@Override
- Type xdual() { return malloc(_alias,_t.dual(),!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs)); }
+ Type xdual() { return _t._dual==_t ? this : malloc(_alias,_t.dual()); }
@Override TypeMem rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMem d = malloc(_alias,null,!_one,!_clz,!_final, XInt.dual(_escFs), XInt.dual(_escAs));
+ TypeMem d = malloc(_alias,null);
(_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 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 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 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() + (_one ? 8196 : 0) + (_clz ? 4098 : 0) + (_final ? 16392 : 0) + XInt.hash(_escFs) + XInt.hash(_escAs); }
+ @Override int hash() { return 9876543 + _alias + _t.hashCode(); }
@Override boolean eq(Type t) {
TypeMem that = (TypeMem) t; // Invariant
- return _alias == that._alias && _t == that._t && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs;
+ return _alias == that._alias && _t == that._t;
}
@Override boolean cycle_eq(Type t) {
TypeMem that = (TypeMem) t; // Invariant
- return _alias == that._alias && _one == that._one && _clz == that._clz && _final == that._final && _escFs == that._escFs && _escAs == that._escAs && _t.cycle_eq(that._t);
+ return _alias == that._alias && _t.cycle_eq(that._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 int nkids() { return 1; }
+ @Override Type at( int idx ) { return _t; }
+ @Override void set( int idx, Type t ) { _t = t; }
@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('#');
- if( !_final ) sb.p('!'); // Mutable memory
- if( _one ) sb.p(_clz ? 'z' : '-');
+ sb.p("#");
if( _alias==0 ) return sb.p(_t._type==TTOP ? "TOP" : "BOT");
- sb.p(_alias).p(":[");
- _t.print(sb,visit,html).p(",");
- XInt.str(sb,_escFs).p(",");
- return XInt.str(sb,_escAs).p("]");
+ return _t.print(sb.p(_alias).p(":"),visit,html);
}
- @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();
- }
-
-
- // 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;
- };
- }
+ @Override public String str() { return toString(); }
- 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;
- };
+ @Override Type _upgradeType(java.util.HashMap<String,Type> types) {
+ return make(_alias,_t._upgradeType(types));
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
index 175b1c63..4a28b919 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeMemPtr.java
@@ -1,9 +1,8 @@
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.BAOS;
+import com.seaofnodes.simple.util.Utils;
import java.util.*;
/**
@@ -30,26 +29,22 @@ public class TypeMemPtr extends TypeNil {
// Deep read-only access through this pointer; independent of slot finality.
public boolean _ro;
- public boolean _one; // Singleton pointer value; the pointer itself is constant.
- public boolean _pub; // Escaped/public pointer. False means constructor-private.
+ public boolean _one; // Singleton instance
private static final Ary<TypeMemPtr> FREE = new Ary<>(TypeMemPtr.class);
- 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; }
+ 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; }
// Return a filled-in TypeMemPtr; either from free list or alloc new.
- 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);
+ private static TypeMemPtr malloc(byte nil, TypeStruct obj, boolean one) {
+ return FREE.isEmpty() ? new TypeMemPtr(nil,obj,one) : FREE.pop().init(nil,obj,one);
}
// All fields directly listed
public static TypeMemPtr make(byte nil, TypeStruct obj, boolean one) {
- return make(nil,obj,one,true);
+ return make(nil,obj,one,obj==TypeStruct.BOT);
}
- 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);
+ private static TypeMemPtr make(byte nil, TypeStruct obj, boolean one, boolean ro) {
+ TypeMemPtr tmp = malloc(nil, obj, one).access(ro);
TypeMemPtr t2 = tmp.intern();
if( t2==tmp ) return tmp;
return VISIT.isEmpty() ? t2.free(tmp) : t2.delayFree(tmp);
@@ -58,8 +53,6 @@ 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);
@@ -70,22 +63,17 @@ 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, _pub,_ro); }
+ public TypeMemPtr makeFrom(TypeStruct obj) { return obj==_obj ? this : make(_nil, obj, _one,_ro); }
public TypeMemPtr makeNullable() { return makeFrom((byte)3); }
- /** 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)); }
+ @Override TypeMemPtr makeFrom(byte nil) { return nil==_nil ? this : make(nil, _obj, _one,_ro); }
+ public TypeMemPtr makeHigh() { return make((byte)0,_obj.makeHigh(),false); }
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, _pub);
+ TypeMemPtr ptr = malloc(_nil, _obj, _one);
ptr._ro = ro;
TypeMemPtr res = ptr.intern();
if( res==ptr ) return ptr;
@@ -115,73 +103,69 @@ public class TypeMemPtr extends TypeNil {
public TypeNil xmeet(Type t) {
TypeMemPtr that = (TypeMemPtr) t;
assert !isFree() && !that.isFree();
- // 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);
+ return make(xmeet0(that), (TypeStruct)_obj.meet(that._obj), _one & that._one,_ro | that._ro);
}
@Override
- TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), !_one, !_pub).access(!_ro); }
+ TypeMemPtr xdual() { return malloc( dual0(), _obj.dual(), _one).access(!_ro); }
@Override TypeMemPtr rdual() {
if( _dual!=null ) return dual();
assert !_terned;
- TypeMemPtr d = malloc(dual0(), null, !_one, !_pub).access(!_ro);
+ TypeMemPtr d = malloc(dual0(), null, _one).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,true,_ro); }
+ @Override Type meet0() { return _nil==3 ? this : make((byte)3,_obj,false,_ro); }
- @Override boolean _isConstant() { return _one; }
+ @Override boolean _isConstant() { return _one && _obj._isConstant(); }
@Override boolean _isFinal() { return _ro; }
@Override TypeMemPtr _makeRO() { return withAccess(true); }
- @Override TypeMemPtr _close( String name, HashMap<String, Type> TYPES ) { return malloc(_nil,_obj._close(name, TYPES ),_one,_pub).access(_ro); }
+ @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(HashMap<String,Type> types) { return makeFrom(_obj._close(types)); }
+
- @Override Type _upgradeType(HashMap<String,Type> TYPES) {
- return makeFrom((TypeStruct)_obj._upgradeType(TYPES));
+ // 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);
}
+ // 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) ^ (_pub ? 4096 : 0); }
+ @Override int hash() { return (_ro ? 8192 : 0) ^ _obj.hashCode() ^ super.hash() ^ (_one ? 2048 : 0); }
@Override boolean eq(Type t) {
TypeMemPtr ptr = (TypeMemPtr)t; // Invariant
- return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _pub == ptr._pub && _obj == ptr._obj;
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _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 && _pub == ptr._pub && _obj.cycle_eq(ptr._obj);
+ return _ro==ptr._ro && super.eq(ptr) && _one == ptr._one && _obj.cycle_eq(ptr._obj);
}
- @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 int nkids() { return 1; }
+ @Override Type at( int idx ) { return _obj; }
+ @Override void set( int idx, Type t ) { _obj = (TypeStruct)t; }
@Override public String str() {
if( this==BOT ) return "PtrBot";
if( this==TOP ) return "PtrTop";
if( this== NOTBOT) return "*void";
- return x()+"*"+(_obj==null?"---":_obj.str())+q();
+ return x()+"*"+_obj.str()+q();
}
@Override SB _print(SB sb, BitSet visit, boolean html ) {
@@ -190,4 +174,8 @@ public class TypeMemPtr extends TypeNil {
if( this== NOTBOT) return sb.p("*void");
return _obj.print(sb.p(x()).p("*"),visit,html).p(q());
}
+
+ @Override Type _upgradeType(java.util.HashMap<String,Type> types) {
+ return makeFrom((TypeStruct)_obj._upgradeType(types));
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
index 2c1c1d76..32505aa2 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeNil.java
@@ -51,6 +51,7 @@ public abstract class TypeNil extends TypeScalar {
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/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
index 38941ed1..75478119 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypePtr.java
@@ -1,12 +1,9 @@
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.
@@ -26,7 +23,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("Should not reach here: plain pointer nil state is canonical"); }
+ TypeNil makeFrom(byte nil) { throw Utils.TODO(); }
@Override public TypeNil xmeet(Type t) {
TypePtr that = (TypePtr) t;
@@ -50,16 +47,8 @@ public class TypePtr extends TypeNil {
// 0->xscalar, 1->nscalar, 2->nscalar, 3->scalar
@Override Type meetX() { return _nil==0 ? XNIL : (_nil==3 ? PTR : NPTR); }
- @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; }
+ boolean _isGLB(boolean mem) { return this==PTR; }
+ @Override TypePtr _glb(boolean mem) { return 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/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
index 8a12f50a..a1389540 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeRPC.java
@@ -1,26 +1,32 @@
package com.seaofnodes.simple.type;
import com.seaofnodes.simple.util.Ary;
-import com.seaofnodes.simple.util.BAOS;
-import java.util.*;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.HashSet;
/**
* Return Program Control or Return PC or RPC
*/
public class TypeRPC extends Type {
- // A set of CallEndNode IDs (or StopNode); commonly just one. High-bit
- // XInt sets represent complements/infinite sets.
- public int[] _rpcs;
+ // 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;
private static final Ary<TypeRPC> FREE = new Ary<>(TypeRPC.class);
- private TypeRPC init(int[] rpcs) { _rpcs = XInt.intern(rpcs); return this; }
+ private TypeRPC init(boolean any, HashSet<Integer> rpcs) { _any=any; _rpcs=rpcs; return this; }
private TypeRPC() { super(TRPC); }
- private static TypeRPC malloc(int[] rpcs) {
- return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(rpcs);
+ private static TypeRPC malloc(boolean any, HashSet<Integer> rpcs) {
+ return (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs);
}
- private static TypeRPC make(int[] rpcs) {
- TypeRPC rpc = malloc(rpcs);
+ private static TypeRPC make(boolean any, HashSet<Integer> rpcs) {
+ TypeRPC rpc = (FREE.isEmpty() ? new TypeRPC() : FREE.pop()).init(any,rpcs);
TypeRPC t2 = rpc.intern();
return t2==rpc ? rpc : t2.free(rpc);
}
@@ -35,72 +41,71 @@ public class TypeRPC extends Type {
@Override boolean isFree() { return _rpcs==null; }
public static TypeRPC constant(int cend) {
- return make(XInt.make(cend));
- }
-
- public int rpc() {
- return XInt.onlyBit(_rpcs);
+ HashSet<Integer> rpcs = new HashSet<>();
+ rpcs.add(cend);
+ return make(false,rpcs);
}
-
- public static final TypeRPC BOT = make(XInt.FULL);
+ public static final TypeRPC BOT = make(true,new HashSet<>());
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 == XInt.FULL ) return "$[ALL]";
- if( _rpcs == XInt.EMPTY ) return "$[]";
- return "$"+XInt.str(_rpcs);
+ if( _rpcs.isEmpty() )
+ return _any ? "$[ALL]" : "$[]";
+ if( _rpcs.size()==1 )
+ for( Integer rpc : _rpcs )
+ return _any ? "$[-"+rpc+"]" : "$["+rpc+"]";
+ return "$["+(_any ? "-" : "")+_rpcs+"]";
}
@Override
public TypeRPC xmeet(Type other) {
TypeRPC rpc = (TypeRPC)other;
- return make(XInt.meet(_rpcs,rpc._rpcs));
+ // 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);
}
- @Override TypeRPC xdual() { return malloc(XInt.dual(_rpcs)); }
- @Override Type rdual() {
- TypeRPC d = xdual();
- (_dual = d)._dual = this;
- return d;
- }
+ @Override Type xdual() { return malloc(!_any,_rpcs); }
- @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 boolean _isConstant() { return !_any && _rpcs.size()==1; }
+ @Override boolean _isGLB(boolean mem) { return true; }
@Override
- int hash() { return XInt.hash(_rpcs); }
+ int hash() { return _rpcs.hashCode() ^ (_any ? -1 : 0) ; }
@Override
public boolean eq( Type t ) {
TypeRPC rpc = (TypeRPC)t; // Contract
- return _rpcs==rpc._rpcs;
+ return _any==rpc._any && _rpcs.equals(rpc._rpcs);
}
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
index eaba0a1c..1b41db13 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeScalar.java
@@ -1,57 +1,31 @@
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.
+ * Values that fit in a register or field: integers, floats and pointers.
+ * These bounds exclude control and memory. An unresolved load can produce a
+ * scalar without making the parser guess which kind of value it will hold.
*/
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;
+ 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 Type xmeet(Type t) { return isHigh() ? t : 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 boolean isHigh() { return this==TOP; }
+ @Override boolean _isGLB(boolean mem) { return this==BOT; }
+ @Override Type _glb(boolean mem) { return BOT; }
@Override public String str() { return isHigh() ? "~scalar" : "scalar"; }
@Override SB _print(SB sb, BitSet visit, boolean html) { return sb.p(str()); }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
index 4c5ae6ea..8eec6dd4 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeStruct.java
@@ -10,65 +10,42 @@ 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 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.
- */
+ // 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.
public String _name; // Struct name
- public boolean _open;
- public boolean _fref;
+ public boolean _open; // infinite fields are all true:BOTTOM, false:TOP
public Field[] _fields;
- 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());
+ private TypeStruct(String name, boolean open, Field[] fields) {
+ super(TSTRUCT);
_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, boolean fref, Field[] fields) {
- return (FREE.isEmpty() ? new TypeStruct() : FREE.pop()).init(name,open,fref,fields);
+ 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;
}
// 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;
}
@@ -77,196 +54,83 @@ public class TypeStruct extends Type {
// All fields directly listed
public static TypeStruct make(String name, boolean open, Field... 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 ts = malloc(name, open, 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, variant body
+ // Array
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 ),
- Field.make("[]",body,bodyAlias,efinal));
- }
- // Array, known constant body
- public static TypeStruct makeAry(String name, int lenAlias, int bodyAlias, TypeConAry con) {
- Type len = TypeInteger.constant(con.len());
- return make(name,false,
- Field.make("#" ,len , lenAlias,true),
- Field.make("[]" ,con.elem(),bodyAlias,true),
- Field.make("[con]",con ,bodyAlias,true));
+ Field.make("#" ,len , lenAlias,true ,false),
+ Field.make("[]",body,bodyAlias,efinal,false));
}
-
- public TypeStruct makeInit() {
+ public TypeStruct makeHigh() {
Field[] fs = new Field[_fields.length];
- 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);
+ for( int i=0; i<_fields.length; i++ )
+ fs[i] = _fields[i].makeFrom(Type.TOP);
+ return make(_name,false,fs);
}
public TypeStruct add( Field f ) {
- assert find(f._fname)==-1; // No double field names
+ assert _open && find(f._fname)==-1; // No double field names
Field[] flds = Arrays.copyOf(_fields,_fields.length+1);
flds[_fields.length] = f;
- return make(_name,_open,_fref,flds);
+ return make(_name,true,flds);
}
public TypeStruct replace( Field f ) {
+ assert !_open;
Field[] flds = Arrays.copyOf(_fields,_fields.length);
flds[find(f._fname)] = f;
- 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);
+ return make(_name,false,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(_name, null).recurClose();
+ return close(null);
}
- @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;
- 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(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;
+ public final TypeStruct close(HashMap<String,Type> types) {
+ return (TypeStruct)recurOpen()._close(types).recurClose();
}
-
- // 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);
+ @Override TypeStruct _close(HashMap<String,Type> types) {
+ Object key = isAry() ? _uid : _name;
+ TypeStruct ts = (TypeStruct)VISIT.get(key);
if( ts!=null ) return ts;
- // Main work: replace open with closed
- if( _open )
- return base;
- // Build recursive version
- ts = recurPre(_uid,_name,false,_fref);
+ if( _open && types!=null && types.get(_name) instanceof TypeMemPtr ptr && !ptr._obj._open )
+ return ptr._obj._close(types);
+ // Closing A must not pretend that an undeclared B is an empty
+ // definition. The root is visited first; self references find it above.
+ // Keep other empty forward shapes open until their own declaration.
+ if( _open && _fields.length==0 && VISIT.size()>1 ) return this;
+ ts = recurPre(key,false);
Field[] flds = ts._fields;
+
// Now start the recursion
for( int i=0; i<flds.length; i++ )
- flds[i].setType(_fields[i]._t._upgradeType(TYPES));
+ flds[i].setType(_fields[i]._t._close(types));
+
return ts;
}
static final AryInt CEQUALS = new AryInt();
- 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 BOT = open("$STRUCT");
+ public static final TypeStruct TOP = BOT.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",-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));
+ private static final TypeStruct ABC = makeAry("[]u8",TypeInteger.constant(3),-1,TypeConAryB.ABC,-4,true);
// A pair of self-cyclic types
- 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));
+ 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();
- @Override public boolean isHigh() { return _name==TOPNAME; }
public static void gather(ArrayList<Type> ts) {
ts.add(BOT);
@@ -274,15 +138,14 @@ 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);
+ Field fcalloc = Field.make("calloc",TypeFunPtr.CALLOC,-2,true,true);
TypeStruct scalloc = make("calloc",false,fcalloc);
ts.add(scalloc);
@@ -291,7 +154,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==fname )
+ if( _fields[i]._fname.equals(fname) )
return i;
return -1;
}
@@ -317,27 +180,28 @@ 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._extern != f1._extern )
+ if( !f0._fname.equals(f1._fname) || f0._alias != f1._alias || f0._one != f1._one )
return BOT;
}
@@ -351,7 +215,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,name,_open | that._open,_fref & that._fref);
+ ts = recurPre(pid,_open | that._open);
// Recurse all common fields
Field[] flds = ts._fields;
@@ -372,10 +236,12 @@ 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(sdual(),!_open,!_fref,flds);
+ return malloc(_name,!_open,flds);
}
// Recursive dual
@@ -383,23 +249,18 @@ public class TypeStruct extends Type {
if( _dual!=null ) return dual();
assert !_terned;
Field[] flds = new Field[_fields.length];
- TypeStruct d = malloc(sdual(),!_open,!_fref,flds);
+ TypeStruct d = malloc(_name,!_open,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;
}
- private String sdual() {
- return
- _name==TOPNAME ? BOTNAME :
- _name==BOTNAME ? TOPNAME :
- _name;
- }
+ // Is forward-reference
+ @Override public boolean isFRef() { return _open; }
@Override boolean _isConstant() {
- 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
+ if( VISIT.containsKey(_uid) ) return true; // Cycles assume constant
VISIT.put(_uid,this);
// Check all fields for being constant
for( Field field : _fields )
@@ -410,76 +271,89 @@ public class TypeStruct extends Type {
// All fields are final
@Override boolean _isFinal() {
- if( _open ) return false; // May have more non-final fields
+ if( _open ) return false; // May have more more non-final fields
if( VISIT.containsKey(_uid) ) // Test: been here before?
return true; // Cycles assume final
- VISIT.put(_uid,this); // Set: don't do this again
+ VISIT.put(_uid,this); // Set: dont do this again
for( Field fld : _fields )
if( !fld._isFinal() )
return false;
return true;
}
- private TypeStruct recurPre(Object key, String name, boolean open, boolean fref) {
+ private TypeStruct recurPre(Object key, boolean open) {
// 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, fref, flds );
- ts._aliases = aliases();
+ TypeStruct ts = malloc(_name, open, flds );
VISIT.put(key,ts);
return ts;
}
- 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);
+ // Make a read-only version
+ @Override TypeStruct _makeRO() {
+ // Check for already visited
+ TypeStruct ts = (TypeStruct)VISIT.get(_name);
if( ts!=null ) return ts; // Already visited
- ts = recurPre(key,name,open,fref);
+ ts = recurPre(_name,_open); // Make a new type with blank fields
Field[] flds = ts._fields;
- for( Field fld : flds )
- fld._final |= xfinal;
+ for( Field fld : flds ) fld._final = true;
// Now start the recursion
for( int i=0; i<flds.length; i++ )
- flds[i].setType(transform.apply(_fields[i]._t));
+ flds[i].setType(_fields[i]._t._makeRO());
return ts;
}
- // Make a read-only version
- @Override TypeStruct _makeRO() {
- return recurTransform(_name,_name,_open,_fref,true,Type::_makeRO);
+
+ // 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;
}
- @Override TypeStruct _makeStorage() {
- return recurTransform(_uid,_name,_open,false,false,Type::_makeStorage);
+ // 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;
}
// 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("Should not reach here: structs have no scalar log size"); }
+ @Override public int log_size() { throw Utils.TODO(); }
@Override public int size() { return offset(_fields.length); }
@Override public int alignment() {
int align = 0;
for( Field f : _fields )
- if( !f._extern ) align = Math.max(align, f._t.alignment());
+ 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 ) {
- 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;
+ return _name.equals(ts._name) && _open==ts._open && _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();
@@ -488,6 +362,7 @@ 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++ )
@@ -515,35 +390,15 @@ public class TypeStruct extends Type {
@Override
int hash() {
- long hash = _name.hashCode() ^ (_open ? 4 : 0) ^ (_fref ? 8 : 0);
+ long hash = _name.hashCode() ^ (_open ? 4 : 0);
for( Field f : _fields )
hash = Utils.rot(hash,13) ^ ((long)f._fname.hashCode() * f._alias);
return Utils.fold(hash);
}
- @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 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
SB _print(SB sb, BitSet visit, boolean html ) {
@@ -553,17 +408,12 @@ public class TypeStruct extends Type {
if( isAry() && field("[]")._t instanceof TypeConAry con )
return sb.p(con.str());
sb.p(_name);
- 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 )
+ if( html || isAry() )
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 && !_fref ) sb.p("... ");
+ if( _open ) sb.p("... ");
return sb.p("}");
}
@@ -574,10 +424,7 @@ public class TypeStruct extends Type {
}
- public boolean isAry() { return find("[]")!=-1; }
- public boolean isConAry() {
- return isAry() && _fields[1]._t instanceof TypeConAry;
- }
+ public boolean isAry() { return _fields.length>=2 && _fields[_fields.length-1]._fname=="[]"; }
public int aryBase() {
assert isAry();
@@ -615,7 +462,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]._extern )
+ if( !_fields[i]._one )
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.
@@ -627,7 +474,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]._extern ) { _offs[idx++] = -1; continue; }
+ if( _fields[i]._one ) continue;
int log = _fields[i]._t.log_size();
_offs[idx++] = offs[log]; // Field offset
offs[log] += 1<<log; // Next field offset at same alignment
@@ -652,4 +499,16 @@ public class TypeStruct extends Type {
return is;
}
+
+ @Override Type _upgradeType(HashMap<String,Type> types) {
+ Type def = types.get(_name);
+ if( def instanceof TypeMemPtr ptr && !ptr._obj._open && _open )
+ return ptr._obj;
+ TypeStruct old = (TypeStruct)VISIT.get(_uid);
+ if( old!=null ) return old;
+ TypeStruct ts = recurPre(_uid,_open);
+ for( int i=0; i<_fields.length; i++ )
+ ts._fields[i].setType(_fields[i]._t._upgradeType(types));
+ return ts;
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
index 075698dc..27a0a9d9 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
+++ b/chapter25/src/main/java/com/seaofnodes/simple/type/TypeTuple.java
@@ -1,6 +1,8 @@
package com.seaofnodes.simple.type;
-import com.seaofnodes.simple.util.*;
+import com.seaofnodes.simple.util.Ary;
+import com.seaofnodes.simple.util.SB;
+import com.seaofnodes.simple.util.Utils;
import java.util.*;
public class TypeTuple extends Type {
@@ -16,6 +18,7 @@ public class TypeTuple extends Type {
// All fields directly listed
public static TypeTuple make(Type... types) {
TypeTuple tt = malloc(types);
+ for( Type t : types ) if( !t._terned ) return tt;
TypeTuple t2 = tt.intern();
return t2==tt ? tt : t2.free(tt);
}
@@ -29,32 +32,20 @@ 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 DEAD_NEW = make(TypeMemPtr.TOP,TypeMem.TOP);
+ 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 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(STATE); ts.add(IF_TRUE); }
+ public static void gather(ArrayList<Type> ts) { ts.add(BOT); ts.add(TEST); ts.add(START); ts.add(IF_TRUE); }
@Override
Type xmeet(Type other) {
@@ -68,23 +59,18 @@ public class TypeTuple extends Type {
@Override TypeTuple xdual() {
if( _types.length==0 ) return malloc(null);
- boolean isCon=true;
- for( Type t : _types )
- if( t._dual!=t )
- // Not a constant
- return malloc(TypeFunPtr.xdual(_types));
- return this;
+ return malloc(TypeFunPtr.xdual(_types));
}
+
+ @Override int nkids() { return _types==null ? 0 : _types.length; }
+ @Override Type at(int idx) { return _types[idx]; }
+ @Override void set(int idx, Type t) { _types[idx]=t; }
@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
+ TypeTuple d = malloc(new Type[_types.length]);
+ (_dual=d)._dual=this;
for( int i=0; i<_types.length; i++ )
- ts[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual();
+ d._types[i] = _types[i]._terned ? _types[i].dual() : _types[i].rdual();
return d;
}
@@ -94,18 +80,11 @@ public class TypeTuple extends Type {
return false;
return true;
}
- @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 boolean _isFinal() { 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 TypeMemPtr _makeRO() { throw Utils.TODO(); }
- @Override public int log_size() { throw Utils.TODO("Should not reach here: tuples have no scalar storage size"); }
+ @Override public int log_size() { throw Utils.TODO(); }
@Override public int alignment() {
assert isConstant();
int align = 0;
@@ -114,30 +93,7 @@ public class TypeTuple extends Type {
return align;
}
- 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()]);
- }
+ public Type ret() { assert _types.length==3; return _types[2]; }
@Override SB _print(SB sb, BitSet visit, boolean html ) {
if( this==TOP ) return sb.p("[TOP]");
@@ -169,4 +125,10 @@ public class TypeTuple extends Type {
return true;
}
+ @Override Type _upgradeType(java.util.HashMap<String,Type> types) {
+ if( _types==null ) return this;
+ Type[] ts = new Type[_types.length];
+ for( int i=0; i<ts.length; i++ ) ts[i] = _types[i]._upgradeType(types);
+ return make(ts);
+ }
}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java b/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java
deleted file mode 100644
index 752c1419..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/type/XInt.java
+++ /dev/null
@@ -1,268 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java b/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java
deleted file mode 100644
index 61ce7387..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/util/BAOS.java
+++ /dev/null
@@ -1,162 +0,0 @@
-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/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java b/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
deleted file mode 100644
index b356ec31..00000000
--- a/chapter25/src/main/java/com/seaofnodes/simple/util/IntHashMap.java
+++ /dev/null
@@ -1,87 +0,0 @@
-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 boolean isEmpty() { return _size==0; }
- 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, resize at 3/4 full
- 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];
- }
-
- private 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 ) {
- int slot = probe(old[i]);
- _es[ slot<<1 ] = old[i ];
- _es[(slot<<1)+1] = old[i+1];
- }
- }
-
- // Just all the keys
- public int[] keySet() {
- int[] keys = new int[_size];
- int j=0;
- for( int i=0; i<_es.length; i+=2 )
- if( _es[i] != _sentinel )
- keys[j++] = _es[i];
- return keys;
- }
-
- @Override public String toString() {
- SB sb = new SB().p("[ ");
- for( int i=0; i<_es.length; i+=2 )
- if( _es[i] != _sentinel )
- sb.p('(').p(_es[i]).p(',').p(_es[i+1]).p(')').p(',');
- return sb.unchar().p(']').toString();
- }
-}
diff --git a/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java b/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java
index cc59f2a6..d7afd6eb 100644
--- a/chapter25/src/main/java/com/seaofnodes/simple/util/SB.java
+++ b/chapter25/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/chapter25/src/main/smp/sys.smp b/chapter25/src/main/smp/sys.smp
index 7705a187..6a6d3ca3 100644
--- a/chapter25/src/main/smp/sys.smp
+++ b/chapter25/src/main/smp/sys.smp
@@ -1,11 +1,199 @@
+// -*- mode: java; -*-
-// top-level default standard library import
+// 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;
+ };
+ };
-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
+ // 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);
+ };
+ };
+};
diff --git a/chapter25/src/main/smp/sys/adt.smp b/chapter25/src/main/smp/sys/adt.smp
deleted file mode 100644
index a4fe40bf..00000000
--- a/chapter25/src/main/smp/sys/adt.smp
+++ /dev/null
@@ -1,3 +0,0 @@
-
-// Abstract data types.
-val bitset = bitset;
diff --git a/chapter25/src/main/smp/sys/adt/bitset.smp b/chapter25/src/main/smp/sys/adt/bitset.smp
deleted file mode 100644
index d02cd2fe..00000000
--- a/chapter25/src/main/smp/sys/adt/bitset.smp
+++ /dev/null
@@ -1,57 +0,0 @@
-
-// 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/chapter25/src/main/smp/sys/ary.smp b/chapter25/src/main/smp/sys/ary.smp
deleted file mode 100644
index 8312e94c..00000000
--- a/chapter25/src/main/smp/sys/ary.smp
+++ /dev/null
@@ -1,24 +0,0 @@
-
-// 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/chapter25/src/main/smp/sys/aryi64.smp b/chapter25/src/main/smp/sys/aryi64.smp
deleted file mode 100644
index 98b72605..00000000
--- a/chapter25/src/main/smp/sys/aryi64.smp
+++ /dev/null
@@ -1,82 +0,0 @@
-
-// 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/chapter25/src/main/smp/sys/aryu8.smp b/chapter25/src/main/smp/sys/aryu8.smp
deleted file mode 100644
index 21cf920b..00000000
--- a/chapter25/src/main/smp/sys/aryu8.smp
+++ /dev/null
@@ -1,87 +0,0 @@
-
-// 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/chapter25/src/main/smp/sys/char.smp b/chapter25/src/main/smp/sys/char.smp
deleted file mode 100644
index 22ca4213..00000000
--- a/chapter25/src/main/smp/sys/char.smp
+++ /dev/null
@@ -1,8 +0,0 @@
-
-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/chapter25/src/main/smp/sys/io.smp b/chapter25/src/main/smp/sys/io.smp
deleted file mode 100644
index d6263ffe..00000000
--- a/chapter25/src/main/smp/sys/io.smp
+++ /dev/null
@@ -1,64 +0,0 @@
-
-// 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/chapter25/src/main/smp/sys/libc.smp b/chapter25/src/main/smp/sys/libc.smp
deleted file mode 100644
index 17ee65cb..00000000
--- a/chapter25/src/main/smp/sys/libc.smp
+++ /dev/null
@@ -1,44 +0,0 @@
-
-// 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/chapter25/src/main/smp/sys/scan.smp b/chapter25/src/main/smp/sys/scan.smp
deleted file mode 100644
index 44bedaaa..00000000
--- a/chapter25/src/main/smp/sys/scan.smp
+++ /dev/null
@@ -1,39 +0,0 @@
-
-// 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;
- };
-};