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 --- .../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 ++++++++++++++++++++++ 16 files changed, 168 insertions(+), 5 deletions(-) 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 (limited to 'chapter25/src') 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; +} -- cgit v1.2.3