summaryrefslogtreecommitdiff
path: root/chapter28a/src
diff options
context:
space:
mode:
authorCliff Click <cliffc@acm.org>2026-10-27 19:06:47 -0700
committerCliff Click <cliffc@acm.org>2026-10-27 19:06:47 -0700
commit19f9ea99114e9c1819239508f812d6521661f7bb (patch)
treedf8b80611f04faceb0e64880bb70eaf17a08c2b6 /chapter28a/src
parenteb9f6b86720c8a5c6c66e1c1837c65a4637aabee (diff)
downloadSimple-19f9ea99114e9c1819239508f812d6521661f7bb.tar.gz
Simple-19f9ea99114e9c1819239508f812d6521661f7bb.zip
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
Diffstat (limited to 'chapter28a/src')
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/Parser.java16
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/CodeGen.java8
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/codegen/ParseAll.java10
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/arm/CallRRARM.java9
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/riscv/CallRRISC.java5
-rw-r--r--chapter28a/src/main/java/com/seaofnodes/simple/node/cpus/x86_64_v2/CallRX86.java7
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/IndirectCallTest.java32
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/ParseAllTest.java42
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/codegen/IndirectCallTestSupport.java17
-rw-r--r--chapter28a/src/test/java/com/seaofnodes/simple/codegen/ParseAllTestSupport.java16
10 files changed, 135 insertions, 27 deletions
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<FRefNode> _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<String> _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 <clinit>, 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<String> 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);
+ }
+}