From 19f9ea99114e9c1819239508f812d6521661f7bb Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Tue, 27 Oct 2026 19:06:47 -0700 Subject: Backport a bunch of fixes ARM encoding Extra fidx burned with no constructor Getting types during a parse to avoid a whole-program walk later --- .../main/java/com/seaofnodes/simple/Parser.java | 16 +++++---- .../com/seaofnodes/simple/codegen/CodeGen.java | 8 ----- .../com/seaofnodes/simple/codegen/ParseAll.java | 10 ++---- .../seaofnodes/simple/node/cpus/arm/CallRRARM.java | 9 +++-- .../simple/node/cpus/riscv/CallRRISC.java | 5 ++- .../simple/node/cpus/x86_64_v2/CallRX86.java | 7 ++-- .../com/seaofnodes/simple/IndirectCallTest.java | 32 +++++++++++++++++ .../java/com/seaofnodes/simple/ParseAllTest.java | 42 ++++++++++++++++++++++ .../simple/codegen/IndirectCallTestSupport.java | 17 +++++++++ .../simple/codegen/ParseAllTestSupport.java | 16 +++++++++ 10 files changed, 135 insertions(+), 27 deletions(-) create mode 100644 chapter28a/src/test/java/com/seaofnodes/simple/IndirectCallTest.java create mode 100644 chapter28a/src/test/java/com/seaofnodes/simple/ParseAllTest.java create mode 100644 chapter28a/src/test/java/com/seaofnodes/simple/codegen/IndirectCallTestSupport.java create mode 100644 chapter28a/src/test/java/com/seaofnodes/simple/codegen/ParseAllTestSupport.java (limited to 'chapter28a') diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java b/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java index 5a9a5ca3..6bd02363 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/Parser.java @@ -100,6 +100,9 @@ public class Parser { ScopeNode _continueScope; // Merge all the while-continues here ScopeNode _returnScope; // Merge all the function exits here Ary _frefs; // Forward refs created during this parse + // Layouts needed by field accesses in this source file. Resolve after + // parsing, since a later local struct declaration can satisfy the use. + public final LinkedHashSet _requiredTypes = new LinkedHashSet<>(); TypeStruct _ctorOpenStruct; // Open struct being initialized by an allocation constructor block. // Constructor exit types survive scope teardown until all fields are known. // A null entry denotes a constructor with no returning path. @@ -170,6 +173,7 @@ public class Parser { assert _frefs == null; _lexer = new Lexer(ref); _frefs = new Ary<>(FRefNode.class); + _requiredTypes.clear(); // Starting Scope has control, memory, initial arguments _scope = new ScopeNode(); _scope.define(ScopeNode.CTRL, Type.CONTROL , false, null, _lexer); @@ -1472,11 +1476,6 @@ public class Parser { return TypeFunPtr.make1((byte)2, true, targs, tret, _code.fidx(clzBare,order)); } - // A class type carries the constructor function as a required final field. - private TypeStruct classStruct( String typeName, TypeFunPtr init, boolean open ) { - return TypeStruct.make(addClzPrefix(typeName),open,Field.make(initFieldName(typeName),init,_code.alias(_ref._cname),true)); - } - private static String initFieldName( String typeName ) { if( startsClzPrefix(typeName) ) typeName = typeName.substring(clzPrefix.length()); @@ -1688,7 +1687,10 @@ public class Parser { private TypeMemPtr forwardRefType( String tname, TypeStruct tself ) { TYPES.put(tname,tself); String clzName = addClzPrefix( tname ); - TYPES.put(clzName, classStruct(tname,structInitSig(false,tself),true)); + // Only a struct declaration publishes an instance initializer. A + // forward type may name a file with just , so do not invent + // a constructor field (or overwrite an already parsed class). + TYPES.putIfAbsent(clzName,TypeStruct.make(clzName,true)); return TypeMemPtr.make(tself); } @@ -2741,6 +2743,8 @@ public class Parser { } // Field offset for an unknown type public Node fldoff( Node n, String name ) { + if( n._type instanceof TypeMemPtr ptr && ptr._obj._fref ) + _requiredTypes.add(ptr._obj._name); return new FldOffNode(n,name,loc()).peephole(); } public Node peep( Node n ) { diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java index e6bace8c..8b3857a1 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java @@ -472,14 +472,6 @@ public class CodeGen { // Last check for bad programs public CodeGen typeCheck() { return phase(Phase.TypeCheck, _phase==Phase.FinalizeIR, () -> TypeCheck.run(this)); } - public static TypeStruct requiredStruct(Node n) { - // CNC: remove? cleanup? - if( n instanceof NewNode alloc ) return alloc._ts; - if( n instanceof ConFldOffNode off ) return off._ts; - if( n instanceof FldOffNode off && off.in(0)._type instanceof TypeMemPtr ptr ) return ptr._obj; - return null; - } - // --------------------------- // TypeCheck queued surviving CLInits during its existing error walk. public CodeGen lower() { diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java index 171d9923..6ae4e4bb 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java @@ -172,13 +172,9 @@ public abstract class ParseAll { } private static void addRequiredTypes(CodeGen code, CompUnit cunit) { - LinkedHashSet required = new LinkedHashSet<>(); - code._stop.walk(n -> { - TypeStruct ts = CodeGen.requiredStruct(n); - if( ts!=null && ts._fref ) required.add(ts._name); - return null; - }); - for( String typeName : required ) { + for( String typeName : code.P._requiredTypes ) { + // A forward use can have been resolved later in this source file. + if( Parser.TYPES.get(typeName) instanceof TypeStruct ts && !ts._fref ) continue; try { CompUnit xcunit = findRequiredType(code,cunit,typeName); if( xcunit != null ) diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java index 1949ae46..856af3b8 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java @@ -1,16 +1,19 @@ package com.seaofnodes.simple.node.cpus.arm; +import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.isa.Arm64; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.util.SB; public class CallRRARM extends CallNode implements MachNode { - CallRRARM(CallNode call) { super(call); } + final TypeFunPtr _tfp; + CallRRARM( CallNode call ) { super(call); _tfp = call.tfp(); } + @Override public TypeFunPtr tfp() { return _tfp; } @Override public String op() { return "callr"; } @Override public String label() { return op(); } @Override public RegMask regmap(int i) { - return i==_inputs._len + return i==_inputs._len-1 ? arm.RMASK // Function call target : arm.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument } @@ -19,7 +22,7 @@ public class CallRRARM extends CallNode implements MachNode { @Override public void encoding( Encoding enc ) { // Needs a register, typically a jump-and-link-register opcode // blr - short self = enc.reg(this); + short self = enc.reg(fptr()); enc.add4(Arm64.blr(Arm64.OP_CALLRARM, self)); } diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java index cda4bc79..4ab69430 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java @@ -1,12 +1,15 @@ package com.seaofnodes.simple.node.cpus.riscv; +import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.isa.RiscV; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.*; import com.seaofnodes.simple.util.SB; public class CallRRISC extends CallNode implements MachNode { - CallRRISC( CallNode call ) { super(call); } + final TypeFunPtr _tfp; + CallRRISC( CallNode call ) { super(call); _tfp = call.tfp(); } + @Override public TypeFunPtr tfp() { return _tfp; } @Override public String op() { return "callr"; } @Override public String label() { return op(); } @Override public RegMask regmap(int i) { diff --git a/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java b/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java index f97163eb..c204f25c 100644 --- a/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java +++ b/chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java @@ -1,5 +1,6 @@ package com.seaofnodes.simple.node.cpus.x86_64_v2; +import com.seaofnodes.simple.type.TypeFunPtr; import com.seaofnodes.isa.X86; import com.seaofnodes.simple.codegen.*; import com.seaofnodes.simple.node.CallNode; @@ -7,11 +8,13 @@ import com.seaofnodes.simple.node.MachNode; import com.seaofnodes.simple.util.SB; public class CallRX86 extends CallNode implements MachNode { - CallRX86( CallNode call ) { super(call); } + final TypeFunPtr _tfp; + CallRX86( CallNode call ) { super(call); _tfp = call.tfp(); } + @Override public TypeFunPtr tfp() { return _tfp; } @Override public String op() { return "callr"; } @Override public String label() { return op(); } @Override public RegMask regmap(int i) { - return i==_inputs._len + return i==_inputs._len-1 ? x86_64_v2.WMASK // Function call target : x86_64_v2.callInMask(tfp(),i,fun()._maxArgSlot); // Normal argument } diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/IndirectCallTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/IndirectCallTest.java new file mode 100644 index 00000000..e65ce3d1 --- /dev/null +++ b/chapter28a/src/test/java/com/seaofnodes/simple/IndirectCallTest.java @@ -0,0 +1,32 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.RegMask; +import com.seaofnodes.simple.codegen.IndirectCallTestSupport; +import com.seaofnodes.simple.node.*; +import com.seaofnodes.simple.type.Type; +import com.seaofnodes.simple.type.TypeFunPtr; +import org.junit.Test; +import static org.junit.Assert.*; + +public class IndirectCallTest { + @Test public void testTargetOperand() { + for( String cpu : new String[]{"x86_64_v2","riscv","arm"} ) { + CodeGen code = new CodeGen("return 0;").driver(CodeGen.Phase.Select,cpu,"SystemV"); + Node target = PhiNode.make("target",TypeFunPtr.BOT,code._start); + target._type = TypeFunPtr.BOT; + CallNode ideal = new CallNode(null,code._start,null,target); + CallNode call = (CallNode)code._mach.instSelect(ideal); + // The last input is the call target, not another argument. + assertEquals("callr",((MachNode)call).op()); + RegMask mask = ((MachNode)call).regmap(call.nIns()-1); + assertTrue(mask.test(5)); + assertTrue(mask.test(6)); + if( cpu.equals("arm") ) IndirectCallTestSupport.checkArmEncoding(code,call); + // Machine operands can lose their ideal type (e.g. a spill reload). + // The call still needs the original signature for argument masks. + target._type = Type.BOTTOM; + assertSame(TypeFunPtr.BOT,call.tfp()); + } + } +} diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/ParseAllTest.java b/chapter28a/src/test/java/com/seaofnodes/simple/ParseAllTest.java new file mode 100644 index 00000000..f74ee7cd --- /dev/null +++ b/chapter28a/src/test/java/com/seaofnodes/simple/ParseAllTest.java @@ -0,0 +1,42 @@ +package com.seaofnodes.simple; + +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.ParseAllTestSupport; +import com.seaofnodes.simple.type.TypeInteger; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +public class ParseAllTest { + @Test public void testTypeOnlyFileHasNoConstructor() throws Exception { + Path base = Path.of("build/objs"); + Files.createDirectories(base); + Path root = Files.createTempDirectory(base,"type-only-"); + Files.createDirectory(root.resolve("A")); + Files.writeString(root.resolve("A/B.smp"),"int x = 0;"); + for( String src : new String[]{ + "B? b = null; return 0;", + "val readB = { B b -> 0; }; return 0;"} ) { + Files.writeString(root.resolve("A.smp"),src); + CodeGen code = new CodeGen(root.toString(),root.resolve("obj").toString(), + "A",null,123,true,TypeInteger.BOT).driver(CodeGen.Phase.TypeCheck); + assertEquals("0",Eval2.eval(code,0)); + } + } + + @Test public void testRequiredTypesBelongToTheirSource() throws Exception { + Path base = Path.of("build/objs"); + Files.createDirectories(base); + Path root = Files.createTempDirectory(base,"required-types-"); + Files.createDirectory(root.resolve("App")); + Files.writeString(root.resolve("App.smp"),"val marker = 0;"); + Files.writeString(root.resolve("App/Main.smp"),"return A.x+C.y;"); + Files.writeString(root.resolve("App/A.smp"),"int x = 0; val readB = { B b -> b.x; };"); + Files.writeString(root.resolve("App/B.smp"),"int x = 0;"); + Files.writeString(root.resolve("App/C.smp"),"int y = 0;"); + CodeGen code = new CodeGen(root.toString(),root.resolve("obj").toString(), + "App/Main",null,123,true,TypeInteger.BOT).parse(); + ParseAllTestSupport.checkRequiredTypes(code); + } +} diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/codegen/IndirectCallTestSupport.java b/chapter28a/src/test/java/com/seaofnodes/simple/codegen/IndirectCallTestSupport.java new file mode 100644 index 00000000..3c27036a --- /dev/null +++ b/chapter28a/src/test/java/com/seaofnodes/simple/codegen/IndirectCallTestSupport.java @@ -0,0 +1,17 @@ +package com.seaofnodes.simple.codegen; + +import com.seaofnodes.simple.node.*; +import static org.junit.Assert.*; + +public class IndirectCallTestSupport { + public static void checkArmEncoding(CodeGen code, CallNode call) { + Encoding enc = new Encoding(code) { + @Override public short reg(Node n) { + assertSame(call.fptr(),n); + return 7; + } + }; + ((MachNode)call).encoding(enc); + assertEquals(0xd63f00e0,enc.read4(0)); // BLR X7 + } +} diff --git a/chapter28a/src/test/java/com/seaofnodes/simple/codegen/ParseAllTestSupport.java b/chapter28a/src/test/java/com/seaofnodes/simple/codegen/ParseAllTestSupport.java new file mode 100644 index 00000000..2ce97a8e --- /dev/null +++ b/chapter28a/src/test/java/com/seaofnodes/simple/codegen/ParseAllTestSupport.java @@ -0,0 +1,16 @@ +package com.seaofnodes.simple.codegen; + +import static org.junit.Assert.*; + +public class ParseAllTestSupport { + public static void checkRequiredTypes(CodeGen code) { + CompUnit a = code._compunits.get("App/A"); + CompUnit b = code._compunits.get("App/B"); + CompUnit c = code._compunits.get("App/C"); + assertNotNull(b); + assertTrue(a._deps.find(b)!=-1); + // C was parsed after A, while B's layout was still unresolved. + // Only A used that layout; C must not inherit A's build dependency. + assertNull(c._deps); + } +} -- cgit v1.2.3