From d32f97238c903765dec4e9a9f3cbfa5fd12cded6 Mon Sep 17 00:00:00 2001 From: Cliff Click Date: Mon, 10 Aug 2026 11:11:18 -0700 Subject: Chapter 25: Modules, Separate Compilation, and SSA Construction Co-authored-by: Hsiang-Ying Fu <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Co-authored-by: Symious <14933944+symious@users.noreply.github.com> Co-authored-by: Jun Kawasaki Co-authored-by: Claude Opus 5 --- .../java/com/seaofnodes/simple/node/LoadNode.java | 182 ++++++++++++++------- 1 file changed, 126 insertions(+), 56 deletions(-) (limited to 'src/main/java/com/seaofnodes/simple/node/LoadNode.java') diff --git a/src/main/java/com/seaofnodes/simple/node/LoadNode.java b/src/main/java/com/seaofnodes/simple/node/LoadNode.java index b3204cff..a557f97c 100644 --- a/src/main/java/com/seaofnodes/simple/node/LoadNode.java +++ b/src/main/java/com/seaofnodes/simple/node/LoadNode.java @@ -2,9 +2,11 @@ package com.seaofnodes.simple.node; import com.seaofnodes.print.ExprPrinter; -import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.Parser; +import com.seaofnodes.simple.codegen.CodeGen; +import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.*; +import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; @@ -22,9 +24,14 @@ public class LoadNode extends MemOpNode { * @param ptr The ptr to the struct base from where we load a field * @param off The offset inside the struct base */ - public LoadNode(Parser.Lexer loc, String name, int alias, Type glb, Node mem, Node ptr, Node off) { - super(loc, name, alias, true, glb, mem, ptr, off); + public LoadNode(Parser.Lexer loc, String name, int alias, Node ctrl, Node mem, Node ptr, Node off) { + super(loc, name, alias, true, Type.BOTTOM, ctrl, mem, ptr, off); } + LoadNode( BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases ) { + super(bais,strs,types,fileAliases,aliases,true); + _con = Type.BOTTOM; // Serialized slot retained while MemOps share TypeNode + } + @Override public Tag serialTag() { return Tag.Load; } @Override boolean canDrop(MemOpNode other, Node dep) { return super.canDrop(other,dep) && !clobbered(dep); @@ -40,7 +47,7 @@ public class LoadNode extends MemOpNode { dep.addDepForwards(use); if( use instanceof StoreNode || use instanceof PhiNode || use instanceof MemMergeNode || - use instanceof NewNode || use instanceof CallNode ) return true; + use instanceof NewNode || use instanceof EscapeNode || use instanceof CallNode ) return true; } return false; } @@ -52,58 +59,102 @@ public class LoadNode extends MemOpNode { @Override public Type compute() { - Type tmem = mem()._type; - if( !(tmem instanceof TypeMem mem) ) - return tmem; // No memory yet? Assume TOP/BOT - assert !_declaredType.isFRef(); - // No lifting if ptr might null-check - Type tptr = ptr()._type; - if( err() != null ) - return _declaredType; - if( !(tptr instanceof TypeMemPtr tmp) ) - return tptr; // No pointer yet? Assume TOP/BOT + Type mem0 = mem()._type; + Type ptr0 = ptr()._type; + // Validate argument types + if( ptr0.isHigh() ) + return TypeScalar.TOP; + if( !(mem0 instanceof TypeMem mem) ) + return mem0.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; + if( mem._t == Type.TOP ) + return TypeScalar.TOP; + if( ptr0 == Type.NIL ) + return Type.BOTTOM; // Load from nil is error + if( ptr0 == Type.BOTTOM ) + return Type.BOTTOM; // Error in is error out + if( !(ptr0 instanceof TypeMemPtr tmp) ) + return scalar(mem._t); + if( tmp.nullable() ) + return Type.BOTTOM; // Load from nil-able is error + // Load field from object - Field f = tmp._obj.field(_name); + Field pfld = tmp._obj.field(_name); // No field? Open objects might yet get the field when falling; // closed objects with missing field are an error. - if( f == null ) - return tmp.isHigh() ? Type.TOP : _declaredType; - Type t = f._t; + if( pfld == null ) + return tmp.isHigh() || tmp._obj.isHigh() ? TypeScalar.TOP : TypeScalar.BOT; + + Type t = pfld._t; + boolean readonly = t instanceof TypeMemPtr view && view._ro; // Load member of constant array if( t instanceof TypeConAry ary ) t = ary.elem(); // TODO: if offset is known, can peek the constant - // Lift from declared type and memory input - t = t.join(MemMergeNode.contents(mem(),_alias,this)); - if( ptr()._type.isFinal() || _declaredType.isFinal() ) - t = t.makeRO(); // Deep final applied - // Pinch between declared type - t = t.join(_declaredType); - return t; + if( mem._alias==_alias ) + t = t.join(mem._t); + + // A deeply read-only base produces a deeply read-only value. The + // generic declared field type must not cast this information away. + if( ptr0.isFinal() || readonly ) + t = t.makeRO(); + return scalar(t); + } + + // Loads produce ordinary scalar values. Field/memory joins can still + // expose the enclosing global lattice bounds through an uninitialized + // Type.TOP/BOTTOM field; keep those results inside the scalar envelope. + private static Type scalar(Type t) { + return t==Type.TOP ? TypeScalar.TOP : t==Type.BOTTOM ? TypeScalar.BOT : t; } @Override public Node idealize() { - if( mem() instanceof CastNode cast ) { - setDef(1,cast.in(1)); + Node mem = mem(); + Node ptr = ptr(); + + // Null checks are handled by types. Array elements retain control for + // range checks, but reading an array's length has no index to check. + if( in(0)!=null && ptr._type instanceof TypeMemPtr tmp && + (!tmp._obj.isAry() || _name.equals("#")) ) { + setDef(0,null); return this; } - if( mem() instanceof MemMergeNode merge ) { - setDef(1,CodeGen.CODE.add(merge.alias(_alias))); + + // Forward-ref loads eventually sharpen to a declared type + Field fld; + if( ptr._type instanceof TypeMemPtr tmp && + (fld=tmp._obj.field(_name)) != null && + _alias != fld._alias) { + assert _alias==1 || _alias == fld._alias; + assert !tmp._obj._open && !tmp._obj._fref; + unlock(); // Alias participates in GVN semantics + _alias = fld._alias; return this; } - Node ptr = ptr(); - Node mem = mem(); + // Must sharpen alias first + if( _alias == 1 ) + return null; - Node win = find(mem,ptr,null,null); - if( win!=null ) return folded(win); + if( mem instanceof MemMergeNode merge ) { + setDef(1,merge.alias(_alias)); + // In support of uplifting control to a prior dominating load, if + // we move our memory, recheck other loads to see if they can hoist. + for( Node use : in(1).outs() ) + if( use instanceof LoadNode ld && use != this && ptr == ld.ptr() ) + CodeGen.CODE.add(ld); + + return this; + } // Uplift control to a prior dominating load. for( Node memuse : mem._outputs ) // Find a prior load, has same mem,ptr,off but higher ctrl if( memuse != this && memuse instanceof LoadNode ld && ptr==ld.ptr() && off()==ld.off() && - cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() ) // Higher control means load is legal earlier + cfg0()!=null && cfg0().domLCA(ld.cfg0(),this) == ld.cfg0() && // Higher control means load is legal earlier + addDep(ld)._type.isa(_type) ) // and not rolling backwards return ld; + Node win = find(mem,ptr,null,null); + if( win!=null ) return folded(win); if( ptr instanceof ReadOnlyNode ro ) ptr=ro.in(1); // Push a Load up through a Phi, as long as it collapses on at least @@ -127,13 +178,17 @@ public class LoadNode extends MemOpNode { !(ptr instanceof PhiNode p && p.region()==memphi.region()) ? memphi : null; if( find(memphi.in(2),ptr,stop,new BitSet())!=null || (!(memphi.region() instanceof LoopNode) && find(memphi.in(1),ptr,null,new BitSet())!=null) ) { - PhiNode phi = new PhiNode(_name,_declaredType,memphi.region()).keep(); - phi.setType(_type); // Backedge searches may return this value Phi. + // Integer loop Phis widen to i64. Keep the load's declared + // width at the result, as for ordinary typed loop variables. + if( _type instanceof TypeInteger && !declaredType().isa(_type) ) return null; + PhiNode phi = new PhiNode(_name,memphi.region()).keep(); + phi.setType(_type instanceof TypeInteger ? TypeInteger.BOT : _type); for( int i=1; i