package s3.services.simplejit.powerpc;

import ovm.util.ByteBuffer;
import ovm.util.HTString2int;
import s3.services.simplejit.CodeLinker;

/**
 * @author Hiroshi Yamauchi
 */
public class CodeLinkerImpl extends CodeLinker {

    public CodeLinkerImpl(int[] bytecodePC2NativePC) {
        super(bytecodePC2NativePC);
    }

    protected RelativeJump makeRelativeJump(int branchPC, int shift, int bits,
            int targetBytecodePC) {
        return new RelativeJumpImpl(branchPC, shift, bits, targetBytecodePC);
    }

    protected AbsoluteRef2BC makeAbsoluteRef2BC(int refererPC,
            int refereeBytecodePC) {
        throw new Error();
    }

    protected AbsoluteRef makeAbsoluteRef(int refererPC, int refereePC) {
        throw new Error();
    }

    protected Call makeCall(int callSitePC, String functionSymbol) {
        throw new Error();
    }

    private static void writeInt(byte[] data, int index, int value) {
        byte lowest = (byte) ((value >>> 0) & 0xff);
        byte low = (byte) ((value >>> 8) & 0xff);
        byte high = (byte) ((value >>> 16) & 0xff);
        byte highest = (byte) ((value >>> 24) & 0xff);
        data[index + 0] = highest;
        data[index + 1] = high;
        data[index + 2] = low;
        data[index + 3] = lowest;
    }

    private static int readInt(byte[] data, int index) {
        int lowest = (int)data[index + 3];
        int low = (int)data[index + 2];
        int high = (int)data[index + 1];
        int highest = (int)data[index + 0];
        return ((highest << 24) & 0xFF000000)
            | ((high << 16) & 0x00FF0000)
            | ((low << 8) & 0x0000FF00)
            | ((lowest << 0) & 0x000000FF);
    }
    
    
    public static class RelativeJumpImpl extends RelativeJump {
        private static void check_range(int value, int width) {
            int shifted = value >> (width - 1);
            if (shifted != 0 && shifted != -1) {
                throw new Error("Out-of-range relative jump : offset = " + value
				+ " width = " + width);
            }
        }
        private static int sign_mask(int value, int num_bits) {
            return value & ((1 << num_bits) - 1);
        }

        private RelativeJumpImpl(int branchPC, int shift, int bits,
                int targetBytecodePC) {
            super(branchPC, shift, bits, targetBytecodePC);
        }

        public void patch(byte[] code, int[] bytecodePC2NativePC) {
            int targetNativePC = bytecodePC2NativePC[targetBytecodePC];
            check_range((targetNativePC - branchPC) >> 2, bits);
            int value = readInt(code, branchPC);
            value &= ~(sign_mask(0xFFFFFFFF, bits) << shift);
            value |= sign_mask((targetNativePC - branchPC) >> 2, bits) << shift;
            writeInt(code, branchPC, value);
        }

        public void patch(ByteBuffer code, int[] bytecodePC2NativePC) {
            int targetNativePC = bytecodePC2NativePC[targetBytecodePC];
            check_range((targetNativePC - branchPC) >> 2, bits);
            int value = code.getInt(branchPC);
            value &= ~(sign_mask(0xFFFFFFFF, bits) << shift);
            value |= sign_mask((targetNativePC - branchPC) >> 2, bits) << shift;
            code.putInt(branchPC, value);
        }
    }
}
