diff options
Diffstat (limited to 'chapter29/src/test/java/com/seaofnodes/simple/Chapter22Test.java')
| -rw-r--r-- | chapter29/src/test/java/com/seaofnodes/simple/Chapter22Test.java | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/chapter29/src/test/java/com/seaofnodes/simple/Chapter22Test.java b/chapter29/src/test/java/com/seaofnodes/simple/Chapter22Test.java new file mode 100644 index 00000000..e9a668a7 --- /dev/null +++ b/chapter29/src/test/java/com/seaofnodes/simple/Chapter22Test.java @@ -0,0 +1,375 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.parser.Parser; +import com.seaofnodes.isa.eval.EvalRisc5; + +import com.seaofnodes.isa.eval.EvalArm64; + +import com.seaofnodes.simple.codegen.RegAllocTestSupport.CheckedCodeGen; +import com.seaofnodes.simple.util.Utils; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.node.cpus.arm.arm; +import com.seaofnodes.simple.node.cpus.riscv.riscv; +import java.io.IOException; +import org.junit.Ignore; +import org.junit.Test; +import static org.junit.Assert.*; + +public class Chapter22Test { + + @Test public void testNarrowCReturnsEmulated() { + String[] types = {"i8","u8","i16","u16","i32","u32","int"}; + long[] values = {0xa5a5a5a500000000L,0xa5a5a5a50000007fL,0xa5a5a5a500000080L, + 0xa5a5a5a500007fffL,0xa5a5a5a500008000L,0xa5a5a5a57fffffffL, + 0xa5a5a5a580000000L,0xa5a5a5a5ffffffffL}; + for( String target : new String[]{"arm","riscv"} ) + for( int i=0; i<types.length; i++ ) { + String src = "{"+types[i]+"} c_narrow=\"C\"; return c_narrow();"; + CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,target,"SystemV"); + var enc = code._encoding; + // Link the external call to a tiny independent native stub. + var call = code._stop.walk(n -> n instanceof com.seaofnodes.simple.node.CallNode ? n : null); + int start = enc.opStart(call); + ((com.seaofnodes.simple.codegen.RIPRelSize)call).patch(enc,start,enc.opLen(call),0x1000-start); + for( long value : values ) { + long expected = switch(i) { + case 0 -> (byte)value; case 1 -> value&255; + case 2 -> (short)value; case 3 -> value&65535; + case 4 -> (int)value; case 5 -> value&0xffffffffL; + default -> value; + }; + byte[] image = new byte[1<<16]; + byte[] bits = enc.bits(); + System.arraycopy(bits,0,image,0,bits.length); + if( target.equals("arm") ) { + EvalArm64 cpu = new EvalArm64(image,1<<15); + // MOVZ X0,0x1100; LDR X0,[X0]; RET X30. + cpu.st4(0x1000,0xd2822000); cpu.st4(0x1004,0xf9400000); + cpu.st4(0x1008,0xd65f03c0); + cpu.st8(0x1100,value); + assertEquals(0,cpu.step(1000)); + assertEquals(0,cpu._pc); + assertEquals(types[i],expected,cpu.regs[0]); + } else { + EvalRisc5 cpu = new EvalRisc5(image,1<<15); + // AUIPC a0,0; LD a0,12(a0); RET. + cpu.st4(0x1000,0x00000517); cpu.st4(0x1004,0x00c53503); + cpu.st4(0x1008,0x00008067); + // RV64 widens even u32 returns by sign-extending bit 31. + cpu.st8(0x100c,i==6 ? value : (int)expected); + assertEquals(0,cpu.step(1000)); + assertEquals(0,cpu._pc); + assertEquals(types[i],expected,cpu.regs[riscv.A0]); + } + } + } + } + + @Test public void testNarrowCReturns() throws IOException { + String src = """ + {i8} c_i8="C"; {u8} c_u8="C"; + {i16} c_i16="C"; {u16} c_u16="C"; + {i32} c_i32="C"; {u32} c_u32="C"; + {int} c_i64="C"; + val test_i8={->return c_i8();}; + val test_u8={->return c_u8();}; + val test_i16={->return c_i16();}; + val test_u16={->return c_u16();}; + val test_i32={->return c_i32();}; + val test_u32={->return c_u32();}; + val test_i64={->return c_i64();}; + val test_cmp={->return c_i32()==-1;}; + """; + for( String conv : new String[]{"SystemV","win64"} ) + TestC.run(src,"c_returns"+conv,null,conv, + conv.equals("SystemV") ? "sysv_abi" : "ms_abi", + TestC.C_DRIVERS_DIR+"c_returns.c","",-1); + } + + + @Test public void testSubZeroTypeError() { + try { + new CodeGen("return null-0;").driver(CodeGen.Phase.TypeCheck); + fail("Subtraction must reject null even when the other operand is zero"); + } catch( Parser.ParseException e ) { + assertEquals("Cannot null - 0",e.getMessage()); + } + } + + + @Ignore + @Test + public void testJig() throws IOException { + String src = """ +struct s0 { + bool v1; + i16 v2; + int v3; + i8 v4; + byte v5; +}; +while(new s0.v3) + while(new s0.v5<<new s0.v4) {} +if(0) { + if(0) { + flt !P5ZUD4=new s0.v2; + } + while(0) {} +} +return new s0.v1; +"""; + testCPU(src,"x86_64_v2", "win64" ,-1,null); + testCPU(src,"riscv" , "SystemV",-1,null); + testCPU(src,"arm" , "SystemV",-1,null); + } + + static CodeGen testCPU( String src, String cpu, String os, int spills, String stop ) { + CodeGen code = new CheckedCodeGen(src).driver(CodeGen.Phase.Encoding,cpu,os); + int delta = spills>>3; + if( delta==0 ) delta = 1; + if( spills != -1 && !CodeGen.iterSeedOverridden() ) + assertEquals("Expect spills:",spills,code._regAlloc._spillScaled,delta); + if( stop != null ) + assertEquals(stop, code.print()); + return code; + } + + + static int testCPUSize( String src, String cpu, String os, int spills ) { + // Includes the file initializer's hidden $init=true store. + return testCPU(src,cpu,os,spills, "return 0;" )._encoding._bits.size(); + } + + // Should not fold away + @Test public void testSextFail() throws IOException { + String src = """ +struct _Person { i32 age;}; +!_Person !p = new _Person; +p.age = (arg<<17)>>17; +return 0; +"""; + assertEquals(76, testCPUSize(src, "x86_64_v2","win64", 3 )); + assertEquals(92, testCPUSize(src, "riscv", "SystemV", 4 )); + assertEquals(92, testCPUSize(src, "arm" , "SystemV", 4 )); + + // do assertEquals here + EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away", 0, 4, false); + int trap = R5.step(100); + assertEquals(0,trap); + + EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away", src, 0, 4, false); + trap = A5.step(100); + assertEquals(0,trap); + + } + + // Should not fold away + @Test public void testSextFail2() throws IOException { + String src = """ + struct _Person { i32 age;}; + !_Person !p = new _Person; + p.age = (arg<<48)>>48; + return 0; + """; + + EvalRisc5 R5 = TestRisc5.build( src, "sext_str_not_fold_away_2", 0, 4, false); + int trap = R5.step(100); + assertEquals(0,trap); + + EvalArm64 A5 = TestArm64.build("sext_str_not_fold_away_2", src, 0, 3, false); + int trap_arm = A5.step(100); + assertEquals(0,trap_arm); + + assertEquals(76, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(92, testCPUSize(src, "riscv", "SystemV",4 )); + assertEquals(92, testCPUSize(src, "arm", "SystemV",4 )); + + } + + // Should fold away + @Test public void testSextSuccess() throws IOException { + String src = """ +// Should fold away sign extend +struct _Person { i8 age;}; +!_Person !p = new _Person; +p.age = (arg<<48)>>48; +return 0; +"""; + + EvalRisc5 R5 = TestRisc5.build( src, "sext_str_fold_away", 0, 5, false); + int trap = R5.step(100); + assertEquals(0,trap); + + EvalArm64 A5 = TestArm64.build("sext_str_fold_away", src, 0, 5, false); + int trap_arm = A5.step(100); + assertEquals(0,trap_arm); + + assertEquals(66, testCPUSize(src, "x86_64_v2","win64",3 )); + assertEquals(84, testCPUSize(src, "riscv", "SystemV",5 )); + assertEquals(84, testCPUSize(src, "arm", "SystemV",5)); + // do assertEquals here + } + + + // Int now is changed to 4 bytes. + @Test public void testPerson() throws IOException { + String src = +""" +struct Person { // Exports a field typed singleton+final *class:Person{age:i32} + i32 age; +}; + +val fcn = { !Person?[] !ps, int x -> // exports a field with a constant fcn ptr; exports the fcn also + if( ps[x] ) + ps[x].age++; +}; +"""; + String person = "6\n"; + TestC.run(src, "person2", null, TestC.CALL_CONVENTION, "", TestC.C_DRIVERS_DIR+"person.c", person, 2); + + // Memory layout starting at PS: + int ps = 1<<16; // Person array pointer starts at heap start + // Person[3] = { len,pad,P0,P1,P2 }; // sizeof = 4*8 + // P0 = { age } // sizeof=8 + int p0 = ps+4*8+0*8; + // P1 = { age } // sizeof=8 + int p1 = ps+4*8+1*8; + // P2 = { age } // sizeof=8 + int p2 = ps+4*8+2*8; + EvalRisc5 R5 = TestRisc5.build( src, "fcn", ps, 2, false); + R5.regs[riscv.A1] = 1; // Index 1 + R5.st8(ps,3); // Length + R5.st8(ps+1*8,p0); + R5.st8(ps+2*8,p1); + R5.st8(ps+3*8,p2); + R5.st8(p0, 5); // age= 5 + R5.st8(p1,17); // age=17 + R5.st8(p2,60); // age=60 + + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals( 5+0,R5.ld8(p0)); + assertEquals(17+1,R5.ld8(p1)); + assertEquals(60+0,R5.ld8(p2)); + + EvalArm64 A5 = TestArm64.build("fcn", src, ps, 2, false); + A5.regs[arm.X1] = 1; // Index 1 + A5.st8(ps, 3); + A5.st8(ps+1*8,p0); + A5.st8(ps+2*8,p1); + A5.st8(ps+3*8,p2); + A5.st8(p0, 5); // age= 5 + A5.st8(p1,17); // age=17 + A5.st8(p2,60); // age=60 + + int trap_arm = A5.step(100); + assertEquals(0,trap_arm); + assertEquals( 5+0, A5.ld8(p0)); + assertEquals(17+1, A5.ld8(p1)); + assertEquals(60+0, A5.ld8(p2)); + } + + @Test + public void testCoRecur() { + String src = """ +val x = 5; // aa.az; // Error to self-define forward ref +struct _A { !Test._B? !b; !Test._C? !c; i64 ax; val az = x*2; new _A = { i64 x -> ax=x; }; }; +struct _B { !Test._A? !a; !Test._C? !c; f32 bx; val bz = x*3; new _B = { f32 x, !Test._A? aa -> bx=x; a=aa; }; }; +struct _C { !Test._A? !a; !Test._B? !b; f64 cx; val cz = x*x; new _C = { f64 x, !Test._A? aa, !Test._B? bb -> cx=x; a=aa; b=bb; }; }; +!_A !aa = new _A(17); +!_B !bb = new _B(3.14f, aa); +!_C !cc = new _C(2.73, aa, bb); +aa.b = bb; +aa.c = cc; +bb.c = cc; +return cc.cz; +"""; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("return 25;", code.print()); + assertEquals("25", Eval2.eval(code, 0)); + } + + @Test + public void testHelloWorld() throws IOException { + String src = """ +// fd buf len -> len +{ i32 i64 u32 -> u32 } write = "C"; +// Write byte array to stdout +val p = { u8[~] str -> + i64 ptr = str; // cast array base to i64 + return write(1,ptr,str#); +}; +p("Hello, World!"); +return 0; +"""; + TestC.runSF(src,"helloWorld", "Hello, World!",2); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build( src, "helloWorld", 0, 5, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + assertEquals("Hello, World!",R5._stdout.toString()); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("helloWorld", src,0, 5, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + assertEquals("Hello, World!",arm._stdout.toString()); + } + + + @Test + public void testStrLoad() { + CodeGen code = new CodeGen("return \"abc\"[1];"); + code.driver(CodeGen.Phase.FinalizeIR); + assertEquals("return .[];", code.print()); + } + + + @Test + public void testFinalArray() { + String src = """ +int N=4; +i32[] !is = new i32[N]; +for( int i=0; i<N; i++ ) + is[i] = i*i; +val _sum = { i32[~] is -> // final array + int sum=0; + for( int i=0; i<is#; i++ ) + sum += is[i]; + return sum; +}; +return _sum(is); +"""; + CodeGen code = new CodeGen(src).driver(CodeGen.Phase.TypeCheck); + assertEquals("return Phi(Loop,0,(Phi_sum+.[]));", code.print()); + assertEquals("14", Eval2.eval(code, 0)); + } + + + @Test @Ignore + public void testEcho() throws IOException { + String src = +""" +// Echo stdin to stdout. +return sys.io.p( sys.io.stdin() ); +"""; + TestC.runSF(src,"echo", "",0); + + // Evaluate on RISC5 emulator + EvalRisc5 R5 = TestRisc5.build( src, "echo", 0, 2, false); + int trap = R5.step(100); + assertEquals(0,trap); + assertEquals(0,R5.regs[riscv.A0]); + + // Evaluate on ARM emulator + EvalArm64 arm = TestArm64.build("echo", src, 0, 2, false); + trap = arm.step(100); + assertEquals(0,trap); + assertEquals(0,arm.regs[0]); + } + + +} |
