package com.seaofnodes.simple.node; import com.seaofnodes.simple.codegen.CodeGen; import com.seaofnodes.simple.codegen.GlobalBits; import com.seaofnodes.simple.type.Type; import com.seaofnodes.simple.type.TypeMem; import com.seaofnodes.simple.util.BAOS; import com.seaofnodes.simple.util.Utils; import java.util.BitSet; import java.util.HashMap; import java.util.IdentityHashMap; /** * A Phi for bulk memory. It covers all aliases except {@link #_aliases}; * those aliases must be represented by parallel precise MemPhis at the same * program slice. */ public class BulkMemPhiNode extends PhiNode { private static final BitSet EMPTY = new BitSet(); // Excluded Aliases; empty means NO exclusions. // Directly visible by design. Empty sets share EMPTY; splitAlias performs // copy-on-first-write and removes the node from GVN before mutation. public BitSet _aliases; public BulkMemPhiNode(String label, Node... inputs) { this(label,EMPTY,inputs); } private BulkMemPhiNode(String label, BitSet aliases, Node... inputs) { super(label, inputs); _aliases = aliases; } public BulkMemPhiNode(BulkMemPhiNode phi) { super(phi,phi._label); _aliases = phi._aliases.isEmpty() ? EMPTY : (BitSet)phi._aliases.clone(); } public BulkMemPhiNode(RegionNode r, Node sample) { super(r,sample); _aliases = EMPTY; } public boolean isSplit(int alias) { return _aliases.get(alias); } @Override public Tag serialTag() { return Tag.BulkMemPhi; } @Override public void packed(BAOS baos, HashMap strs, HashMap types, IdentityHashMap anodes) { super.packed(baos,strs,types,anodes); baos.packed2(_aliases.cardinality()); for( int alias = _aliases.nextSetBit(0); alias >= 0; alias = _aliases.nextSetBit(alias+1) ) baos.packed2(alias); } static Node make(BAOS bais, String[] strs, Type[] types, GlobalBits fileAliases, GlobalBits aliases) { Node[] ins = new Node[bais.packed1()]; String label = strs[bais.packed2()]; BitSet bits = new BitSet(); for( int i=bais.packed2(); i>0; i-- ) { int alias = bais.packed2(); if( alias >= GlobalBits.RESERVED ) alias = aliases.map(fileAliases,alias); bits.set(alias); } return new BulkMemPhiNode(label,bits.isEmpty() ? EMPTY : bits,ins); } @Override public Type compute() { Type t = super.compute(); return t instanceof TypeMem mem ? mem : t==Type.BOTTOM ? TypeMem.BOT : TypeMem.TOP; } @Override public Node idealize() { Node progress = super.idealize(); if( progress != null ) return progress; if( ((RegionNode)region()).inProgress() || region().nIns()<=1 ) return null; // Input is in-progress // "Peek through" a MemMerge that covers this alias set on its default for( int i=1; i= mmm.nIns() || mmm.in(alias)==null) ) return false; return true; } // True if the MemMerge has no precise slice still covered by this Phi. private boolean canPeek(MemMergeNode mmm) { return missingAlias(mmm,false)==0; } // Return an alias required by an input but still covered by this Phi, or 0. private int inputAlias(Node n) { return switch(n) { case ParmNode p -> { assert p._idx==1; yield 0; } case ProjNode proj -> 0; case ConstantNode con -> { assert !(con._con instanceof TypeMem tmem && tmem._alias != 1); yield 0; } case MemMergeNode mmm -> missingAlias(mmm,false); case MemOpNode mem -> unsplit(mem._alias); case BulkMemPhiNode bulk -> missingAlias(bulk); default -> throw Utils.TODO("Should not reach here"); }; } // Return an alias required by a user but still covered by this Phi, or 0. private int outputAlias(Node use) { // This decision depends backwards on an immediate user's alias/set. // Normal graph-neighbor enqueueing flows from defs to uses, so retain // an explicit forward dependency to revisit this Phi when the user // sharpens. use = addDepForwards(use); return switch(use) { case ScopeNode scope -> 0; case ParmNode parm -> 0; case MemMergeNode mmm -> missingAlias(mmm,true); case BulkMemPhiNode bulk -> missingAlias(bulk); case MemOpNode mem -> unsplit(mem._alias); case MemPhiNode phi -> unsplit(phi._alias); case EscapeNode esc -> esc.pub()==this ? unsplit(esc.fld()._alias) : 0; default -> { assert use instanceof ReturnNode || use instanceof CallNode : "Unexpected bulk-memory user "+use.getClass().getSimpleName()+"#"+use._nid+": "+use; yield 0; } }; } // Alias zero/one is bulk, and an already excluded precise alias cannot // trigger another split of this Phi. private int unsplit(int alias) { if( alias <= 1 ) return 0; assert !_aliases.get(alias); return alias; } // First alias excluded by the neighboring bulk Phi but not by this one. private int missingAlias(BulkMemPhiNode bulk) { for( int alias = bulk._aliases.nextSetBit(0); alias >= 0; alias = bulk._aliases.nextSetBit(alias+1) ) if( !_aliases.get(alias) ) return alias; return 0; } // First precise MemMerge slice still covered by this Phi. When inspecting // a user, ignore slots which point back to this Phi. private int missingAlias(MemMergeNode mmm, boolean user) { for( int alias=2; alias= 0; old = _aliases.nextSetBit(old+1) ) { assert old != alias; mmm.alias(old,precisePhi(old));} mmm.alias(alias,precise); assert checkMerge(mmm); mmm.init(); // Slicing changes representation, not the set of values in memory. // Newly exposed precise inputs can carry conservative bulk escape // summaries; do not let those invent escapes absent from this Phi. TypeMem old = (TypeMem)_type; TypeMem mem = (TypeMem) mmm._type; mmm._type = TypeMem.make(mem._alias,mem._t,mem._one,mem._clz, mem._final,old._escFs,old._escAs); return mmm; } // Find or make the precise Phi parallel to this bulk Phi at the same merge. private Node precisePhi(int alias) { MemPhiNode mphi = _findPhi(alias); if( mphi!=null ) return mphi; mphi = new MemPhiNode("$"+alias,alias); mphi.addDef(region()); // Due to cycles, must set before calling peephole mphi.setType(TypeMem.BOT.makeFrom(alias)); for( int i=1; i= 0; alias = bulk._aliases.nextSetBit(alias+1) ) if( alias >= mmm.nIns() || mmm.in(alias)==null ) return false; return true; } @Override public boolean eq(Node n) { return _aliases.equals(((BulkMemPhiNode)n)._aliases) && super.eq(n); } @Override int hash() { return _aliases.hashCode(); } }