package s3.core.domain;

import ovm.core.repository.Selector;
import ovm.core.domain.Blueprint;
import ovm.core.domain.ObjectModel;
import ovm.core.domain.Oop;
import ovm.core.repository.RepositoryUtils;
import ovm.core.services.memory.JMTkOop;
import ovm.core.services.memory.VM_Address;
import ovm.core.stitcher.InvisibleStitcher;
import ovm.services.bytecode.JVMConstants.InvokeSystemArguments;
import ovm.services.monitors.Monitor;
import ovm.services.monitors.MonitorMapper;
import ovm.util.OVMError;
import ovm.util.UnsafeAccess;
import ovm.util.logging.Logger;
import s3.util.PragmaTransformCallsiteIR.BCdead;
import s3.util.PragmaTransformCallsiteIR.Rewriter;

/** Describes an object layout with one 32word for blueprint,
 *  one 32word for monitor, one 32word to serve as JMTk's 'available bits' word
 *  and one 32word for a hash value. These may be followed by zero or more bytes
 *  of additional JMTk state. The number of available bits (from zero up to all
 *  32 in the available bits word) and the number of bytes of extra state are
 *  parameters to the constructor.
 *  @author Chapman Flack
 **/
public class S3Model_B_M_J_H
extends ObjectModel implements JMTkOop.Model {

    private static final char BP_OFFSET = 0;
    public  static final char MONITOR_OFFSET = 4; // FIXME: public needed for JMTk hack
    private static final char JMTK_OFFSET = 8;
    private static final char HASH_OFFSET = 12;
    private static final char JMTK_EXTRA = 16; // space used here may be zero
    
    private final char numAvailableBits;
    private final char numBytesHeader; // size used at JMTK_EXTRA
    
    public S3Model_B_M_J_H( String availBits, String extraBytes) {
        int ab, eb;
        try {
            ab = Integer.decode( availBits).intValue();
            eb = Integer.decode( extraBytes).intValue();
        }
        catch ( NumberFormatException nfe ) {
            throw new InvisibleStitcher.MisconfiguredException(
                "S3Model_B_M_J_H parameters invalid", nfe);
        }
        
        if ( 0 > ab  ||  ab > 32 )
            throw new InvisibleStitcher.MisconfiguredException(
                "NUM_AVAILABLE_BITS("+ab+") should be in [0,32]");
        if ( 0 > eb  ||  eb > 65532  ||  0 != eb % 4 )
            throw new InvisibleStitcher.MisconfiguredException(
                "NUM_BYTES_HEADER("+eb+") should be nonnegative multiple of 4");
        if ( 8 < eb )
            Logger.global.warning(
                "NUM_BYTES_HEADER("+eb+") seems unreasonably large");
        numAvailableBits = (char)ab;
        numBytesHeader = (char)eb;
    }

    // the methods in this class should not be called directly (i.e. with
    // this class specified at the call site) but rather on the appropriate
    // interfaces or ObjectModel, where the pragmas giving the correct
    // runtime behavior are specified.  To catch perverse attempts to call
    // directly on this class, the methods here will all throw BCdead.

    public int NUM_AVAILABLE_BITS() throws BCdead {
        return numAvailableBits;
    }
    
    public int GC_HEADER_OFFSET() throws BCdead {
        // if it proves too troublesome to check this, we don't have to.
        if ( 0 == numBytesHeader )
            throw new InvisibleStitcher.MisconfiguredException(
                "GC_HEADER_OFFSET used in configuration with NUM_BYTES_HEADER 0");
        return JMTK_EXTRA;
    }
    
    public int NUM_BYTES_HEADER() throws BCdead {
        return numBytesHeader;
    }
    
    public VM_Address newInstance() throws BCdead { return new Proxy(); }
    
    public int headerSkipBytes() throws BCdead { return 16; }
    
    public int identityHash( Object o) throws BCdead
    { return System.identityHashCode(o); 
    }
    
    // monitor counts. blueprint doesn't (reachable through domain).
    // forward address doesn't (to-space copy is not yet in danger of collection!)
    public int maxReferences() throws BCdead { return 1; }
    
    public Oop stamp( VM_Address a, Blueprint bp) {
      	a.add(BP_OFFSET).setAddress(VM_Address.fromObject( bp));
	a.add(JMTK_OFFSET).setInt(0); // FIXME call VM_AllocatorHeader.initialize
	a.add(HASH_OFFSET).setInt(a.asInt());
	return super.stamp(a, bp);
    }
    
    /**Returns a string with a description of the header data structure in C.**/
    public String toCStruct() {
        return ("struct HEADER {\n"+
		"\tstruct s3_core_domain_S3Blueprint *_blueprint_;\n" +
                "\tstruct ovm_services_monitors_Monitor *_monitor_;\n" +
		"\tjint _jmtk_availbits_;\n" +
		"\tjint _hashcode_;\n" +
                ( 0 == numBytesHeader ? "" :
                    "\tjbyte _jmtk_bytes_[" + numBytesHeader + "];\n" ) +
		"};\n" +
		"#define HEADER_BLUEPRINT(H) " +
		"(((struct HEADER*)H)->_blueprint_)\n" +
		"#define HEADER_HASHCODE(H)  " +
		"(((struct HEADER*)H)->_hashcode_)\n");
    }
    public String toCxxStruct() {
	return ("struct HEADER {\n"+
		"\tstruct e_s3_core_domain_S3Blueprint *blueprint;\n"+
		"\tstruct e_java_lang_Object *monitor;\n"+
		"\tjint jmtk_availbits;\n" +
		"\tjint hashcode;\n" +
                ( 0 == numBytesHeader ? "" :
                    "\tjbyte jmtk_bytes[" + numBytesHeader + "];\n" ) +
		"};\n"+
		"#define HEADER_BLUEPRINT(H) ((H)->HEADER::blueprint)\n" +
		"#define HEADER_HASHCODE(H)  ((H)->HEADER::hashcode)\n");
    }

    public byte[] x86_getBlueprint(byte objectRegister,
				   byte blueprintRegister) {
	// X86 : mov 0(objectRegister) -> blueprintRegister
	return new byte[]
	    { (byte)0x8B, (byte)(0 | (blueprintRegister << 3) | objectRegister ) };
    }

    public int[] ppc_getBlueprint(int blueprintRegister, int objectRegister) {
        // PPC : lwz blueprintRegister <- 0(objectRegister)
        return new int[] { ((32 << 26) | (blueprintRegister << 21) | (objectRegister << 16) | (0 << 0)) };
    }
    
    private static class Proxy
    extends ObjectModel.Proxy
    implements Oop.WithUpdate, MonitorMapper, JMTkOop, UnsafeAccess {
        // Oop.WithUpdate:
        public void updateBlueprint( Blueprint newValue){
            throw new OVMError.Unimplemented( "updateBlueprint at build time");
        }
    
        public void updateReference( int k, Oop newValue){
            throw new OVMError.Unimplemented( "updateReference at build time");
        }

        // MonitorMapper:
        public Monitor getMonitor() {
	    throw new OVMError.Unimplemented( "getMonitor at build time");
	}

	public void setMonitor(Monitor monitor) {
	    throw new OVMError.Unimplemented( "setMonitor at build time");
	}
	
	public void releaseMonitor() {
	    throw new OVMError.Unimplemented( "releaseMonitor at build time");
	}
    
        // JMTkOop:
        public boolean attemptAvailableBits(int oldVal, int newVal)
        throws BCdead {
            throw new OVMError.Unimplemented(
                "attemptAvailableBits at build time");
        }

        public VM_Address objectStartRef() throws BCdead {
            return this;
        }

        public int prepareAvailableBits() throws BCdead {
            throw new OVMError.Unimplemented(
                "prepareAvailableBits at build time");
        }

        public int readAvailableBitsWord() throws BCdead {
            throw new OVMError.Unimplemented(
                "readAvailableBitsWord at build time");
        }

        public void setAvailableBit(int idx, boolean flag)
        throws BCdead {
            throw new OVMError.Unimplemented(
                "setAvailableBit at build time");
        }

        public boolean testAvailableBit(int idx) throws BCdead {
            throw new OVMError.Unimplemented(
                "testAvailableBit at build time");
        }

        public void writeAvailableBitsWord(int val) throws BCdead {
            throw new OVMError.Unimplemented(
                "writeAvailableBitsWord at build time");
        }
    }

    protected void init() throws BCdead {
        initOop( BP_OFFSET, HASH_OFFSET);
        S3Model_B_M.initGetReference1( MONITOR_OFFSET);
        S3Model_B_M.initMonitorMapper( MONITOR_OFFSET);
	S3Model_B_M_F_H.initUpdate1( BP_OFFSET, MONITOR_OFFSET);
        initJMTkOop( JMTK_OFFSET);
        initJMTkOopModel( numAvailableBits, numBytesHeader, JMTK_EXTRA);
    }
    
    protected static void initOop( final char bpOffset, final char hashOffset) {
        S3Model_B_M.initOop( bpOffset);
        rewrite( "getHash:()I",
            new Rewriter() {
                protected boolean rewrite() {
                    cursor.addQuickOpcode(GETFIELD_QUICK, hashOffset);
		    addFiat();
                    return true;
                }
            });        
    }
    
    protected static void initJMTkOop( final char availWordOffset)
    throws BCdead {
        final Selector.Method vaai = // VM_Address.add(i)
            RepositoryUtils.methodSelectorFor(
                "Lovm/core/services/memory/VM_Address;",
                "add:(I)Lovm/core/services/memory/VM_Address;");

        rewrite(JMTkOop.ID,
            "objectStartRef:()Lovm/core/services/memory/VM_Address;",
            new Rewriter() {
                protected boolean rewrite() {
                    addFiat();
                    return true; // delete original instruction
                }
            });
        rewrite( "testAvailableBit:(I)Z",
            new Rewriter() {
                protected boolean rewrite() {           // oop idx
                    cursor.addLoadConstant( 1);         // oop idx 1
                    cursor.addSimpleInstruction( SWAP); // oop 1 idx
                    cursor.addSimpleInstruction( ISHL); // oop mask
                    cursor.addSimpleInstruction( SWAP); // mask oop
                    cursor.addQuickOpcode(GETFIELD_QUICK,availWordOffset);
                    cursor.addSimpleInstruction( IAND); // 0|mask
                    addTrailingIntNe0();
                    return true; // delete original instruction
                }
            });
        rewrite( "setAvailableBit:(IZ)V",
            new Rewriter() {
                // Requires no branches, but depends on flg being a proper
                // boolean (0 or 1 only)!
                protected boolean rewrite() {   // oop idx flg
                    cursor.addSimpleInstruction( SWAP); // oop flg idx
                    cursor.addSimpleInstruction( DUP_X1);   // oop idx flg idx
                    cursor.addSimpleInstruction( ISHL); // oop idx |msk
                    cursor.addSimpleInstruction( SWAP); // oop |msk idx
                    cursor.addLoadConstant( -1);        // oop |msk idx -1
                    cursor.addSimpleInstruction( SWAP); // oop |msk -1 idx
                    cursor.addSimpleInstruction( ISHL); // oop |msk 1+&msk
                    cursor.addLoadConstant( -1);        // oop |msk 1+&msk -1
                    cursor.addSimpleInstruction( IADD); // oop |msk &msk
                    cursor.addRoll( (char)3, (byte)-1); // |msk &msk oop
                    cursor.addSimpleInstruction( DUP_X2);// oop |msk &msk oop
                    cursor.addQuickOpcode(GETFIELD_QUICK,availWordOffset);
                    cursor.addSimpleInstruction( IAND); // oop |msk w&msk
                    cursor.addSimpleInstruction( IOR);  // oop new
                    cursor.addQuickOpcode(PUTFIELD_QUICK,availWordOffset);
                    return true; // delete original instruction
                }
            });
        Rewriter rabw = new Rewriter() {
            protected boolean rewrite() { // oop
                cursor.addQuickOpcode(GETFIELD_QUICK,availWordOffset);
                return true; 
            }
        };
        rewrite( "readAvailableBitsWord:()I", rabw);
        rewrite( "writeAvailableBitsWord:(I)V",
            new Rewriter() {
                protected boolean rewrite() { // oop val
                    cursor.addQuickOpcode(PUTFIELD_QUICK,availWordOffset);
                    return true; // delete original instruction
                }
            });
        rewrite( "prepareAvailableBits:()I", rabw); // same as readAv..BitsWord
        rewrite( "attemptAvailableBits:(II)Z",
            new Rewriter() {
                protected boolean rewrite() { // oop expected new
                    cursor.addRoll( (char)3, (byte)-1); // exp new oop
                    cursor.addLoadConstant( availWordOffset); // exp new oop off
                    cursor.addINVOKEVIRTUAL( vaai); // exp new adr
                    cursor.addRoll( (char)3, (byte)1);  // adr exp new
                    cursor.addINVOKESYSTEM( (byte)InvokeSystemArguments.CAS32);
                    return true; // delete original instruction
                }
            });
    }
    
    protected static void initJMTkOopModel( final char numAvailableBits,
                                            final char numBytesHeader,
                                            final char gcHeaderOffset) { 
        rewrite( JMTkOop.Model.ID, "NUM_AVAILABLE_BITS:()I",
            new Rewriter() {
                protected boolean rewrite() {
                    cursor.addSimpleInstruction( POP);
                    cursor.addLoadConstant( numAvailableBits);
                    return true; // delete original instruction
                }
            });
        rewrite( "NUM_BYTES_HEADER:()I",
            new Rewriter() {
                protected boolean rewrite() {
                    cursor.addSimpleInstruction( POP);
                    cursor.addLoadConstant( numBytesHeader);
                    return true; // delete original instruction
                }
            });
        rewrite( "GC_HEADER_OFFSET:()I",
            new Rewriter() {
                protected boolean rewrite() {
                    if ( 0 < numBytesHeader ) {
                        cursor.addSimpleInstruction( POP);
                        cursor.addLoadConstant( gcHeaderOffset);
                        return true; // delete original instruction
                    }
                    // if it proves too troublesome to check this, then don't
                    InvisibleStitcher.MisconfiguredException me =
                    new InvisibleStitcher.MisconfiguredException(
                        "GC_HEADER_OFFSET used in configuration with " +
                        "NUM_BYTES_HEADER 0");
                    PuntException pe = new PuntException();
                    pe.initCause( me);
                    throw pe;
                }
            });
    }
}
