From aaec6f8136df6f7020bfc05f79ff5273adf73de1 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Wed, 7 Oct 2026 12:57:31 -0700 Subject: flt->int conversion --- chapter14/README.md | 17 ++++++++--- .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 3 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 24 ++++++++++++++-- .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 3 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 3 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 3 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 3 +- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../java/com/seaofnodes/simple/Chapter18Test.java | 5 +--- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../simple/node/cpus/riscv/F8ToIRISC.java | 27 ++++++++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 27 ++++++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 28 ++++++++++++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 1 + .../simple/node/cpus/riscv/F8ToIRISC.java | 28 ++++++++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 30 ++++++++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 21 ++++++++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 1 + .../simple/node/cpus/riscv/F8ToIRISC.java | 19 +++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 22 +++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 4 +++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 21 ++++++++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 1 + .../simple/node/cpus/riscv/F8ToIRISC.java | 19 +++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 22 +++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ chapter23/README.md | 8 +----- .../main/java/com/seaofnodes/simple/Parser.java | 4 +++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 21 ++++++++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 1 + .../simple/node/cpus/riscv/F8ToIRISC.java | 19 +++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 22 +++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 4 +++ .../com/seaofnodes/simple/node/ToIntegerNode.java | 23 +++++++++++++++ .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 21 ++++++++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 1 + .../simple/node/cpus/riscv/F8ToIRISC.java | 19 +++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 22 +++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/evaluator/Evaluator.java | 2 ++ .../main/java/com/seaofnodes/simple/Parser.java | 2 ++ .../com/seaofnodes/simple/node/ConvertNode.java | 4 +++ .../main/java/com/seaofnodes/simple/node/Node.java | 3 +- .../com/seaofnodes/simple/node/ToIntegerNode.java | 24 ++++++++++++++++ .../seaofnodes/simple/node/cpus/arm/F8ToIARM.java | 21 ++++++++++++++ .../com/seaofnodes/simple/node/cpus/arm/arm.java | 1 + .../simple/node/cpus/riscv/F8ToIRISC.java | 19 +++++++++++++ .../seaofnodes/simple/node/cpus/riscv/riscv.java | 1 + .../simple/node/cpus/x86_64_v2/F8ToIX86.java | 22 +++++++++++++++ .../simple/node/cpus/x86_64_v2/x86_64_v2.java | 1 + .../java/com/seaofnodes/simple/Chapter12Test.java | 4 +-- .../com/seaofnodes/simple/Chapter14FloatTest.java | 19 +++++++++++++ .../java/com/seaofnodes/simple/Chapter17bTest.java | 4 +-- .../java/com/seaofnodes/simple/Chapter21Test.java | 21 ++++++++++++++ .../src/test/java/com/seaofnodes/simple/Eval2.java | 2 ++ .../com/seaofnodes/simple/progs/floatToInteger.c | 25 ++++++++++++++++ docs/chapter-backports.md | 33 ++++++++++++++-------- isa/src/main/java/com/seaofnodes/isa/Arm64.java | 6 ++++ isa/src/main/java/com/seaofnodes/isa/RiscV.java | 10 +++++++ isa/src/main/java/com/seaofnodes/isa/X86.java | 16 +++++++++++ .../java/com/seaofnodes/isa/eval/EvalArm64.java | 9 ++++-- .../java/com/seaofnodes/isa/eval/EvalRisc5.java | 20 ++++++++++++- isa/src/test/java/com/seaofnodes/isa/RiscTest.java | 22 +++++++++++++++ isa/src/test/java/com/seaofnodes/isa/X86Test.java | 4 +++ 135 files changed, 1299 insertions(+), 77 deletions(-) create mode 100644 chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter15/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter16/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter17a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter17b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter18/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter19/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter21/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java create mode 100644 chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter23/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java create mode 100644 chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter24/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java create mode 100644 chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java create mode 100644 chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java create mode 100644 chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java create mode 100644 chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java create mode 100644 chapter25/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c diff --git a/chapter14/README.md b/chapter14/README.md index 81862508..60e82403 100644 --- a/chapter14/README.md +++ b/chapter14/README.md @@ -34,10 +34,16 @@ f = f+x; // Auto-widen x to 2.0, result is 2.5 f = f+true; // Since bools are just synonyms for integers 0,1 they auto-widen also ``` -There is no corresponding way right now to round a `flt` back to an `int`: +Assigning a float to an integer truncates toward zero: ```java -int x = 3.14; // error +int rounded = 17.3/2.5; // 6 +rounded = -17.3/2.5; // -6 ``` +This applies to variable initialization, reassignment, and stores to integer +fields or array elements. Conversion first produces a signed 64-bit integer: +NaN becomes zero, and values outside the range (including infinities) clamp to +the nearest limit. Narrow integer destinations then apply the same bit +truncation and sign/zero extension as integer-to-integer assignments. Here is Newton's method for computing a square root: ```java @@ -60,14 +66,17 @@ Floating point operations have their own Nodes: | AddF, SubF, MulF, DivF | Basic binary FP ops | | MinusF | Unary negate | | EQF, LTF, LEF | FP compare operators | +| ToFloat, ToInteger | Numeric conversions | +| RoundF32 | Round to f32 precision | The parser uses [`Node.widen()`](src/main/java/com/seaofnodes/simple/node/Node.java) to choose the floating-point operation and insert `ToFloatNode` conversions. The float lattice has constants between `TOP` and `BOT`, like the integer lattice before ranges. [`TypeFloat`](src/main/java/com/seaofnodes/simple/type/TypeFloat.java) also records whether a value fits `f32`; assigning to `f32` rounds it to that -precision. Conversion from float back to integer remains a separate, pending -language feature. +precision. `ToIntegerNode` performs the inverse numeric conversion on assignment +to an integer destination, folding constants with the same rules as runtime +conversion. ## Narrow or sub-word types diff --git a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java index 4ab4d9ac..0602d290 100644 --- a/chapter14/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter14/src/main/java/com/seaofnodes/simple/Parser.java @@ -733,6 +733,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter14/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java index e84da36a..9e7240d8 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -130,8 +130,7 @@ N n = new N; n.i = 3.14; return n.i; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString()); } @Test diff --git a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index f2b51da2..5438fb2b 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var stop = new Parser(test[0]).parse().iterate(); + assertEquals(test[0],test[1],Evaluator.evaluate(stop,17).toString()); + assertEquals(test[0],test[2],Evaluator.evaluate(stop,-17).toString()); + } + } + + @Test public void testSubZeroFloat() { var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); // Compare strings so +0.0 and -0.0 remain distinct. @@ -81,7 +100,7 @@ return x+1==x; assertEquals(0L, Evaluator.evaluate(stop, 1)); } @Test - public void testReferenceFieldRejectsFloat() { + public void testReferenceFieldConvertsFloat() { Parser parser = new Parser( """ struct N { N next; int i; } @@ -89,7 +108,6 @@ N n = new N; n.i = 3.14; return n.i; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString()); } } diff --git a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index afe4cfe3..afa35372 100644 --- a/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter14/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -157,6 +157,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java index a0c15ae3..81291326 100644 --- a/chapter15/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter15/src/main/java/com/seaofnodes/simple/Parser.java @@ -834,6 +834,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter15/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter15/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter15/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java index e885deb3..813dd757 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -126,8 +126,7 @@ N n = new N; n.i = 3.14; return n.i; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString()); } @Test diff --git a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index f4323fe0..fd71ad5f 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var stop = new Parser(test[0]).parse().iterate(); + assertEquals(test[0],test[1],Evaluator.evaluate(stop,17).toString()); + assertEquals(test[0],test[2],Evaluator.evaluate(stop,-17).toString()); + } + } + + @Test public void testSubZeroFloat() { var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 9eed23de..485f73c5 100644 --- a/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter15/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -305,6 +305,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java index deb359ce..41a3fcaa 100644 --- a/chapter16/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter16/src/main/java/com/seaofnodes/simple/Parser.java @@ -918,6 +918,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter16/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter16/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter16/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 22976058..8d1d93c6 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -125,8 +125,7 @@ N n = new N; n.i = 3.14; return n.i; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Cannot store 3.14 into field int i",e.getMessage()); } + assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString()); } @Test diff --git a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index f4323fe0..fd71ad5f 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var stop = new Parser(test[0]).parse().iterate(); + assertEquals(test[0],test[1],Evaluator.evaluate(stop,17).toString()); + assertEquals(test[0],test[2],Evaluator.evaluate(stop,-17).toString()); + } + } + + @Test public void testSubZeroFloat() { var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3eb406eb..8b530307 100644 --- a/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter16/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -297,6 +297,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter17a/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17a/src/main/java/com/seaofnodes/simple/Parser.java index f5c371cc..e2df494e 100644 --- a/chapter17a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17a/src/main/java/com/seaofnodes/simple/Parser.java @@ -1009,6 +1009,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter17a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter17a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter17a/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 7e3b6843..a12e4218 100644 --- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -125,8 +125,7 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString()); } @Test diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index bff36f3a..ce674b44 100644 --- a/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter17a/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var stop = new Parser(test[0]).parse().iterate(); + assertEquals(test[0],test[1],Evaluator.evaluate(stop,17).toString()); + assertEquals(test[0],test[2],Evaluator.evaluate(stop,-17).toString()); + } + } + + @Test public void testSubZeroFloat() { var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter17a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter17a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 30af9895..0721402f 100644 --- a/chapter17a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter17a/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -297,6 +297,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter17b/src/main/java/com/seaofnodes/simple/Parser.java b/chapter17b/src/main/java/com/seaofnodes/simple/Parser.java index 2d794aa5..c02f0305 100644 --- a/chapter17b/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter17b/src/main/java/com/seaofnodes/simple/Parser.java @@ -1156,6 +1156,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter17b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter17b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter17b/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 7e3b6843..a12e4218 100644 --- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -125,8 +125,7 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { parser.parse().iterate(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + assertEquals("3",com.seaofnodes.simple.evaluator.Evaluator.evaluate(parser.parse().iterate(),0).toString()); } @Test diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index bff36f3a..ce674b44 100644 --- a/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter17b/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var stop = new Parser(test[0]).parse().iterate(); + assertEquals(test[0],test[1],Evaluator.evaluate(stop,17).toString()); + assertEquals(test[0],test[2],Evaluator.evaluate(stop,-17).toString()); + } + } + + @Test public void testSubZeroFloat() { var stop = new Parser("flt x = arg; return 0-x;").parse().iterate(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter17b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter17b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 30af9895..0721402f 100644 --- a/chapter17b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter17b/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -297,6 +297,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java index 3e80aa60..82ad8d04 100644 --- a/chapter18/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter18/src/main/java/com/seaofnodes/simple/Parser.java @@ -1350,6 +1350,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter18/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter18/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter18/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java index fc5b37d9..130ac8ea 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -123,8 +123,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 7b065411..7eed49fb 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index e64d5b27..93b64747 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -124,8 +124,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java index 0758fbf5..22eb91b9 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/Chapter18Test.java @@ -14,11 +14,8 @@ public class Chapter18Test { CodeGen zero = new CodeGen("var a = new "+type+"[arg]; return a[arg-1];") .parse().opto().typeCheck(); assertEquals("0.0",Eval2.eval(zero,3)); - // Eval2 does not execute RoundF32 yet; check nonconstant narrowing here. new CodeGen(type+" x = arg; var a = new "+type+"[arg]; a[0] = x; return a[0];") .parse().opto().typeCheck(); - } - for( String type : new String[]{"flt","f64"} ) { CodeGen loop = new CodeGen("var a = new "+type+"[arg]; "+ "for(int i=0; i store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 1e2a08a7..7dbc551e 100644 --- a/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter18/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -296,6 +296,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java index dde2d09b..00aa117e 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/Parser.java @@ -1340,6 +1340,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter19/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..261a1dcd --- /dev/null +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.node.cpus.x86_64_v2.x86_64_v2; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.RMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + + @Override public String op() { return "f8toi"; } +} diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 81b31c6a..ad6e1c0f 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -202,6 +202,7 @@ public class riscv extends Machine{ 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..33a77d4c --- /dev/null +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,27 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + + @Override public String op() { return "f8toi"; } +} diff --git a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 523d0e34..e7e0b874 100644 --- a/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter19/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -205,6 +205,7 @@ public class x86_64_v2 extends Machine { case SubFNode subf -> new SubFX86(subf); case SubNode sub -> sub(sub); case ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIX86(cvt); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 199d185d..c2e07b22 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -123,8 +123,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 7b065411..7eed49fb 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -8,6 +8,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 3de0a901..bd957d6a 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -124,8 +124,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java index 4837fcda..46251cef 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/Eval2.java @@ -279,6 +279,8 @@ public abstract class Eval2 { case StoreNode st -> store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3f023bb2..cb98971b 100644 --- a/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter19/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -299,6 +299,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java index 2a228bc8..697e84ce 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/Parser.java @@ -1341,6 +1341,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter20/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..937c437b --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +// VCVT (between floating-point and integer) +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) {super(f8toi);} + @Override public String op() { return "f8toi"; } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return arm.DMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return arm.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 009af342..72254310 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -219,6 +219,7 @@ public class arm extends Machine { case SubFNode subf -> new SubFARM(subf); case SubNode sub -> sub(sub); case ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIARM(cvt); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..f4de5d48 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,28 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask outregmap() { return riscv.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + + @Override public String op() { return "f8toi"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index daf33f29..0542085c 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -221,6 +221,7 @@ public class riscv extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..49d666b7 --- /dev/null +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,30 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.TypeInteger; +import java.io.ByteArrayOutputStream; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + + // Register mask allowed on input i. + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + // Register mask allowed as a result. 0 for no register. + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + // Encoding is appended into the byte array; size is returned + @Override public int encoding(ByteArrayOutputStream bytes) { + throw Utils.TODO(); + } + + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + + @Override public String op() { return "f8toi"; } +} diff --git a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 57771d6a..c66aed9f 100644 --- a/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter20/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -250,6 +250,7 @@ public class x86_64_v2 extends Machine { case SubFNode subf -> new SubFX86(subf); case SubNode sub -> sub(sub); case ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIX86(cvt); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 36a1a0aa..6b7b5d85 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -124,8 +124,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 009beb0b..23d0ef37 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -7,6 +7,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index b799ee82..ff35f904 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -123,8 +123,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java index b55607df..3312ec6b 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/Eval2.java @@ -281,6 +281,8 @@ public abstract class Eval2 { case StoreNode st -> store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3f023bb2..cb98971b 100644 --- a/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter20/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -299,6 +299,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java index 10e2c5c1..6f307785 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/Parser.java @@ -1359,6 +1359,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter21/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..098c1353 --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + +} 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 af0461a6..4006565e 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 @@ -314,6 +314,7 @@ public class arm extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..b2d4efea --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} 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 a9a6d84b..7e840509 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 @@ -239,6 +239,7 @@ public class riscv extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..44bcbe7b --- /dev/null +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 959ea440..1b7cf775 100644 --- a/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter21/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -284,6 +284,7 @@ public class x86_64_v2 extends Machine { 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 -> new ConstantNode(Type.XCONTROL); case XorNode xor -> xor(xor); diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java index f2078d03..ec7b3cb3 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -124,8 +124,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 009beb0b..23d0ef37 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -7,6 +7,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index eeb94d50..e7f5849d 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -123,8 +123,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java index dabd63cc..a855350e 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/Eval2.java @@ -283,6 +283,8 @@ public abstract class Eval2 { case StoreNode st -> store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3f023bb2..cb98971b 100644 --- a/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter21/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -299,6 +299,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java index 6ceccf0a..ea618757 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/Parser.java @@ -665,6 +665,8 @@ public class Parser { // Auto-narrow wide ints to narrow ints. For loads, emit code to force // the loaded value to match the declared sign/zero bits. For stores, // just force the type, acting "as if" the store silently truncates. + if( expr._type instanceof TypeFloat && t instanceof TypeInteger ) + expr = peep(new ToIntegerNode(expr)); Type et = expr._type; if( isLoad ) { expr = zsMask(expr,t); et = expr._type; } else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t; @@ -1438,6 +1440,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter22/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..098c1353 --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,21 @@ +package com.seaofnodes.simple.node.cpus.arm; + +import com.seaofnodes.isa.Arm64; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index 15e2f0fe..da5e6d4b 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -316,6 +316,7 @@ public class arm extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..b2d4efea --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,19 @@ +package com.seaofnodes.simple.node.cpus.riscv; + +import com.seaofnodes.isa.RiscV; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index a9327c50..a1e3b767 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -240,6 +240,7 @@ public class riscv extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..44bcbe7b --- /dev/null +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,22 @@ +package com.seaofnodes.simple.node.cpus.x86_64_v2; + +import com.seaofnodes.isa.X86; +import com.seaofnodes.simple.*; +import com.seaofnodes.simple.codegen.*; +import com.seaofnodes.simple.node.*; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 5c0d4599..7ddd17d4 100644 --- a/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter22/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -287,6 +287,7 @@ public class x86_64_v2 extends Machine { 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 -> new ConstantNode(Type.XCONTROL); case XorNode xor -> xor(xor); diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java index ad0b6088..9b41d77f 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -124,8 +124,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 009beb0b..23d0ef37 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -7,6 +7,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index eeb94d50..e7f5849d 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -123,8 +123,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type int",e.getMessage()); } + code.parse().opto(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java index cbe7bde0..bbf88f1a 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/Eval2.java @@ -281,6 +281,8 @@ public abstract class Eval2 { case StoreNode st -> store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 3f023bb2..cb98971b 100644 --- a/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter22/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -299,6 +299,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter23/README.md b/chapter23/README.md index fd330cdd..59ce0dc5 100644 --- a/chapter23/README.md +++ b/chapter23/README.md @@ -48,12 +48,6 @@ Also in this chapter we revisit our Types and make some major changes: etc; only the implementation details change. -The lazy memory model from Chapter 11 continues through the new type system. -BulkMemPhi/MemPhi discover aliases during optimization. Each New covers only -instance fields; class-wide (`_one`) fields are excluded from its partial memory. -Field offsets stay symbolic until layout, and Loads retain cyclic field types -and deep-final information while querying the contents of a precise alias. - You can also read [this chapter](https://github.com/SeaOfNodes/Simple/tree/linear-chapter23) in a linear Git revision history on the [linear](https://github.com/SeaOfNodes/Simple/tree/linear) branch and [compare](https://github.com/SeaOfNodes/Simple/compare/linear-chapter22...linear-chapter23) it to the previous chapter. @@ -185,7 +179,7 @@ alternatives) but it is a great tutorial data structure. ```java struct List { !List? !next; // Next pointer or null - str !name; // Payload + str !name; // Payload }; ``` diff --git a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java index 4b7cca70..6084eaa5 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/Parser.java @@ -700,6 +700,8 @@ public class Parser { // Auto-narrow wide ints to narrow ints. For loads, emit code to force // the loaded value to match the declared sign/zero bits. For stores, // just force the type, acting "as if" the store silently truncates. + if( expr._type instanceof TypeFloat && t instanceof TypeInteger ) + expr = peep(new ToIntegerNode(expr)); Type et = expr._type; if( isLoad ) { expr = zsMask(expr,t); et = expr._type; } else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t; @@ -1583,6 +1585,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter23/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..31dcfc36 --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,21 @@ +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.util.SB; + +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index e7119608..8bdcfe10 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -316,6 +316,7 @@ public class arm extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..a56ad216 --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,19 @@ +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.util.SB; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 6be0376a..78d21f3b 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -240,6 +240,7 @@ public class riscv extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..a79a1017 --- /dev/null +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,22 @@ +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.util.SB; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 4beef793..e283bc8f 100644 --- a/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter23/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -287,6 +287,7 @@ public class x86_64_v2 extends Machine { 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 -> new ConstantNode(Type.XCONTROL); case XorNode xor -> xor(xor); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 8e9aaed6..041e0d06 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -124,8 +124,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type i64",e.getMessage()); } + code.parse().opto().typeCheck(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 009beb0b..23d0ef37 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -7,6 +7,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 813fc4c2..59bd3b0c 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -123,8 +123,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type i64",e.getMessage()); } + code.parse().opto().typeCheck(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java index feb67ba9..908cc2e0 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/Eval2.java @@ -283,6 +283,8 @@ public abstract class Eval2 { case StoreNode st -> store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 1c116162..20aac367 100644 --- a/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter23/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -298,6 +298,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java index f28d0871..9cf565ac 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/Parser.java @@ -707,6 +707,8 @@ public class Parser { // Auto-narrow wide ints to narrow ints. For loads, emit code to force // the loaded value to match the declared sign/zero bits. For stores, // just force the type, acting "as if" the store silently truncates. + if( expr._type instanceof TypeFloat && t instanceof TypeInteger ) + expr = peep(new ToIntegerNode(expr)); Type et = expr._type; if( isLoad ) { expr = zsMask(expr,t); et = expr._type; } else if( et instanceof TypeInteger && t instanceof TypeInteger ) et=t; @@ -1643,6 +1645,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter24/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..df740202 --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,23 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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 String label() { return "ToInteger"; } + @Override protected String format() { return "(int)%1"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..31dcfc36 --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,21 @@ +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.util.SB; + +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java index f512f467..defec5ef 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/arm/arm.java @@ -318,6 +318,7 @@ public class arm extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..a56ad216 --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,19 @@ +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.util.SB; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java index 6be0376a..78d21f3b 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/riscv/riscv.java @@ -240,6 +240,7 @@ public class riscv extends Machine { 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); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..a79a1017 --- /dev/null +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,22 @@ +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.util.SB; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} diff --git a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java index 4beef793..e283bc8f 100644 --- a/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java +++ b/chapter24/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/x86_64_v2.java @@ -287,6 +287,7 @@ public class x86_64_v2 extends Machine { 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 -> new ConstantNode(Type.XCONTROL); case XorNode xor -> xor(xor); diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java index 8e9aaed6..041e0d06 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -124,8 +124,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type i64",e.getMessage()); } + code.parse().opto().typeCheck(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 009beb0b..23d0ef37 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -7,6 +7,25 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index 813fc4c2..59bd3b0c 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -123,8 +123,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type i64",e.getMessage()); } + code.parse().opto().typeCheck(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java index e0d56399..98e0f18a 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/Eval2.java @@ -283,6 +283,8 @@ public abstract class Eval2 { case StoreNode st -> store(st); case SubNode sub -> x(sub.in(1)) - x(sub.in(2)); case SubFNode sbf -> d(sbf.in(1)) - d(sbf.in(2)); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java b/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java index 1c116162..20aac367 100644 --- a/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java +++ b/chapter24/src/test/java/com/seaofnodes/simple/evaluator/Evaluator.java @@ -298,6 +298,8 @@ public class Evaluator { case OrNode or -> vall(or .in(1)) | vall(or .in(2)); case XorNode xor -> vall(xor.in(1)) ^ vall(xor.in(2)); case CastNode cast -> val(cast.in(1)); + case RoundF32Node rnd -> (double)(float)vald(rnd.in(1)); + case ToIntegerNode cvt -> (long)vald(cvt.in(1)); case ToFloatNode cast -> (double)vall(cast.in(1)); case LoadNode load -> load(load); case StoreNode store -> store(store); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java index 42c83f93..cd80b3ff 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/Parser.java @@ -2365,6 +2365,8 @@ public class Parser { // 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 ) { + if( val._type instanceof TypeFloat && t instanceof TypeInteger ) + val = new ToIntegerNode(val).peephole(); if( !(val._type instanceof TypeInteger tval && t instanceof TypeInteger t0 && !tval.isa(t0)) ) { if( !(val._type instanceof TypeFloat tval && t instanceof TypeFloat t0 && !tval.isa(t0)) ) return val; 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 b038366a..b6c3b0c9 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ConvertNode.java @@ -55,6 +55,10 @@ public class ConvertNode extends Node { if( (src instanceof TypeInteger || src==Type.NIL) && _dst instanceof TypeFloat ) return new ToFloatNode(val()); + // Convert to i64 first, then apply the destination's integer width. + if( src instanceof TypeFloat && _dst instanceof TypeInteger ) + return new ConvertNode(_dst,new ToIntegerNode(val()).peephole()); + // Narrow integers produce the declared sign/zero extension. if( src instanceof TypeInteger && _dst instanceof TypeInteger dst ) { if( dst._min==0 ) 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 9f5e107a..23a7e2c4 100644 --- a/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/Node.java @@ -77,7 +77,7 @@ public abstract class Node extends BaseNode implements Cloneable { New,Never,Not,Or,Parm,Phi,Proj, ReadOnly,Return,Region,RoundF32, Sar,Shl,Shr,Start,Stop,Store,Sub,ToFloat, - XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt,ExternOffset; + XCtrl,Xor,StartCU,StopCU,Guard,MemPhi,BulkMemPhi,PtrToInt,ExternOffset,ToInteger; public static final Tag[] VALS = values(); public Node make( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { return switch(this) { @@ -101,6 +101,7 @@ public abstract class Node extends BaseNode implements Cloneable { case Start -> new StartNode(null,null,TypeInteger.BOT); case Sub -> new SubNode(null,null,(byte)bais.packed1()); case ToFloat-> new ToFloatNode(null); + case ToInteger-> new ToIntegerNode(null); case XCtrl -> new XCtrlNode(); case Xor -> { int mode=bais.packed1(); assert mode==1; yield new XorNode(null,null,null); } case Guard -> GuardNode.make(bais); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java b/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java new file mode 100644 index 00000000..33806300 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/ToIntegerNode.java @@ -0,0 +1,24 @@ +package com.seaofnodes.simple.node; + +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFloat; +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"; } + + @Override public Type compute() { + Type t = in(1)._type; + if( t.isHigh() ) return TypeInteger.TOP; + if( t instanceof TypeFloat tf && tf.isConstant() ) + return TypeInteger.constant((long)tf.value()); + return TypeInteger.BOT; + } + + @Override public Node idealize() { return null; } + @Override Node copy(Node lhs, Node rhs) { return new ToIntegerNode(lhs); } +} diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java new file mode 100644 index 00000000..31dcfc36 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/arm/F8ToIARM.java @@ -0,0 +1,21 @@ +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.util.SB; + +public class F8ToIARM extends MachConcreteNode implements MachNode { + F8ToIARM(Node f8toi) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { return arm.DMASK; } + @Override public RegMask outregmap() { return arm.WMASK; } + @Override public void encoding( Encoding enc ) { + enc.add4(Arm64.floatToInteger(enc.reg(in(1))-arm.D_OFFSET,enc.reg(this))); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } + +} 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 2ba2d62e..660f1559 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 @@ -339,6 +339,7 @@ public class arm extends Machine { case StoreNode st -> st(st); case SubNode sub -> sub.mode()==2 ? new SubFARM(sub) : sub(sub); case ToFloatNode tfn -> new I2F8ARM(tfn); + case ToIntegerNode cvt -> new F8ToIARM(cvt); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java new file mode 100644 index 00000000..a56ad216 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/riscv/F8ToIRISC.java @@ -0,0 +1,19 @@ +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.util.SB; + +public class F8ToIRISC extends MachConcreteNode implements MachNode { + F8ToIRISC(Node f8toi) {super(f8toi);} + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return riscv.FMASK; } + @Override public RegMask outregmap() { return riscv.WMASK; } + @Override public void encoding( Encoding enc ) { + RiscV.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-riscv.F_OFFSET); + } + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} 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 808ad82e..ae69b439 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 @@ -252,6 +252,7 @@ public class riscv extends Machine { case StoreNode st -> st(st); case SubNode sub -> sub.mode()==2 ? new SubFRISC(sub) : sub(sub); case ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIRISC(cvt); case XorNode xor -> xor(xor); case LoopNode loop -> new LoopNode(loop); diff --git a/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java new file mode 100644 index 00000000..a79a1017 --- /dev/null +++ b/chapter25/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/F8ToIX86.java @@ -0,0 +1,22 @@ +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.util.SB; + +public class F8ToIX86 extends MachConcreteNode implements MachNode { + F8ToIX86(Node f8toi ) { super(f8toi); } + @Override public String op() { return "cvti"; } + @Override public RegMask regmap(int i) { assert i==1; return x86_64_v2.XMASK; } + @Override public RegMask killmap() { return x86_64_v2.FLAGS_MASK; } + @Override public RegMask outregmap() { return x86_64_v2.WMASK; } + + @Override public void encoding( Encoding enc ) { + X86.floatToInteger(enc,enc.reg(this),enc.reg(in(1))-x86_64_v2.XMM_OFFSET); + } + + @Override public void asm(CodeGen code, SB sb) { + sb.p(code.reg(this)).p(" = ").p("(int)").p(code.reg(in(1))); + } +} 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 f006411e..ff15fdad 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 @@ -320,6 +320,7 @@ public class x86_64_v2 extends Machine { case StoreNode st -> st(st); case SubNode sub -> sub.mode()==2 ? new SubFX86(sub) : sub(sub); case ToFloatNode tfn -> i2f8(tfn); + case ToIntegerNode cvt -> new F8ToIX86(cvt); case XCtrlNode x -> ConstantNode.raw(Type.XCONTROL); case XorNode xor -> xor(xor); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java index a71cc167..f1ff2e14 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter12Test.java @@ -124,8 +124,8 @@ struct N { !N? !next; int i; }; n.i = 3.14; return n.i; """); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 3.14 is not of declared type i64",e.getMessage()); } + code.parse().opto().typeCheck(); + assertEquals("3",Eval2.eval(code,0)); } @Test diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java index 20274581..80f70ff8 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter14FloatTest.java @@ -5,6 +5,25 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; public class Chapter14FloatTest { + @Test public void testNumericAssignments() { + String[][] tests = { + {"int rounded=17.3/2.5; return rounded;", "6", "6"}, + {"int rounded=arg/2.5; return rounded;", "6", "-6"}, + {"int rounded=0; rounded=arg/2.5; return rounded;", "6", "-6"}, + {"i8 rounded=arg*16.0; return rounded;", "16", "-16"}, + {"int rounded=1e100; return rounded;", "9223372036854775807", "9223372036854775807"}, + {"int rounded=-1e100; return rounded;", "-9223372036854775808", "-9223372036854775808"}, + {"int rounded=1e300*1e300-1e300*1e300; return rounded;", "0", "0"}, + {"f32 rounded=arg+16777216.0; return rounded;", "1.6777232E7", "1.6777199E7"} + }; + for( String[] test : tests ) { + var code = new CodeGen(test[0]).parse().opto().typeCheck(); + assertEquals(test[0],test[1],Eval2.eval(code,17)); + assertEquals(test[0],test[2],Eval2.eval(code,-17)); + } + } + + @Test public void testSubZeroFloat() { var code = new CodeGen("flt x = arg; return 0-x;").parse().opto().typeCheck(); // Compare strings so +0.0 and -0.0 remain distinct. diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java index ab1a59e1..62d0b261 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter17bTest.java @@ -123,8 +123,8 @@ return s.x; @Test public void testInt10() { CodeGen code = new CodeGen("flt x = 1.5; x++; int i = x; return i;"); - try { code.parse().opto().typeCheck(); fail(); } - catch( Exception e ) { assertEquals("Type 2.5f is not of declared type i64",e.getMessage()); } + code.parse().opto().typeCheck(); + assertEquals("2",Eval2.eval(code,0)); } diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java index 4d610a42..ba71d1dd 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Chapter21Test.java @@ -15,6 +15,27 @@ import static org.junit.Assert.*; public class Chapter21Test { + @Test public void testFloatToInteger() throws IOException { + String src = "val float_to_int={ flt x -> int i=x; return i; };"+ + "val float_to_u8={ flt x -> u8 i=x; return i; };"; + TestC.runC(src,"floatToInteger","",-1); + for( double x : new double[]{0.0,-0.0,6.92,-6.92,255.75,-257.75, + 0x1p63,Math.nextDown(0x1p63),-0x1p63,Math.nextDown(-0x1p63), + Double.POSITIVE_INFINITY,Double.NEGATIVE_INFINITY,Double.NaN} ) { + for( String name : new String[]{"float_to_int","float_to_u8"} ) { + long expected = name.equals("float_to_u8") ? ((long)x)&255 : (long)x; + EvalRisc5 r = TestRisc5.build(src,name,0,-1,false); + r.fregs[riscv.FA0-riscv.F_OFFSET]=x; + assertEquals(0,r.step(1000)); + assertEquals(name+"("+x+")",expected,r.regs[riscv.A0]); + EvalArm64 a = TestArm64.build(name,src,0,-1,false); + a.fregs[arm.D0-arm.D_OFFSET]=x; + assertEquals(0,a.step(1000)); + assertEquals(name+"("+x+")",expected,a.regs[arm.X0]); + } + } + } + @Test public void testReadBeforeConditionalStore() { var code = new CodeGen(""" struct S { int x; }; !S !a=new S; !S !b=new S; diff --git a/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java b/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java index 5716c1b8..21e677ce 100644 --- a/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java +++ b/chapter25/src/test/java/com/seaofnodes/simple/Eval2.java @@ -307,6 +307,8 @@ public abstract class Eval2 { case SubNode sub -> sub.mode()==1 ? (Object)(x(sub.in(1)) - x(sub.in(2))) : (Object)(d(sub.in(1)) - d(sub.in(2))); + case RoundF32Node rnd -> (double)(float)d(rnd.in(1)); + case ToIntegerNode cvt -> (long)d(cvt.in(1)); case ToFloatNode toflt-> (double)x(toflt.in(1)); case XorNode xor -> x(xor.in(1)) ^ x(xor.in(2)); default -> throw Utils.TODO(); diff --git a/chapter25/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c b/chapter25/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c new file mode 100644 index 00000000..cf9390d8 --- /dev/null +++ b/chapter25/src/test/java/com/seaofnodes/simple/progs/floatToInteger.c @@ -0,0 +1,25 @@ +#include +#include +#include + +extern int64_t float_to_int(double); +extern int64_t float_to_u8(double); + +int main(void) { + double values[] = {0.0,-0.0,6.92,-6.92,0.99,-0.99,255.75,-257.75, + 0x1p63,0x1.fffffffffffffp62,-0x1p63,-0x1.0000000000001p63, + INFINITY,-INFINITY,NAN,-NAN}; + for( unsigned i=0; i=0x1p63 ? INT64_MAX + : x<=-0x1p63 ? INT64_MIN : (int64_t)x; + int64_t actual=float_to_int(x), narrow=float_to_u8(x); + if( actual!=expected || narrow!=(expected&255) ) { + fprintf(stderr,"conversion of %g: got %lld / %lld, expected %lld / %lld\n", + x,(long long)actual,(long long)narrow, + (long long)expected,(long long)(expected&255)); + return 1; + } + } + return 0; +} diff --git a/docs/chapter-backports.md b/docs/chapter-backports.md index 765cad82..4b516036 100644 --- a/docs/chapter-backports.md +++ b/docs/chapter-backports.md @@ -41,8 +41,8 @@ shallow-reference boundary and resolving field definitions by name. Historical validation entries below use the chapter numbers and test names in effect when the work was done. In particular, old 10a/10b records refer to current 10/11, and old `Chapter12Test` float regressions now live in -`Chapter14FloatTest`. The pending float-to-int conversion belongs with the -numeric material in Chapter 14; it is not implemented by this reorganization. +`Chapter14FloatTest`. Float-to-int assignment conversion was subsequently +added with the numeric material in Chapter 14 and propagated through 25. Validation of the reorganization (2026-10-03): Make suites pass in every chapter 10-25, including the shared printer/ISA tests and Chapter 25's native @@ -465,14 +465,6 @@ lines beyond the original extraction. ## Pending corrections -- **Float-to-integer conversion (new feature).** Missing from Chapters 14-25; - Chapter 14's `RoundF32Node` only narrows floats to `f32`. Add an explicit - conversion, with Chapter 14 as the proposed introduction, and carry it - through the later chapters. Decide syntax, rounding versus truncation, - and behavior for NaN, infinities, and out-of-range values before implementing. - Cover constant folding, evaluation, and machine lowering on all three targets; - update the Chapter 14 explanation and add boundary-case regressions. - - **Chapter 24 Load BOTTOM-on-error backport, unwound/deferred (2026-09-28).** Cliff requested starting with 24 and analyzing failures before proceeding. Literal global BOTTOM memory/pointer inputs already propagate BOTTOM. The @@ -664,6 +656,23 @@ The top-level runner accepts explicit chapter lists, e.g. ## Validation record +- **Numeric assignment conversions and evaluators (2026-10-07).** Chapters + 14-25 accept float-to-integer assignment using ToIntegerNode: truncate toward + zero, saturate to i64 on overflow, map NaN to zero, then apply the declared + integer width. Locals, fields, and arrays use the conversion; Chapters 22-24 + also convert before their store-specific width handling. Chapter 25 lowers + authoritative Convert nodes and serializes ToInteger in exported graphs. + Both evaluators now execute ToInteger and RoundF32, including nonconstant + f32 array stores. The Chapter 14 README documents the assignment syntax. + Instruction selection starts in 19, ARM support in 20, and shared encoding + in 21. x86 and RISC-V sequences normalize hardware overflow/NaN differences; + ARM uses FCVTZS. Full Make suites pass in 14-25 and the shared ISA module. + Tests cover constant and dynamic conversions, signed values, narrow integer + results, limits, infinities, and NaN; Chapter 25 runs native x86 plus compiled + ARM/RISC-V emulation. Disposable probes also check 19-20 selection/allocation, + 21-24 encoding/emulation, and 25 importing an exported conversion function. + Logs: `build/float-convert-.log`, `build/float-convert-isa.log`. + - **Floating arrays in Chapter 18 (2026-10-07).** Added TypeFloat to the `TypeStruct.makeAry` assertion, matching Chapter 19 onward; earlier array chapters already accept floating elements. The original `return new flt[1];` @@ -672,8 +681,8 @@ The top-level runner accepts explicit chapter lists, e.g. It fails at the original assertion before the correction. The complete Chapter 18 suite passes (340 tests plus its fuzzer wrapper); no other compiler snapshot needed this assertion change. Nonconstant scalar and array narrowing - now passes type checking with the RoundF32 correction below. Eval2 does not - execute RoundF32 yet, so runtime coverage uses constant f32 stores. Logs: + passes type checking with the RoundF32 correction below. Subsequent evaluator + support also exercises nonconstant f32 stores at runtime. Original logs: `build/float-arrays-18-before.log`, `build/float-arrays-18-after.log`. - **Nonconstant f32 narrowing (2026-10-07).** RoundF32Node now returns F32 diff --git a/isa/src/main/java/com/seaofnodes/isa/Arm64.java b/isa/src/main/java/com/seaofnodes/isa/Arm64.java index 2c969da0..ce7f7b11 100644 --- a/isa/src/main/java/com/seaofnodes/isa/Arm64.java +++ b/isa/src/main/java/com/seaofnodes/isa/Arm64.java @@ -236,6 +236,12 @@ public final class Arm64 { (vd << 12) | (0x01100010 << 4) | vm; } + /** FCVTZS Xd,Dn: truncate, saturate overflow, and convert NaN to zero. */ + public static int floatToInteger(int rn, int rd) { + assert 0<=rn && rn<32 && 0<=rd && rd<32; + return 0x9E780000 | (rn<<5) | rd; + } + public static int float_cast(int opcode, int ftype, int rn, int rd) { assert 0 <= rd && rd < 32; assert 0 <= rn && rn < 32; diff --git a/isa/src/main/java/com/seaofnodes/isa/RiscV.java b/isa/src/main/java/com/seaofnodes/isa/RiscV.java index cb446a6a..0ecd1cec 100644 --- a/isa/src/main/java/com/seaofnodes/isa/RiscV.java +++ b/isa/src/main/java/com/seaofnodes/isa/RiscV.java @@ -59,6 +59,16 @@ public final class RiscV { return bits << 12 | rd << 7 | opcode; } + /** FCVT.L.D with RTZ saturates overflow; explicitly map either NaN to zero. */ + public static void floatToInteger(CodeSink out, int dst, int src) { + out.add4(r_type(OP_FP,dst,1,src,0,0x71)); // FCLASS.D + out.add4(i_type(OP_IMM,dst,7,dst,0x300)); // ANDI: NaN classification bits + out.add4(b_type(OP_BRANCH,1,(short)dst,(short)0,12)); // BNE nan + out.add4(r_type(OP_FP,dst,RM.RTZ.ordinal(),src,2,0x61)); // FCVT.L.D + out.add4(j_type(OP_JAL,0,8)); // J done + out.add4(i_type(OP_IMM,dst,0,0,0)); // nan: LI dst,0 + } + public static int i_type(int opcode, int rd, int func3, int rs1, int imm12) { assert 0 <= rd && rd < 32; assert 0 <= rs1 && rs1 < 32; diff --git a/isa/src/main/java/com/seaofnodes/isa/X86.java b/isa/src/main/java/com/seaofnodes/isa/X86.java index 99d61dff..78fa3da5 100644 --- a/isa/src/main/java/com/seaofnodes/isa/X86.java +++ b/isa/src/main/java/com/seaofnodes/isa/X86.java @@ -60,6 +60,22 @@ public final class X86 { out.add1(0x48).add1(0x99); // CQO: sign-extend rax into rdx:rax. unary(out,0xF7,7,src); } + /** Truncate f64 to i64, saturating overflow and mapping NaN to zero. */ + public static void floatToInteger(CodeSink out, int dst, int src) { + sse(out,0xF2,0x0F2C,true,dst,src); // CVTTSD2SI + imm(out,0x81,7,dst,1); // CMP dst,1: OF iff dst is MIN_VALUE + out.add1(0x71).add1((src>=8 ? 5 : 4)+24); // JNO done + sse(out,0x66,0x0F2E,false,src,src); // UCOMISD: unordered iff NaN + out.add1(0x7A).add1(19); // JP nan + sse(out,0x66,0x0F7E,true,src,dst); // MOVQ: inspect the float's sign + shift(out,7,dst,63); // SAR: 0 or -1 + unary(out,0xF7,2,dst); // NOT: -1 or 0 + reg(out,0x0FBA,7,dst); // BTC sign bit: MAX_VALUE or MIN_VALUE + out.add1(63); + out.add1(0xEB).add1(3); // JMP done + reg(out,0x33,dst,dst); // nan: XOR dst,dst + } + public static void sse(CodeSink out, int prefix, int op, boolean wide, int dst, int src) { if( prefix!=0 ) out.add1(prefix); rexF(dst,src,-1,wide,out); diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java index 2a4a2708..2fa4aec1 100644 --- a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java +++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalArm64.java @@ -404,10 +404,13 @@ public class EvalArm64 { break; } case 0x9E: { - // scvtf - is_f = true; int rs1 = (ir >> 5) & 0x1F; - frval = (double)regs[rs1]; + if( (ir & 0xFFFFFC00)==0x9E780000 ) { // FCVTZS Xd,Dn + rval = (long)fregs[rs1]; + } else { // SCVTF + is_f = true; + frval = (double)regs[rs1]; + } break; } diff --git a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java index f618075d..46baf447 100644 --- a/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java +++ b/isa/src/test-support/java/com/seaofnodes/isa/eval/EvalRisc5.java @@ -334,7 +334,25 @@ public class EvalRisc5 { default -> throw new UnsupportedOperationException(); }; break; - case 26: frval = (double)regs[rs1]; break; // fcvt.w.d + case 24: // FCVT.L.D, RTZ + if( rs2!=2 || ((ir>>12)&7)!=1 ) throw new UnsupportedOperationException(); + is_f = false; + rval = Double.isNaN(lhs) ? Long.MAX_VALUE : (long)lhs; + break; + case 26: frval = (double)regs[rs1]; break; // fcvt.d.l + case 28: { // FCLASS.D + if( rs2!=0 || ((ir>>12)&7)!=1 ) throw new UnsupportedOperationException(); + is_f = false; + long bits = Double.doubleToRawLongBits(lhs); + boolean neg = bits<0; + int kind = Double.isNaN(lhs) ? ((bits&(1L<<51))==0 ? 8 : 9) + : Double.isInfinite(lhs) ? (neg ? 0 : 7) + : lhs==0 ? (neg ? 3 : 4) + : Math.abs(lhs) X86.floatToInteger(b,0,0)); + check("f24d0f2cca4983f901711d66450f2ed27a13664d0f7ed149c1f93f49f7d1490fbaf93feb034d33c9", + b -> X86.floatToInteger(b,9,10)); check("4d03ca", b -> X86.reg(b,0x03,9,10)); check("4d0fafca", b -> X86.reg(b,0x0FAF,9,10)); check("4981c17fffffff", b -> X86.imm(b,0x81,0,9,-129)); -- cgit v1.2.3