summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ASimpleReply/ASimpleReply2.md123
-rw-r--r--ASimpleReply/ASimpleReply3.md127
-rw-r--r--ASimpleReply/ASimpleReplyChain.md16
-rw-r--r--ASimpleReply/DadalReply2.md58
-rw-r--r--ASimpleReply/DadalReply3.md25
-rw-r--r--chapter21/README.md2
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java18
-rw-r--r--chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java12
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java35
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java67
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java90
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java34
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/TestC.java3
-rw-r--r--chapter21/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp6
14 files changed, 580 insertions, 36 deletions
diff --git a/ASimpleReply/ASimpleReply2.md b/ASimpleReply/ASimpleReply2.md
index 62f1a5ba..45017b61 100644
--- a/ASimpleReply/ASimpleReply2.md
+++ b/ASimpleReply/ASimpleReply2.md
@@ -7,23 +7,128 @@ The whole reply chain:
https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReplyChain.md
I find long, involved, multi-part discussions difficult to follow because
-basically we're really having 8 unrelated technical discussions. I broke up my
+basically we're really having 6 unrelated technical discussions. I broke up my
replies so each part is easier to consider on its own.
-# Reply - Using t effect chains based on equivalence classes
+## Reply - "Guard tests for correctness"
-# Reply - "Guard tests for correctness"
+Score one for static typing!
-# Reply - "Graphs are nice for intuition but they don't scale well."
+Here perhaps there is another win, but not really SoN related as least not
+directly. Every "guardable" aspect in Java - except range checks - falls
+neatly into a type *lattice*. This includes all kinds of subtype checks
+(including instanceof, checkcast, user-written, arraystore, etc), null checks,
+and constant bounds. A type *lattice* allows Sparse Conditional Constant
+Propagation on all these types, which makes for a linear time, super cheap
+analysis which wipes out that 90% of checks.
-# Reply - "Use a worklist!"
+
+## Reply - "Graphs are nice for intuition but they don't scale well."
+
+Could be that graph viz tech has improved enough - although I tried hard with
+GraphViz on Simple, and other than some pretty demos its not cut out for
+serious debugging work. Meanwhile, ASCII dumps in C2 run into the 100
+thousands of lines, are instantly quick, land in my editor buffer where they
+are ready to be searched, grep'ed, folded, stapled and mutalilated. Of course
+they appear as a full CFG, with loop structure, typed, backlinks to source code,
+etc. So maybe the answer is: "if you work on your debugging tools long enough,
+they work for you".
+
+
+## Reply - "Use a worklist!"
I think we agree, at least in theory, here.
-# Reply - Late arriving control flow from expanding small "diamonds"
-# Reply - "Data? Perf discussions with facts? Apples to apples comparisons? No?"
+## Reply - "Data? Perf discussions with facts? Apples to apples comparisons? No?"
+
+Yeah, apples & oranges. The obvious observation to make about any comparison
+is that many benchmarkers have a desired outcome in mind... and will pick their
+comparison according. Turboshaft was compared with the V8 variant SoN.
+I'll betcha when doing comparable work C2 is *very* competitive with
+Turboshaft, *especially* on a cache miss basis. This was *the number one
+timing thing* I tracked in during C2's development. "Back in the day" nearly
+all small to medium methods (plus inlines) fit in those processors *L1* cache.
+
+
+## Reply - Late arriving control flow from expanding small "diamonds"
+## Reply - Hard to introduce new control flow
+
+Full optimization of "diamond like things", without being a diamond and
+remaining "pure" (no CFG at least), is done generally via the type lattice on
+higher level common ops. They get lowered after there's no hope of other
+optimization (provable no-progress by the lattice) - which is generally during
+instruction scheduling for C2.
+
+If that's not enough and if you want to introduce control flow - its the same
+problem in a CFG as in SoN (which *has* a CFG embedded). So whatever solution
+you have for your CFG approach... SoN can use directly.
+
+
+## Reply - It's easy in Simple
+
+Yeah, yeah, more opcodes and complexier semantics. C2 also has a zillion
+opcodes, and deopts and re-opts, handles large graphs, multi IR levels, and
+sings and dances.... and Simple has all the right compiler pieces to slot into
+a runtime as complex as a JVM. I don't buy the "its easy because its Simple",
+its easy because... its *simple*. Simplicity is a virtue in its own right, and
+many hard looking things become, well, *simple* when you start from the right
+base.
+
+
+## Reply - Using multiple effect chains based on equivalence classes
+
+Well, this is the biggest failure I see in the V8 SoN effort and deserves the
+most inspection... and has the most room for valid design tradeoffs. So I'm
+saying a little more here, to match your larger writings, but also because
+there's just more stuff to chew on.
+
+
+"It requires typing the whole graph throughout the whole pipeline" - yes. This
+is basically free in Simple/SoN, or at least costs some modest count of cpu
+cycles (and generally no cache misses). Then it sits as a pointer in an IR
+node. There's no bulk copy/bulk read going on, so... it costs nothing to sit
+there. But then, when you need some type info - lo! There it is! C2
+maintains type info end-to-end, because its free to do so and because its
+useful.
+
+"Equivalence classes are much weaker for arrays" - sure. And basically make no
+differece to 99% of all Java programs. 1-d arrays vectorize just fine; stencil
+calculations also fine. Register blocking and tiling? First Java needs to
+change how multi-D arrays are laid out then we can worry about loop carried
+aliasing. Basically, Java was never intended to replace Fortran, although in
+the 1-D space it can hold its own.
+
+"Every call to user-defined functions, as well as most builtin calls have
+arbitrary side effects" - sure same as Java. Doesn't really matter in the
+grand scheme of things. If you can inline, the problem goes away. If not,
+well *There's Yer Problem*. The lack of call aliasing info means you didn't
+optimize some things around the call - all dwarfed by the call overhead itself.
+
+"Memory optimizations also cannot typically float past deoptimization state" -
+plenty of those in Java too. C2 also does not sink stores *below* a deopt
+state. At least for Java, we can float loads *up* past a deopt state (and do
+so plenty).
+
+Also, call inline points *completely* disappear in C2. There is no
+optimization barrier after inlining, even for memory effects. This is
+different from *deoptimization* in this regard.
+
+# "Because of polymorphism.... but rather union types".
+
+Here I honestly don't know. I can fit some kinds of union types in a type
+*lattice*, and then we're back to "basically free types". On the other hand,
+the usefulness of the equivalence class model is based on "since you *cannot*
+alias, the other aliases are not even representable".
+
+This gets into a weak argument from me about how after specialization guards,
+we're back to equivalence classes... but I honestly dunno. Not tried it in JS
+at least (C2 certainly does this, but its not so common as JS).
+Very curious though...
+
+Curious enough for Google to fund a reprise with me lending a hand?
+
+:-)
-# Reply - Hard to introduce new control flow
+Cliff
-# Reply - It's easy in Simple
diff --git a/ASimpleReply/ASimpleReply3.md b/ASimpleReply/ASimpleReply3.md
new file mode 100644
index 00000000..395f4b77
--- /dev/null
+++ b/ASimpleReply/ASimpleReply3.md
@@ -0,0 +1,127 @@
+# A Simple Reply (3)
+
+The whole reply chain:
+
+https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReplyChain.md
+
+
+## Unzipping
+
+Starting from Dadal's code, which has inlined some specialization checks:
+
+```
+foo (o) {
+ let x;
+ if (o.map == Map1) {
+ x = RawLoad(o, offset=8);
+ } else if (o.map == Map2) {
+ x = RawLoad(o, offset=16);
+ } else if (o.map == Map3) {
+ x = RawLoad(o, offset=12);
+ } else throw deopt; // Cliff added
+ .... // Cliff adds: call this code "S1"
+ let y;
+ if (o.map == Map1) {
+ y = RawLoad(o, offset=0);
+ } else if (o.map == Map2) {
+ y = RawLoad(o, offset=20);
+ } else if (o.map == Map3) {
+ y = RawLoad(o, offset=4);
+ } else throw depopt; // Cliff added
+}
+```
+
+I added the "throw deopt", which I assume Dadal implies when `x` and `y` are
+none of `Map1, Map2, Map3`.
+
+Dadal then adds:
+
+> "Here, it's not like any of the type-checks when loading `y` could be
+> removed. Well, in some cases, we do things like double-diamond elimination to
+> remove some checks, but often it's not possible."
+
+C2 sees this kind of pattern fairly often as well; generally replacing `o.map
+== Map1` with `o instanceof Map1`; not that it matters. C2 uses a "unzipping"
+xform for repeated checks; I've talked about it publically numerous times.
+Here C2 will look at the `S1` code above, and based on a heuristic (mostly
+size) it will clone `S1` to remove the excess checks:
+
+```
+foo (o) {
+ let x,y;
+ if (o.map == Map1) {
+ x = RawLoad(o, offset=8);
+ S1;
+ y = RawLoad(o, offset=0);
+
+ } else if (o.map == Map2) {
+ x = RawLoad(o, offset=16);
+ S1;
+ y = RawLoad(o, offset=20);
+
+ } else if (o.map == Map3) {
+ x = RawLoad(o, offset=12);
+ S1;
+ y = RawLoad(o, offset=4);
+
+ } else throw deopt; // Cliff added
+}
+```
+
+Now you get 3 chunks of specialized code where in each case you know the type
+of `o.map` throughout. This process can be repeated at all kinds of levels,
+and assuming the strong knowledge of `o.map` allows more optimizations, can pay
+for itself despite cloning some amount of `S1`. Its certainly common for C2 to
+clone long runs of repeated checks such that in the end there's a fast-path
+where all checks pass "the good way", and bail-out points all long that go to a
+correct but slow path.
+
+
+## Common Case vs Deopt
+
+```
+foo (o) {
+ let x = o.x;
+ bar(); // Might change the "shape" of `o`
+ let y = o.y;
+}
+```
+
+Dadal:
+
+> "If `bar` isn't inlined, then we have to assume that `bar` could change
+> the shape of `o`, and thus a type-check is needed before loading `y`."
+
+In a similar situation C2 will do the same. Of course, for sufficiently hot
+`foo` and modest `bar`, inlining is the answer. This makes me wonder if C2's
+inlininhg budget is substantially higher than V8's.
+
+Dadal:
+> "Oh, and `bar` could be changing the shape of `o` only some times"
+
+In any of these "sometimes Bad Things Happens" situations, C2's go-to answer is
+to register a dependency and deopt if the Bad Thing happens. The set of Bad
+Things that happen between JS and the JVM are different, but it seems to me the
+general theory still holds. Similar (not exact) situation:
+
+```java
+foo( Object o ) {
+ var x = ((Cat)o).x; // Dependency that Cat has no subclasses
+ x.bar(); // Always a `Cat` so call Cat.bar
+}
+```
+
+If later `Cat` is extended with `Siamese` which implements an alternative
+`bar`, this code is deopt'd. The existing calls to `foo` can finish - they are
+correct because they got called with a `Cat` and cannot change that - but no
+new calls are allowed; they will use an alternative more conservative execution
+until a fresh JITd code for `foo` can be made.
+
+
+## Summary
+
+Yeah, the problems between JS and the JVM are different... but not hugely so.
+And the problems shown here are *utterly* unrelated to the Sea-of-Nodes.
+I'd do this stuff in a normal CFG framework just the same.
+
+Cliff
diff --git a/ASimpleReply/ASimpleReplyChain.md b/ASimpleReply/ASimpleReplyChain.md
index 2d53a8ee..eb0bc80e 100644
--- a/ASimpleReply/ASimpleReplyChain.md
+++ b/ASimpleReply/ASimpleReplyChain.md
@@ -17,10 +17,22 @@ https://gist.github.com/DadaIsCrazy/8ce07d409f8f74bad7b36e74b77a3129
### Cliff's Reply(2):
-(first, this reply-chain to gather all the parts)
-
https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReply2.md
+### Dadal's Reply(2):
+
+https://studio.youtube.com/channel/UCbutMk6cKrH8q4sJyQHGPVQ/comments/inbox?filter=%5B%5D
+https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/DadalReply2.md
+
+
+### Cliff's Reply(3)
+
+https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReply3.md
+
+### Dadal's Reply(3)
+
+https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/DadalReply3.md
+
[room for more conversations]
diff --git a/ASimpleReply/DadalReply2.md b/ASimpleReply/DadalReply2.md
new file mode 100644
index 00000000..bb30e155
--- /dev/null
+++ b/ASimpleReply/DadalReply2.md
@@ -0,0 +1,58 @@
+
+This is Dadal's reply from U-tube, recreated here (permission pending) so its
+both easier to find and not lost if U-tube changes their mind.
+
+The original link I copied from:
+
+https://studio.youtube.com/channel/UCbutMk6cKrH8q4sJyQHGPVQ/comments/inbox?filter=%5B%5D
+
+The whole replace chain:
+
+https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReplyChain.md
+
+
+
+Type checks: to give an example:
+
+```
+foo(o) {
+ let x = o.x;
+ ....
+ let y = o.y;
+}
+```
+
+Assuming polymorphic feedback (which is very frequent in JS), this will become something like (using JS-like syntax, but this happening inside the compiler of course)
+```
+foo (o) {
+ let x;
+ if (o.map == Map1) {
+ x = RawLoad(o, offset=8);
+ } else if (o.map == Map2) {
+ x = RawLoad(o, offset=16);
+ } else if (o.map == Map3) {
+ x = RawLoad(o, offset=12);
+ }
+ ....
+ let y;
+ if (o.map == Map1) {
+ y = RawLoad(o, offset=0);
+ } else if (o.map == Map2) {
+ y = RawLoad(o, offset=20);
+ } else if (o.map == Map3) {
+ y = RawLoad(o, offset=4);
+ }
+}
+```
+
+Here, it's not like any of the type-checks when loading `y` could be removed. Well, in some cases, we do things like double-diamond elimination to remove some checks, but often it's not possible.
+
+For monomorphic cases, subsequent checks will be removed when possible, but objects can change shapes in JavaScript. This means that if you have something like
+```
+foo (o) {
+ let x = o.x;
+ bar();
+ let y = o.y;
+}
+```
+If `bar` isn't inlined, then we have to assume that `bar` could change the shape of `o`, and thus a type-check is needed before loading `y`. Oh, and `bar` could be changing the shape of `o` only some times, so `o.x` could have monomorphic feedback while `o.y` could have polymorphic feedback. Or the other way around :)
diff --git a/ASimpleReply/DadalReply3.md b/ASimpleReply/DadalReply3.md
new file mode 100644
index 00000000..e5e89aa4
--- /dev/null
+++ b/ASimpleReply/DadalReply3.md
@@ -0,0 +1,25 @@
+
+This is Dadal's reply from U-tube, recreated here (permission pending) so its
+both easier to find and not lost if U-tube changes their mind.
+
+The original link I copied from:
+
+https://www.youtube.com/watch?v=iNWAgSAEPAg&lc=Ugwn_YpruALAauwRLqt4AaABAg.AGbLSQ5tlmFAGcQm0uZyZO
+
+The whole replace chain:
+
+https://github.com/SeaOfNodes/Simple/blob/main/ASimpleReply/ASimpleReplyChain.md
+
+
+
+On medium sized functions, everything tends to be inlined in V8, but on larger
+functions it tends to be more complicated. We've tried before to increase the
+inclining budget, but this means reducing compile time, which means delaying
+the time until the optimized code starts being executed, which in turns
+regresses whatever benchmarks we're using to measure how fast V8 is. One
+difference with Java here might be 1- how slow unoptimized JS code is (after
+all, V8 bytecodes are ultra-generic, assuming any types for inputs, and having
+to check a ton of various cases) and 2- how fast-running JS applications are
+compared to Java (not in the sense that "JS is faster" but in the sense that
+"JS applications probably stop running at least one order of magnitude earlier
+than Java ones")
diff --git a/chapter21/README.md b/chapter21/README.md
index 4c88e351..7ff4f9b0 100644
--- a/chapter21/README.md
+++ b/chapter21/README.md
@@ -4,7 +4,7 @@
The output of Encodings - conversion of machine Nodes to instruction bits - is
difficult to test in bulk without an execution strategy. We won't have an
-execution stratgy until all three of Instruction Selection, Register Allocation
+execution strategy until all three of Instruction Selection, Register Allocation
and Encodings are done. Hence in and around the completion of Encoding we
expect to find lots of bugs in Instruction Selection and Register Allocation.
We certainly hand-inspect the output and fix obvious problems, but still lots
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
index a36cdd51..782a3cae 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java
@@ -16,20 +16,20 @@ public class arm extends Machine {
@Override public String name() { return "arm"; }
// GPR(S)
- static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7;
- static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15;
- static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23;
- static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31;
+ public static final int X0 = 0, X1 = 1, X2 = 2, X3 = 3, X4 = 4, X5 = 5, X6 = 6, X7 = 7;
+ public static final int X8 = 8, X9 = 9, X10 = 10, X11 = 11, X12 = 12, X13 = 13, X14 = 14, X15 = 15;
+ public static final int X16 = 16, X17 = 17, X18 = 18, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X23 = 23;
+ public static final int X24 = 24, X25 = 25, X26 = 26, X27 = 27, X28 = 28, X29 = 29, X30 = 30, RSP = 31;
// Floating point registers
- static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39;
- static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47;
- static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55;
- static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63;
+ public static final int D0 = 32, D1 = 33, D2 = 34, D3 = 35, D4 = 36, D5 = 37, D6 = 38, D7 = 39;
+ public static final int D8 = 40, D9 = 41, D10 = 42, D11 = 43, D12 = 44, D13 = 45, D14 = 46, D15 = 47;
+ public static final int D16 = 48, D17 = 49, D18 = 50, D19 = 51, D20 = 52, D21 = 53, D22 = 54, D23 = 55;
+ public static final int D24 = 56, D25 = 57, D26 = 58, D27 = 59, D28 = 60, D29 = 61, D30 = 62, D31 = 63;
static final int FLAGS = 64;
static final int MAX_REG = 65;
- static final int D_OFFSET = 31;
+ public static final int D_OFFSET = 31;
static final String[] REGS = new String[] {
"X0", "X1", "X2", "X3", "X4", "X5", "X6", "X7",
diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
index 6a8f34b1..103fff0d 100644
--- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
+++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java
@@ -22,14 +22,14 @@ public class riscv extends Machine {
public static int S8 = 24, S9 = 25, S10= 26, S11= 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31;
// FP registers
- static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39;
- static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47;
- static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55;
- static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, F8 = 60, F9 = 61, F10 = 62, F11 = 63;
+ public static int F0 = 32, F1 = 33, F2 = 34, F3 = 35, F4 = 36, F5 = 37, F6 = 38, F7 = 39;
+ public static int FS0 = 40, FS1 = 41, FA0 = 42, FA1 = 43, FA2 = 44, FA3 = 45, FA4 = 46, FA5 = 47;
+ public static int FA6 = 48, FA7 = 49, FS2 = 50, FS3 = 51, FS4 = 52, FS5 = 53, FS6 = 54, FS7 = 55;
+ public static int FS8 = 56, FS9 = 57, FS10 = 58, FS11 = 59, FT8 = 60, FT9 = 61, FT10 = 62, FT11 = 63;
static final int MAX_REG = 64;
- static final int F_OFFSET = 32;
+ public static final int F_OFFSET = 32;
static final String[] REGS = new String[] {
"zero","rpc" , "sp" , "s12" , "s13" , "t0" , "t1" , "t2" ,
@@ -281,7 +281,7 @@ public class riscv extends Machine {
(1L<<T0 ) | (1L<<T1 ) | (1L<<T2 ) | (1L<<T3 ) | (1L<<T4 ) | (1L<<T5 ) | (1L<<T6 ) |
(1L<<FA0) | (1L<<FA1) | (1L<<FA2) | (1L<<FA3) | (1L<<FA4) | (1L<<FA5) | (1L<<FA6) | (1L<<FA7) |
(1L<<F0 ) | (1L<<F1 ) | (1L<<F2 ) | (1L<<F3 ) | (1L<<F4 ) | (1L<<F5 ) | (1L<<F6 ) | (1L<<F7 ) |
- (1L<<F8 ) | (1L<<F9 ) | (1L<<F10) | (1L<<F11) ;
+ (1L<<FT8) | (1L<<FT9) | (1L<<FT10)| (1L<<FT11);
@Override public long callerSave() { return CALLER_SAVE; }
@Override public long neverSave() { return (1L<<RSP) | (1L<<ZERO); }
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
index f3b2c26d..faf21b4f 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter21Test.java
@@ -102,6 +102,12 @@ public class Chapter21Test {
9 3.000000
""";
TestC.run("newtonFloat",result);
+ EvalRisc5 R5 = TestRisc5.build("newtonFloat", 0);
+ R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 4.0;
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+ // Return register A0 holds fib(8)==55
+ assertEquals(2.0,R5.fregs[riscv.FA0 - riscv.F_OFFSET], 0.00001);
}
@Test public void testSieve() throws IOException {
@@ -173,9 +179,36 @@ public class Chapter21Test {
assertEquals(17+1,R5.ld8(p1));
assertEquals(60+0,R5.ld8(p2));
}
-
@Test public void testArgCount() throws IOException {
String arg_count = "191.000000\n";
TestC.run("arg_count", arg_count);
+
+ EvalRisc5 R5 = TestRisc5.build("no_stack_arg_count", 0);
+
+ // Todo: handle stack(imaginary stack in emulator)
+ // pass in float arguments
+ R5.fregs[riscv.FA0 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA1 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA2 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA3 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA4 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA5 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA6 - riscv.F_OFFSET] = 1.1;
+ R5.fregs[riscv.FA7 - riscv.F_OFFSET] = 1.1;
+
+ R5.regs[riscv.A1] = 2;
+ R5.regs[riscv.A2] = 2;
+ R5.regs[riscv.A3] = 2;
+ R5.regs[riscv.A4] = 2;
+ R5.regs[riscv.A5] = 2;
+ R5.regs[riscv.A6] = 2;
+ R5.regs[riscv.A7] = 2;
+
+ int trap = R5.step(100);
+ assertEquals(0,trap);
+
+ double result = R5.fregs[riscv.FA0 - riscv.F_OFFSET];
+ // missing a float
+ assertEquals(22.8, result, 0.00001);
}
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
new file mode 100644
index 00000000..55a16f84
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalArm64.java
@@ -0,0 +1,67 @@
+package com.seaofnodes.simple;
+
+public class EvalArm64 {
+ // Memory image, always 0-based
+ public final byte[] _buf;
+
+ // GPRs
+ final long[] regs;
+ // FRs
+ final double[] fregs;
+
+ int _pc;
+
+ // Start of free memory for allocation
+ int _heap;
+
+ // Cycle counters
+ int _cycle;
+
+ EvalArm64(byte[] buf, int stackSize) {
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
+ // Stack grows down, heap grows up
+ regs[2/*RSP*/] = _heap = stackSize;
+ _pc = 0;
+ }
+
+ public int step(int maxops) {
+ int trap = 0;
+ long rval = 0;
+ double frval = 0;
+ int pc = _pc;
+ int cycle = _cycle;
+ boolean is_f = false;
+ outer:
+ for(int icount = 0; icount < maxops; icount++) {
+ int ir = 0;
+ rval = 0;
+ cycle++;
+
+ if( pc >= _buf.length ) {
+ trap = 1 + 1; // Handle access violation on instruction read.
+ break;
+ }
+
+ if( (pc & 3)!=0 ) {
+ trap = 1; // Handle PC-misaligned access
+ break;
+ }
+
+ // Load instruction from image buffer at PC
+ //ir = ld4s( pc );
+
+ //int rdid = ir & 0x1f;
+ //switch(ir & )
+ }
+ if(trap != 0) {
+ return trap;
+ }
+ _cycle = _cycle;
+ _pc = pc;
+ return 0;
+ }
+
+
+}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java b/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
index eed596e8..50e52095 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/EvalRisc5.java
@@ -19,7 +19,8 @@ import java.util.Arrays;
// GPRs
final long[] regs;
-
+ // FRs
+ final double[] fregs;
// PC
int _pc;
@@ -30,8 +31,9 @@ import java.util.Arrays;
int _cycle;
EvalRisc5( byte[] buf, int stackSize ) {
- _buf = buf;
- regs = new long[32];
+ _buf = buf;
+ regs = new long[32];
+ fregs = new double[32];
// Stack grows down, heap grows up
regs[2/*RSP*/] = _heap = stackSize;
_pc = 0;
@@ -59,9 +61,10 @@ import java.util.Arrays;
public int step( int maxops ) {
int trap = 0;
long rval = 0;
+ double frval = 0;
int pc = _pc;
int cycle = _cycle;
-
+ boolean is_f = false;
outer:
for( int icount = 0; icount < maxops; icount++ ) {
int ir = 0;
@@ -82,6 +85,17 @@ import java.util.Arrays;
int rdid = (ir >> 7) & 0x1f;
switch( ir & 0x7f ) {
+ // floats
+ case 0x7: {
+ is_f = true;
+ // does not handle rsp case
+
+ long rs1 = (ir >> 15) & 0x1f;
+ // Todo: look for stack case
+ if (rs1 == 2) break;
+ frval = ir & 0x7FF000;
+ break;
+ }
case 0x37: // LUI (0b0110111)
rval = ( ir & 0xfffff000 );
break;
@@ -263,6 +277,68 @@ import java.util.Arrays;
}
break;
}
+
+ case 0x53: {
+ // differentiate between fcvt.d.w and fadd.d
+ is_f = true;
+ int rs1;
+ int rs1_int;
+ int rs2;
+ int func5 = (ir >> 27) & 0x1F;;
+ int func2 = (ir >> 25) & 0x3;
+// if(func5 == 0 && func2 == 1) {
+// }
+ if(func5 == 0) {
+ // fadd.d
+ rs1 = (ir >> 15) & 0x1F;
+ rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ frval = lhs + rhs;
+ break;
+ } else if(func5 == 26) {
+ //fcvt.d.w
+ rs1_int = (ir >> 15) & 0x1F;
+ long value = regs[rs1_int];
+ frval = (double)value;
+ break;
+ } else if (func5 == 5) {
+ // fmin.d
+ rs1 = (ir >> 15) & 0x1F;
+ rs2 = (ir >> 20) & 0x1F;
+ // just pick one as they are the same
+ frval = fregs[rs1];
+ break;
+ } else if(func5 == 3) {
+ // fdiv.d
+ rs1 = (ir >> 15) & 0x1F;
+ rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ frval = lhs / rhs;
+ break;
+ } else if(func5 == 2) {
+ // fmul.d
+ rs1 = (ir >> 15) & 0x1F;
+ rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ frval = lhs * rhs;
+ break;
+
+ } else if(func5 == 1) {
+ // fsub.d
+ rs1 = (ir >> 15) & 0x1F;
+ rs2 = (ir >> 20) & 0x1F;
+ double lhs = fregs[rs1];
+ double rhs = fregs[rs2];
+ frval = lhs - rhs;
+ break;
+ } else {
+ trap = (2+1); // Fault: Invalid opcode.
+ throw Utils.TODO();
+ }
+ }
default: trap = (2+1); // Fault: Invalid opcode.
}
@@ -272,8 +348,10 @@ import java.util.Arrays;
break;
}
- if( rdid!=0 )
- regs[rdid] = rval; // Write back register.
+ if( rdid!=0 || is_f) {
+ if(is_f) {fregs[rdid] = frval; is_f = false;} // Write back register.
+ else regs[rdid] = rval; // Write back register.
+ }
pc += 4;
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java b/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java
new file mode 100644
index 00000000..96aa5b74
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/TestArm64.java
@@ -0,0 +1,34 @@
+package com.seaofnodes.simple;
+
+
+import com.seaofnodes.simple.codegen.CodeGen;
+import com.seaofnodes.simple.node.cpus.arm.arm;
+import junit.framework.Assert;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.io.IOException;
+
+// Runs 32-bit ARM code in an emulator
+
+public class TestArm64 {
+ public static EvalArm64 build(String file, int arg) throws IOException {
+ return build("src/test/java/com/seaofnodes/simple/progs",file, arg);
+ }
+
+ // Compile and run a simple program
+ public static EvalArm64 build(String dir, String file, int arg) throws IOException {
+ // Compile and export Simple
+ String src = Files.readString(Path.of(dir+"/"+file+".smp"));
+ CodeGen code = new CodeGen(src).driver(CodeGen.Phase.RegAlloc,"arm", "SystemV").encode(true);
+
+ // Image
+ byte[] image = new byte[1<<20]; // A megabyte (1024*1024 bytes)
+ EvalArm64 ARM = new EvalArm64(image, 1<<16);
+ // Code at offset 0
+ System.arraycopy(code._encoding.bits(), 0, image, 0, code._encoding.bits().length);
+ // Initial incoming int arg
+ ARM.regs[arm.D0] = arg;
+ // malloc memory starts at 64K and goes up
+ return ARM;
+ }
+}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/TestC.java b/chapter21/src/test/java/com/seaofnodes/simple/TestC.java
index 4294606c..8398c673 100644
--- a/chapter21/src/test/java/com/seaofnodes/simple/TestC.java
+++ b/chapter21/src/test/java/com/seaofnodes/simple/TestC.java
@@ -43,7 +43,6 @@ public abstract class TestC {
String obj = bin+".o";
// Compile simple, emit ELF
CodeGen code = new CodeGen(src).driver( CPU_PORT, simple_conv, obj);
-
// Compile the C program
var params = new String[] {
//if (USE_WSL) "wsl.exe";
@@ -77,6 +76,6 @@ public abstract class TestC {
System.err.println(result);
}
assertEquals( 0, error );
- assertEquals(expected,result);
+ assertEquals(expected,result.replace("\r",""));
}
}
diff --git a/chapter21/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp b/chapter21/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp
new file mode 100644
index 00000000..20a05445
--- /dev/null
+++ b/chapter21/src/test/java/com/seaofnodes/simple/progs/no_stack_arg_count.smp
@@ -0,0 +1,6 @@
+// -*- mode: java; -*-
+val addAll = { int i0, flt f1, int i2, flt f3, int i4, flt f5, int i6, flt f7, int x8, flt f9, int i10, flt f11, int i12, flt f13, int i14, flt f15 ->
+ return
+ i0 + f1+ i2+ f3+ i4+ f5+ i6+ f7+ x8 +f9 +
+ i10+f11+i12+f13+i14+f15;
+};