text:
package com.thesett.aima.logic.fol.wam.compiler;

import java.nio.ByteBuffer;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.thesett.aima.logic.fol.Functor;
import com.thesett.aima.logic.fol.FunctorName;
import com.thesett.aima.logic.fol.LinkageException;
import com.thesett.aima.logic.fol.Term;
import com.thesett.aima.logic.fol.VariableAndFunctorInterner;
import com.thesett.aima.logic.fol.wam.machine.WAMCodeView;
import com.thesett.aima.logic.fol.wam.machine.WAMMachine;
import com.thesett.common.util.Pair;
import com.thesett.common.util.Sizeable;
import com.thesett.common.util.SizeableLinkedList;
import com.thesett.common.util.SizeableList;
import com.thesett.common.util.doublemaps.SymbolKey;

/**
 * WAMInstruction provides a structured in-memory representation of the WAM instruction set, as well as utilities to
 * emmit the instructions as byte code, disassemble them back into the structured representation, and pretty print them.
 *
 *
Instructions implement {@link Sizeable} reporting their length in bytes as their 'size of'.
 *
 *

CRC Card

 *
 Responsibilities  Collaborations
 *
 Provide the translation of the WAM instruction set down to bytes.
 *
 Provide dissasembly of byte encoded instructions back into structured instructions.
 *
 Calculate the length of an instruction in bytes.  {@link com.thesett.common.util.Sizeable}
 *

 *
 * @author Rupert Smith
 */
public class WAMInstruction implements Sizeable
{
    /** Instruction to write out a struc onto the heap. */
    public static final byte PUT_STRUC = 0x01;

    /** The instruction to set a register as a variable. */
    public static final byte SET_VAR = 0x02;

    /** The instruction to set a register to a heap location. */
    public static final byte SET_VAL = 0x03;

    /** The instruction to compare a register to a structure on the heap. */
    public static final byte GET_STRUC = 0x04;

    /** The instruction to unify a register with a variable. */
    public static final byte UNIFY_VAR = 0x05;

    /** The instruction to unify a register with a location on the heap. */
    public static final byte UNIFY_VAL = 0x06;

    /** The instruction to copy a heap location into an argument register. */
    public static final byte PUT_VAR = 0x07;

    /** The instruction to copy a register into an argument register. */
    public static final byte PUT_VAL = 0x08;

    /** The instruction to unify an argument register with a variable. */
    public static final byte GET_VAR = 0x09;

    /** The instruction to unify a register with a location on the heap. */
    public static final byte GET_VAL = 0x0a;

    /** The instruction to copy a constant into an argument register. */
    public static final byte PUT_CONST = 0x12;

    /** The instruction to compare or bind a reference from a register to a constant. */
    public static final byte GET_CONST = 0x13;

    /** The instruction to write a constant onto the heap. */
    public static final byte SET_CONST = 0x14;

    /** The instruction to unify the heap with a constant. */
    public static final byte UNIFY_CONST = 0x15;

    /** The instruction to copy a list pointer into an argument register. */
    public static final byte PUT_LIST = 0x16;

    /** The instruction to compare or bind a reference from a register to a list pointer. */
    public static final byte GET_LIST = 0x17;

    /** The instruction to write an anonymous variable onto the heap. */
    public static final byte SET_VOID = 0x18;

    /** The instruction to unify with anonymous variables on the heap. */
    public static final byte UNIFY_VOID = 0x19;

    /** The instruction to initialize an argument register, ensuring the stack variable it references is globalized. */
    public static final byte PUT_UNSAFE_VAL = 0x1c;

    /** The instruction to set a register, ensuring the stack variable it references is globalized. */
    public static final byte SET_LOCAL_VAL = 0x1d;

    /** The instruction to unify a register, ensuring any stack variable it references is globalized. */
    public static final byte UNIFY_LOCAL_VAL = 0x1e;

    /** The instruction to call a predicate. */
    public static final byte CALL = 0x0b;

    /** The instruction to tail call a predicate. */
    public static final byte EXECUTE = 0x1a;

    /** The instruction to return from a called predicate. */
    public static final byte PROCEED = 0x0c;

    /** The stack frame allocation instruction. */
    public static final byte ALLOCATE = 0x1b;

    /** The stack frame allocation instruction for queries. */
    public static final byte ALLOCATE_N = 0x0d;

    /** The stack frame de-allocation instruction. */
    public static final byte DEALLOCATE = 0x0e;

    /** The first clause try instruction. */
    public static final byte TRY_ME_ELSE = 0x0f;

    /** The middle clause retry instruction. */
    public static final byte RETRY_ME_ELSE = 0x10;

    /** The final clause trust or fail instruction. */
    public static final byte TRUST_ME = 0x11;

    /** The first clause indexing try instruction. */
    public static final byte TRY = 0x1f;

    /** The middle clause indexing retry instruction. */
    public static final byte RETRY = 0x20;

    /** The final clause indexing trust or fail instruction. */
    public static final byte TRUST = 0x21;

    /** The second level indexing instruction. */
    public static final byte SWITCH_ON_TERM = 0x22;

    /** The third level indexing instruction for constants. */
    public static final byte SWITCH_ON_CONST = 0x23;

    /** The third level indexing instruction for structures. */
    public static final byte SWITCH_ON_STRUC = 0x24;

    /** The simpler neck-cut instruction. */
    public static final byte NECK_CUT = 0x25;

    /** The get level instruction for cuts. */
    public static final byte GET_LEVEL = 0x26;

    /** The full deep cut instruction. */
    public static final byte CUT = 0x27;

    /** The continue instruction for inline choice points. */
    public static final byte CONTINUE = 0x28;

    /** The no-op instruction, useful for inserting labels into the code. */
    public static final byte NO_OP = 0x29;

    /** The internal call instruction for intrinsics. */
    public static final byte CALL_INTERNAL = 0x2a;

    /** The suspend operation. */
    public static final byte SUSPEND = 0x7f;

    // === Defines the addressing modes.

    /** Used to specify addresses relative to registers. */
    public static final byte REG_ADDR = 0x01;

    /** Used to specify addresses relative to the current stack frame. */
    public static final byte STACK_ADDR = 0x02;

    // === Defines the heap cell marker types.

    /** Indicates a reference data type. */
    public static final byte REF = 0x00;

    /** Indicates a structure data type. */
    public static final byte STR = 0x01;

    /** Indicates a constant atom data type. */
    public static final byte CON = 0x02;

    /** Indicates a list data type. */
    public static final byte LIS = 0x03;

    /** Defines the L0 virtual machine instruction set as constants. */
    public enum WAMInstructionSet
    {
        /** Instruction to write out a struc onto the heap. */
        PutStruc(PUT_STRUC, "put_struc", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to set a register as a variable. */
        SetVar(SET_VAR, "set_var", 3, 0x3),

        /** The instruction to set a register to a heap location. */
        SetVal(SET_VAL, "set_val", 3, 0x3),

        /** The instruction to set a register, ensuring the stack variable it references is globalized. */
        SetLocalVal(SET_LOCAL_VAL, "set_local_val", 3, 0x3),

        /** The instruction to compare a register to a structure on the heap. */
        GetStruc(GET_STRUC, "get_struc", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to unify a register with a variable. */
        UnifyVar(UNIFY_VAR, "unify_var", 3, 0x3),

        /** The instruction to unify a register with a location on the heap. */
        UnifyVal(UNIFY_VAL, "unify_val", 3, 0x3),

        /** The instruction to unify a register, ensuring any stack variable it references is globalized. */
        UnifyLocalVal(UNIFY_LOCAL_VAL, "unify_local_val", 3, 0x3),

        /** The instruction to copy a heap location into an argument register. */
        PutVar(PUT_VAR, "put_var", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to copy a register into an argument register. */
        PutVal(PUT_VAL, "put_val", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /**
         * The instruction to initialize an argument register, ensuring the stack variable it references is globalized.
         */
        PutUnsafeVal(PUT_UNSAFE_VAL, "put_unsafe_val", 4, 0x7)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to unify an argument register with a variable. */
        GetVar(GET_VAR, "get_var", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to unify a register with a location on the heap. */
        GetVal(GET_VAL, "get_val", 4, 0x7)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Reg2(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1Reg2(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Reg2(pretty, instruction);
            }
        },

        /** The instruction to copy a constant into an argument register. */
        PutConstant(PUT_CONST, "put_const", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to compare or bind a reference from a register to a constant. */
        GetConstant(GET_CONST, "get_const", 7, 0xb)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1Fn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeReg1Fn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringReg1Fn(pretty, instruction);
            }
        },

        /** The instruction to write a constant onto the heap. */
        SetConstant(SET_CONST, "set_const", 5, 0x8)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleFn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeFn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringFn(pretty, instruction);
            }
        },

        /** The instruction to unify the heap with a constant. */
        UnifyConstant(UNIFY_CONST, "unify_const", 5, 0x8)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleFn(codeBuf, ip, instruction, interner);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                emmitCodeFn(codeBuf, code, instruction, machine);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return toStringFn(pretty, instruction);
            }
        },

        /** The instruction to copy a list pointer into an argument register. */
        PutList(PUT_LIST, "put_list", 3, 0x3),

        /** The instruction to compare or bind a reference from a register to a list pointer. */
        GetList(GET_LIST, "get_list", 3, 0x3),

        /** The instruction to write an anonymous variable onto the heap. */
        SetVoid(SET_VOID, "set_void", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The instruction to unify with anonymous variables on the heap. */
        UnifyVoid(UNIFY_VOID, "unify_void", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The instruction to call a predicate. */
        Call(CALL, "call", 7, 0xa)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int entryPoint = codeBuf.getInt(ip + 1);
                int arity = codeBuf.get(ip + 5);
                instruction.reg1 = codeBuf.get(ip + 6);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int toCall = machine.internFunctorName(instruction.fn);

                WAMCallPoint callPointToCall = machine.resolveCallPoint(toCall);

                // Ensure that a valid call point was returned, otherwise a linkage error has occurred.
                if (callPointToCall == null)
                {
                    throw new LinkageException("Could not resolve call to " + instruction.fn + ".", null, null,
                        "Unable to resolve call to " + instruction.fn.getName() + "/" + instruction.fn.getArity() +
                        ".");
                }

                int entryPoint = callPointToCall.entryPoint;

                codeBuf.put(code);
                codeBuf.putInt(entryPoint);
                codeBuf.put((byte) instruction.fn.getArity());
                codeBuf.put(instruction.reg1);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " +
                    ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "") +
                    (", " + instruction.reg1);
            }
        },

        /** The instruction to tail call a predicate. */
        Execute(EXECUTE, "execute", 6, 0x8)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int entryPoint = codeBuf.getInt(ip + 1);
                int arity = codeBuf.get(ip + 5);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int toCall = machine.internFunctorName(instruction.fn);

                WAMCallPoint callPointToCall = machine.resolveCallPoint(toCall);

                // Ensure that a valid call point was returned, otherwise a linkage error has occurred.
                if (callPointToCall == null)
                {
                    throw new LinkageException("Could not resolve call to " + instruction.fn + ".", null, null,
                        "Unable to resolve call to " + instruction.fn.getName() + "/" + instruction.fn.getArity() +
                        ".");
                }

                int entryPoint = callPointToCall.entryPoint;

                codeBuf.put(code);
                codeBuf.putInt(entryPoint);
                codeBuf.put((byte) instruction.fn.getArity());
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " +
                    ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "");
            }
        },

        /** The instruction to return from a called predicate. */
        Proceed(PROCEED, "proceed", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The stack frame allocation instruction. */
        Allocate(ALLOCATE, "allocate", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The stack frame allocation instruction for queries. */
        AllocateN(ALLOCATE_N, "allocate_n", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The stack frame deallocation instruction. */
        Deallocate(DEALLOCATE, "deallocate", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The first clause try instruction. */
        TryMeElse(TRY_ME_ELSE, "try_me_else", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Intern the alternative forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The middle clause retry instruction. */
        RetryMeElse(RETRY_ME_ELSE, "retry_me_else", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The final clause trust or fail instruction. */
        TrustMe(TRUST_ME, "trust_me", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The first clause indexing try instruction. */
        Try(TRY, "try", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                if (instruction.label != null)
                {
                    int label = machine.internFunctorName(instruction.label);
                    machine.resolveLabelPoint(label, ip);
                }

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The middle clause indexing retry instruction. */
        Retry(RETRY, "retry", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                if (instruction.label != null)
                {
                    int label = machine.internFunctorName(instruction.label);
                    machine.resolveLabelPoint(label, ip);
                }

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The final clause indexing trust or fail instruction. */
        Trust(TRUST, "trust", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                if (instruction.label != null)
                {
                    int label = machine.internFunctorName(instruction.label);
                    machine.resolveLabelPoint(label, ip);
                }

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The second level indexing instruction. */
        SwitchOnTerm(SWITCH_ON_TERM, "switch_on_term", 17, 0x0)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward labels, and write them out as zero initially, for later completion.
                int jump = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(jump, ip + 1);

                jump = machine.internFunctorName(instruction.target2);
                machine.reserveReferenceToLabel(jump, ip + 5);

                jump = machine.internFunctorName(instruction.target3);
                machine.reserveReferenceToLabel(jump, ip + 9);

                jump = machine.internFunctorName(instruction.target4);
                machine.reserveReferenceToLabel(jump, ip + 13);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + labelToString(instruction.target1) + " / " + labelToString(instruction.target2) +
                    " / " + labelToString(instruction.target3) + " / " + labelToString(instruction.target4);
            }
        },

        /** The third level indexing instruction for constants. */
        SwitchOnConst(SWITCH_ON_CONST, "switch_on_const", 9, 0x0)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward labels, and write them out as zero initially, for later completion.
                int jump = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(jump, ip + 1);

                jump = machine.internFunctorName(instruction.target2);
                machine.reserveReferenceToLabel(jump, ip + 5);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + labelToString(instruction.target1) + " / " + labelToString(instruction.target2);
            }
        },

        /** The third level indexing instruction for structures. */
        SwitchOnStruc(SWITCH_ON_STRUC, "switch_on_struc", 9, 0x0)
        {
            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                // Intern the alternative forward labels, and write them out as zero initially, for later completion.
                int jump = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(jump, ip + 1);

                jump = machine.internFunctorName(instruction.target2);
                machine.reserveReferenceToLabel(jump, ip + 5);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + labelToString(instruction.target1) + " / " + labelToString(instruction.target2);
            }
        },

        /** The simpler neck-cut instruction. */
        NeckCut(NECK_CUT, "neck_cut", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */

            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The get level instruction for cuts. */
        GetLevel(GET_LEVEL, "get_level", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The full deep cut instruction. */
        Cut(CUT, "cut", 2, 0x2)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                disassembleReg1NoMode(codeBuf, ip, instruction);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                emmitCodeReg1NoMode(codeBuf, code, instruction);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " + instruction.reg1;
            }
        },

        /** The continue instruction for inline choice points. */
        Continue(CONTINUE, "continue", 5, 0x10)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int label = codeBuf.getInt(ip + 1);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int ip = codeBuf.position();

                // Intern the forward label, and write it out as zero initially, for later completion.
                int toCall = machine.internFunctorName(instruction.target1);
                machine.reserveReferenceToLabel(toCall, ip + 1);

                codeBuf.put(code);
                codeBuf.putInt(0);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                WAMLabel label = instruction.target1;

                return pretty + " " + ((label != null) ? label.toPrettyString() : "");
            }
        },

        /** The no-op instruction, useful for inserting labels into the code. */
        NoOp(NO_OP, "no_op", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                int ip = codeBuf.position();

                // Resolve any forward reference to the label for this instruction.
                int label = machine.internFunctorName(instruction.label);
                machine.resolveLabelPoint(label, ip);

                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        },

        /** The instruction to call a predicate. */
        CallInternal(CALL_INTERNAL, "call_internal", 7, 0xa)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                int entryPoint = codeBuf.getInt(ip + 1);
                int arity = codeBuf.get(ip + 5);
                instruction.reg1 = codeBuf.get(ip + 6);
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
                throws LinkageException
            {
                int toCall = machine.internFunctorName(instruction.fn);

                WAMCallPoint callPointToCall = machine.resolveCallPoint(toCall);

                // Ensure that a valid call point was returned, otherwise a linkage error has occurred.
                if (callPointToCall == null)
                {
                    throw new LinkageException("Could not resolve internal call to " + instruction.fn + ".", null, null,
                        "Unable to resolve internal call to " + instruction.fn.getName() + "/" +
                        instruction.fn.getArity() + ".");
                }

                int entryPoint = callPointToCall.entryPoint;

                codeBuf.put(code);
                codeBuf.putInt(entryPoint);
                codeBuf.put((byte) instruction.fn.getArity());
                codeBuf.put(instruction.reg1);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty + " " +
                    ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "") +
                    (", " + instruction.reg1);
            }
        },

        /** The suspend on success instruction. */
        Suspend(SUSPEND, "suspend", 1, 0x0)
        {
            /** {@inheritDoc} */
            protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
                VariableAndFunctorInterner interner)
            {
                // Do nothing as this instruction takes no arguments.
            }

            /** {@inheritDoc} */
            public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            {
                codeBuf.put(code);
            }

            /** {@inheritDoc} */
            public String toString(WAMInstruction instruction)
            {
                return pretty;
            }
        };

        /** Holds a mapping of the instruction by byte code. */
        private static final Map<Byte, WAMInstructionSet> codeToValue = new HashMap<Byte, WAMInstructionSet>();

        static
        {
            for (WAMInstructionSet instruction : EnumSet.allOf(WAMInstructionSet.class))
            {
                codeToValue.put(instruction.code, instruction);
            }
        }

        /** Holds the byte representation of the instruction. */
        protected byte code;

        /** Holds the human readable form of the instruction. */
        protected String pretty;

        /** Holds the length of the instruction in bytes. */
        protected int length;

        /**
         * Holds a bit mask defining which argument fields this op-code uses. The correspondence between bits and fields
         * is:
         *
         *

Bits to Fields

         *
 Bit  Field
         *
  0   {@link WAMInstruction#mode1}
         *
  1   {@link WAMInstruction#reg1}
         *
  2   {@link WAMInstruction#reg2}
         *
  3   {@link WAMInstruction#fn}
         *
  4   {@link WAMInstruction#target1}
         *

         */
        protected int fieldMask;

        /**
         * Creates a new L0 instruction with the specified byte code.
         *
         * @param i      The byte code for the instruction.
         * @param pretty The human readable form of the instruction.
         * @param length The length of the instruction plus arguments in bytes.
         */
        private WAMInstructionSet(byte i, String pretty, int length, int fieldMask)
        {
            this.code = i;
            this.pretty = pretty;
            this.length = length;
            this.fieldMask = fieldMask;
        }

        /**
         * Creates an instruction from a byte code.
         *
         * @param  code The byte coded form of the instruction.
         *
         * @return An instruction matching the byte code, or <tt>null <tt>if no instruction matches the code.
         */
        public static WAMInstructionSet fromCode(byte code)
        {
            return codeToValue.get(code);
        }

        /**
         * Provides the base name of register 1, "X" for register addressing, "Y" for stack mode addressing.
         *
         * @param  instruction The instruction to get the base register name from;
         *
         * @return The base name of register 1, "X" for register addressing, "Y" for stack mode addressing.
         */
        public static String reg1Name(WAMInstruction instruction)
        {
            return (instruction.mode1 == STACK_ADDR) ? "Y" : "X";
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param  instruction The instruction, including its arguments.
         * @param  codeBuf     The code buffer to write to.
         * @param  machine     The binary machine to write the code into.
         *
         * @throws LinkageException If required symbols to link to cannot be found in the binary machine.
         */
        public void emmitCode(WAMInstruction instruction, ByteBuffer codeBuf, WAMMachine machine)
            throws LinkageException
        {
            emmitCodeReg1(codeBuf, code, instruction);
        }

        /**
         * Provides the field mask describing which argument fields this instruction uses.
         *
         * @return The field mask describing which argument fields this instruction uses.
         */
        public int getFieldMask()
        {
            return fieldMask;
        }

        /**
         * Provides the human readable form of the instruction.
         *
         * @return The human readable form of the instruction.
         */
        public String getPretty()
        {
            return pretty;
        }

        /**
         * Prints the human readable form of the instruction for debugging purposes.
         *
         * @param  instruction The instruction, including its arguments.
         *
         * @return The human readable form of the instruction for debugging purposes.
         */
        public String toString(WAMInstruction instruction)
        {
            return toStringReg1(pretty, instruction);
        }

        /**
         * Gets the byte coded representation of the instruction.
         *
         * @return The byte coded representation of the instruction.
         */
        public byte getCode()
        {
            return code;
        }

        /**
         * Calculates the length of this instruction in bytes.
         *
         * @return The length of this instruction in bytes.
         */
        public int length()
        {
            return length;
        }

        /**
         * Disassembles the arguments of an instruction. This is a default implementation to handle the most common
         * case; instructions with one register argument.
         *
         * @param instruction The instruction, including its arguments.
         * @param ip          The start of the instruction within the buffer.
         * @param codeBuf     The code buffer.
         * @param interner    The interner to look up function and variable names against.
         */
        protected void disassembleArguments(WAMInstruction instruction, int ip, ByteBuffer codeBuf,
            VariableAndFunctorInterner interner)
        {
            disassembleReg1(codeBuf, ip, instruction);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         */
        private static void emmitCodeReg1(ByteBuffer codeBuf, byte code, WAMInstruction instruction)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.mode1);
            codeBuf.put(instruction.reg1);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         */
        private static void emmitCodeReg1NoMode(ByteBuffer codeBuf, byte code, WAMInstruction instruction)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.reg1);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         */
        private static void emmitCodeReg1Reg2(ByteBuffer codeBuf, byte code, WAMInstruction instruction)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.mode1);
            codeBuf.put(instruction.reg1);
            codeBuf.put(instruction.reg2);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         * @param interner    The binary machine to write the code into.
         */
        private static void emmitCodeReg1Fn(ByteBuffer codeBuf, byte code, WAMInstruction instruction,
            VariableAndFunctorInterner interner)
        {
            codeBuf.put(code);
            codeBuf.put(instruction.mode1);
            codeBuf.put(instruction.reg1);

            int arity = instruction.fn.getArity() << 24;
            int name = interner.internFunctorName(instruction.fn) & 0x00ffffff;
            codeBuf.putInt(arity | name);
        }

        /**
         * Writes out the instruction plus arguments in the byte code format to the specified location within a code
         * buffer.
         *
         * @param codeBuf     The code buffer to write to.
         * @param code        The instruction mnemonic.
         * @param instruction The instruction, including its arguments.
         * @param interner    The binary machine to write the code into.
         */
        private static void emmitCodeFn(ByteBuffer codeBuf, byte code, WAMInstruction instruction, VariableAndFunctorInterner interner)
        {
            codeBuf.put(code);

            int arity = instruction.fn.getArity() << 24;
            int name = interner.internFunctorName(instruction.fn) & 0x00ffffff;
            codeBuf.putInt(arity | name);
        }

        /**
         * Helper print function that prints an instruction with one register argument.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringReg1(String pretty, WAMInstruction instruction)
        {
            return pretty + " " + reg1Name(instruction) + instruction.reg1;
        }

        /**
         * Helper print function that prints an instruction with two register arguments.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringReg1Reg2(String pretty, WAMInstruction instruction)
        {
            return pretty + " " + reg1Name(instruction) + instruction.reg1 + ", A" + instruction.reg2;
        }

        /**
         * Helper print function that prints an instruction with one register argument and a functor reference.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringReg1Fn(String pretty, WAMInstruction instruction)
        {
            return pretty + " " +
                ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "") + ", A" +
                instruction.reg1;
        }

        /**
         * Helper print function that prints an instruction with one functor reference.
         *
         * @param  pretty      The pretty printed instruction mnenomic.
         * @param  instruction The instruction data.
         *
         * @return A pretty printed instruction.
         */
        private static String toStringFn(String pretty, WAMInstruction instruction)
        {
            return pretty + " " +
                ((instruction.fn != null) ? (instruction.fn.getName() + "/" + instruction.fn.getArity()) : "");
        }

        /**
         * Disassembles the arguments to an instruction that takes one register and one functor reference.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         * @param interner    The interner to look up function and variable names against.
         */
        private static void disassembleReg1Fn(ByteBuffer codeBuf, int ip, WAMInstruction instruction,
            VariableAndFunctorInterner interner)
        {
            instruction.mode1 = codeBuf.get(ip + 1);
            instruction.reg1 = codeBuf.get(ip + 2);

            int fn = codeBuf.getInt(ip + 3);
            int f = fn & 0x00ffffff;
            instruction.fn = interner.getDeinternedFunctorName(f);
        }

        /**
         * Disassembles the arguments to an instruction that takes one functor reference.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         * @param interner    The interner to look up function and variable names against.
         */
        private static void disassembleFn(ByteBuffer codeBuf, int ip, WAMInstruction instruction,
            VariableAndFunctorInterner interner)
        {
            int fn = codeBuf.getInt(ip + 1);
            int f = fn & 0x00ffffff;
            instruction.fn = interner.getDeinternedFunctorName(f);
        }

        /**
         * Disassembles the arguments to an instruction that takes one register argument.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         */
        private static void disassembleReg1(ByteBuffer codeBuf, int ip, WAMInstruction instruction)
        {
            instruction.mode1 = codeBuf.get(ip + 1);
            instruction.reg1 = codeBuf.get(ip + 2);
        }

        /**
         * Disassembles the arguments to an instruction that takes one register argument and no mode flag.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         */
        private static void disassembleReg1NoMode(ByteBuffer codeBuf, int ip, WAMInstruction instruction)
        {
            instruction.reg1 = codeBuf.get(ip + 1);
        }

        /**
         * Disassembles the arguments to an instruction that takes one register argument.
         *
         * @param codeBuf     The code buffer to disassemble from.
         * @param ip          The instruction pointer within the code buffer.
         * @param instruction The instruction to store the disassembles arguments in.
         */
        private static void disassembleReg1Reg2(ByteBuffer codeBuf, int ip, WAMInstruction instruction)
        {
            instruction.mode1 = codeBuf.get(ip + 1);
            instruction.reg1 = codeBuf.get(ip + 2);
            instruction.reg2 = codeBuf.get(ip + 3);
        }
    }

    /** The optional address label of the instruction. */
    protected WAMLabel label;

    /** The instruction. */
    protected WAMInstructionSet mnemonic;

    /** Holds the addressing mode of the first register argument to the instruction. */
    protected byte mode1;

    /** Holds the first register argument to the instruction. */
    protected byte reg1;

    /** Holds the second register argument to the instruction. */
    protected byte reg2;

    /** Holds the functor argument to the instruction. */
    protected FunctorName fn;

    /** An optional target address label. */
    protected WAMLabel target1;

    /** An optional target address label. */
    protected WAMLabel target2;

    /** An optional target address label. */
    protected WAMLabel target3;

    /** An optional target address label. */
    protected WAMLabel target4;

    /** Holds the symbol key of the argument that is held in the first register of this instruction. */
    private SymbolKey symbolKeyReg1;

    /** Holds the functor name of the argument that is assigned to the first register of this instruction. */
    private Integer functorNameReg1;

    /** An optional list of entries for an indexing table. */
    protected List<Pair<Integer, WAMLabel>> indexTable;

    /**
     * Creates an instruction for the specified mnemonic.
     *
     * @param mnemonic The instruction mnemonic.
     */
    public WAMInstruction(WAMInstructionSet mnemonic)
    {
        this.mnemonic = mnemonic;
    }

    /**
     * Creates an instruction with the mnemonic resolved from its byte encoded form.
     *
     * @param code The byte encoded form of the instruction mnemonic.
     */
    public WAMInstruction(byte code)
    {
        this.mnemonic = WAMInstructionSet.fromCode(code);
    }

    /**
     * Creates an instruction for the specified mnemonic that takes one register and one functor argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The register argument.
     * @param fn       The functor argument.
     * @param reg1Term The term that is assigned or associated with reg1.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, FunctorName fn, Term reg1Term)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
        this.fn = fn;

        // Record the symbol keys of the term that resulted in the creation of the instruction, and are associated
        // with reg1 of the instruction.
        symbolKeyReg1 = reg1Term.getSymbolKey();
        functorNameReg1 = reg1Term.isFunctor() ? ((Functor) reg1Term).getName() : null;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes one register and one functor argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The register argument.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, FunctorName fn)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
        this.fn = fn;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes two register arguments.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the first register argument.
     * @param reg1     The first register argument.
     * @param reg2     The second register argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, byte reg2)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
        this.reg2 = reg2;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single register argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The single register argument.
     * @param reg1Term The term that is assigned or associated with reg1.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1, Term reg1Term)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;

        // Record the symbol keys of the term that resulted in the creation of the instruction, and are associated
        // with reg1 of the instruction.
        symbolKeyReg1 = reg1Term.getSymbolKey();
        functorNameReg1 = reg1Term.isFunctor() ? ((Functor) reg1Term).getName() : null;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single register argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param mode1    The addressing mode to use with the register argument.
     * @param reg1     The single register argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte mode1, byte reg1)
    {
        this.mnemonic = mnemonic;
        this.mode1 = mode1;
        this.reg1 = reg1;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single register argument, and no addressing mode,
     * for situations where the addressing mode is already inferred by the instruction type.
     *
     * @param mnemonic The instruction mnemonic.
     * @param reg1     The single register argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte reg1)
    {
        this.mnemonic = mnemonic;
        this.reg1 = reg1;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single functor argument.
     *
     * @param mnemonic The instruction mnemonic.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, FunctorName fn)
    {
        this.mnemonic = mnemonic;
        this.fn = fn;
    }

    /**
     * Creates an instruction for the specified mnemonic that takes a single functor argument and a single register.
     *
     * @param mnemonic The instruction mnemonic.
     * @param reg1     The single register argument.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMInstructionSet mnemonic, byte reg1, FunctorName fn)
    {
        this.mnemonic = mnemonic;
        this.reg1 = reg1;
        this.fn = fn;
    }

    /**
     * Creates a labelled instruction for the specified mnemonic that takes a single functor argument.
     *
     * @param label    The instructions address label.
     * @param mnemonic The instruction mnemonic.
     * @param fn       The functor argument.
     */
    public WAMInstruction(WAMLabel label, WAMInstructionSet mnemonic, WAMLabel fn)
    {
        this.label = label;
        this.mnemonic = mnemonic;
        this.target1 = fn;
    }

    /**
     * Creates a labelled instruction for the specified mnemonic.
     *
     * @param label    The instructions address label.
     * @param mnemonic The instruction mnemonic.
     */
    public WAMInstruction(WAMLabel label, WAMInstructionSet mnemonic)
    {
        this.label = label;
        this.mnemonic = mnemonic;
    }

    /**
     * Disassembles the instructions from the specified byte buffer, starting at a given location (ip). An interner for
     * the functor names encountered in the instruction buffer must also be supplied, in order to look up the functor
     * names by encoded value.
     *
     * @param  start    The start offset into the buffer to disassemble.
     * @param  length   The length of data in the buffer to disassemble.
     * @param  codeBuf  The code buffer.
     * @param  interner The interner to look up interned names with.
     * @param  codeView A view onto the machines code buffer.
     *
     * @return A list of instructions disassembles from the code buffer.
     */
    public static SizeableList<WAMInstruction> disassemble(int start, int length, ByteBuffer codeBuf,
        VariableAndFunctorInterner interner, WAMCodeView codeView)
    {
        SizeableList<WAMInstruction> result = new SizeableLinkedList<WAMInstruction>();
        int ip = start;

        while (ip < (start + length))
        {
            // Decode the instruction and its arguments.
            byte iCode = codeBuf.get(ip);

            WAMInstruction instruction = new WAMInstruction(iCode);
            instruction.mnemonic.disassembleArguments(instruction, ip, codeBuf, interner);

            // Restore any label on the instruction.
            Integer label = codeView.getNameForAddress(ip);

            if (label != null)
            {
                FunctorName name = interner.getDeinternedFunctorName(label);

                if (name instanceof WAMLabel)
                {
                    instruction.label = (WAMLabel) name;
                }
            }

            result.add(instruction);

            ip += instruction.mnemonic.length();
        }

        return result;
    }

    /**
     * Gets the instruction mnemonic.
     *
     * @return The instruction mnemonic.
     */
    public WAMInstructionSet getMnemonic()
    {
        return mnemonic;
    }

    /**
     * Gets the addressing mode of the first register argument to the instruction.
     *
     * @return The addressing mode of the first register argument to the instruction.
     */
    public byte getMode1()
    {
        return mode1;
    }

    /**
     * Gets the first register to which the instruction applies.
     *
     * @return The first register to which the instruction applies.
     */
    public byte getReg1()
    {
        return reg1;
    }

    /**
     * Sets the first register to which the instruction applies.
     *
     * @param reg1 The first register to which the instruction applies.
     */
    public void setReg1(byte reg1)
    {
        this.reg1 = reg1;
    }

    /**
     * Gets the second register to which the instruction applies.
     *
     * @return The second register to which the instruction applies.
     */
    public byte getReg2()
    {
        return reg2;
    }

    /**
     * Sets the second register to which the instruction applies.
     *
     * @param reg2 The second register to which the instruction applies.
     */
    public void setReg2(byte reg2)
    {
        this.reg2 = reg2;
    }

    /**
     * Provides the symbol key of the argument that is assigned to the first register of this instruction.
     *
     * @return The symbol key, or <tt>null</tt> if none was associated with reg1.
     */
    public SymbolKey getSymbolKeyReg1()
    {
        return symbolKeyReg1;
    }

    /**
     * Provides the functor name of the argument that is assigned to the first register of this instruction.
     *
     * @return The functor name, or <tt>null</tt> if none was associated with reg1.
     */
    public Integer getFunctorNameReg1()
    {
        return functorNameReg1;
    }

    /**
     * Gets the functor argument, if any, to which the instruction applies.
     *
     * @return The functor argument to which the instruction applies, or <tt>null <tt>if there is none.
     */
    public FunctorName getFn()
    {
        return fn;
    }

    /**
     * Provides the target label argument, if any, to which the instruction applies.
     *
     * @return The target label argument to which the instruction applies, or <tt>null <tt>if there is none.
     */
    public WAMLabel getTarget1()
    {
        return target1;
    }

    /**
     * Sets the functor argument to which the instruction applies.
     *
     * @param fn The functor argument to which the instruction applies.
     */
    public void setFn(FunctorName fn)
    {
        this.fn = fn;
    }

    /**
     * Gets the label for the instruction, if any is set.
     *
     * @return The label for the instruction, or <tt>null</tt> if none is set.
     */
    public WAMLabel getLabel()
    {
        return label;
    }

    /**
     * Writes out the instruction plus arguments in the byte code format to the specified location within a code buffer.
     *
     * @param  codeBuffer The code buffer to write to.
     * @param  machine    The binary machine to write the code into.
     *
     * @throws LinkageException If required symbols to link to cannot be found in the binary machine.
     */
    public void emmitCode(ByteBuffer codeBuffer, WAMMachine machine) throws LinkageException
    {
        mnemonic.emmitCode(this, codeBuffer, machine);
    }

    /**
     * Calculates the length of this instruction in bytes.
     *
     * @return The length of this instruction in bytes.
     */
    public long sizeof()
    {
        return mnemonic.length();
    }

    /**
     * Prints the human readable form of the instruction for debugging purposes.
     *
     * @return The human readable form of the instruction for debugging purposes.
     */
    public String toString()
    {
        return mnemonic.toString(this);
    }

    /**
     * Pretty prints a label as a string.
     *
     * @param  label The label to print.
     *
     * @return A pretty version of the label.
     */
    private static String labelToString(WAMLabel label)
    {
        return ((label != null) ? label.toPrettyString() : "");
    }
}


text:
package com.facebook.buck.shell;

import com.facebook.buck.step.AbstractExecutionStep;
import com.facebook.buck.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.util.ProjectFilesystem;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Set;

/**
 * {@link Step} that takes a collection of entries in a directory and creates a set of read-only
 * symlinks (with the same structure) to the original entries in a new directory.
 */
public class SymlinkFilesIntoDirectoryStep extends AbstractExecutionStep {

  private final Path srcDir;
  private final ImmutableSet<String> entries;
  private final Path outDir;

  /**
   * @param srcDir relative to the project root that contains the {@code entries}.
   * @param entries that exist in {@code srcDir}.
   * @param outDir relative to the project root where the symlinks will be created.
   */
  public SymlinkFilesIntoDirectoryStep(Path srcDir, Set<String> entries, Path outDir) {
    super("symlinking files into " + outDir);
    this.srcDir = Preconditions.checkNotNull(srcDir);
    this.entries = ImmutableSet.copyOf(entries);
    this.outDir = Preconditions.checkNotNull(outDir);
  }

  @Override
  public int execute(ExecutionContext context) {
    // Note that because these paths are resolved to absolute paths, the symlinks will be absolute
    // paths, as well.
    ProjectFilesystem projectFilesystem = context.getProjectFilesystem();
    Path outDir = projectFilesystem.resolve(this.outDir);
    Path srcDir = projectFilesystem.resolve(this.srcDir);

    for (String entry : entries) {
      Path link = outDir.resolve(entry);
      Path target = srcDir.resolve(entry);
      try {
        Files.createDirectories(link.getParent());
        Files.createSymbolicLink(link, target);
      } catch (IOException e) {
        context.logError(e, "Failed to create symlink from %s to %s.", link, target);
        return 1;
      }
    }
    return 0;
  }

}

redpajama_set_name: RedPajamaGithub


text:
#include "tensorflow/lite/delegates/gpu/cl/tensor.h"

#include <cstdint>
#include <cstring>
#include <memory>

#include "absl/strings/str_cat.h"
#include "tensorflow/lite/delegates/gpu/cl/buffer.h"
#include "tensorflow/lite/delegates/gpu/cl/cl_image_format.h"
#include "tensorflow/lite/delegates/gpu/common/data_type.h"
#include "tensorflow/lite/delegates/gpu/common/status.h"
#include "tensorflow/lite/delegates/gpu/common/task/tensor_desc.h"
#include "tensorflow/lite/delegates/gpu/common/task/texture2d_desc.h"

namespace tflite {
namespace gpu {
namespace cl {
namespace {
absl::Status AllocateTensorMemory(const CLContext& context, const BHWDC& shape,
                                  const TensorDescriptor& descriptor,
                                  const void* data_ptr, CLMemory* result) {
  const int slices = DivideRoundUp(shape.c, 4);
  cl_mem_flags mem_flags = CL_MEM_READ_WRITE;
  if (data_ptr) {
    mem_flags |= CL_MEM_COPY_HOST_PTR;
  }
  switch (descriptor.storage_type) {
    case TensorStorageType::BUFFER:
    case TensorStorageType::IMAGE_BUFFER: {
      const size_t data_size = shape.b * shape.w * shape.h * shape.d * slices *
                               4 * SizeOf(descriptor.data_type);
      cl_int error_code;
      cl_mem memory = clCreateBuffer(context.context(), mem_flags, data_size,
                                     const_cast<void*>(data_ptr), &error_code);
      if (!memory) {
        return absl::UnknownError(
            absl::StrCat("Failed to allocate device memory (clCreateBuffer): ",
                         CLErrorCodeToString(error_code)));
      }
      *result = CLMemory(memory, true);
      return absl::OkStatus();
    }
    case TensorStorageType::TEXTURE_2D: {
      cl_image_desc desc;
      desc.image_type = CL_MEM_OBJECT_IMAGE2D;
      desc.image_width = shape.w * shape.b * shape.d;
      desc.image_height = shape.h * slices;
      desc.image_depth = 0;
      desc.image_row_pitch = 0;
      desc.image_slice_pitch = 0;
      desc.num_mip_levels = 0;
      desc.num_samples = 0;
      desc.buffer = nullptr;

      cl_image_format format;
      format.image_channel_order = CL_RGBA;
      format.image_channel_data_type =
          DataTypeToChannelType(descriptor.data_type);

      cl_int error_code;
      cl_mem memory =
          CreateImage2DLegacy(context.context(), mem_flags, &format, &desc,
                              const_cast<void*>(data_ptr), &error_code);
      if (error_code != CL_SUCCESS) {
        return absl::UnknownError(
            absl::StrCat("Failed to create 2D texture (clCreateImage): ",
                         CLErrorCodeToString(error_code)));
      }

      *result = CLMemory(memory, true);
      return absl::OkStatus();
    }
    case TensorStorageType::TEXTURE_3D: {
      cl_image_desc desc;
      desc.image_type = CL_MEM_OBJECT_IMAGE3D;
      desc.image_width = shape.w * shape.b;
      desc.image_height = shape.h;
      desc.image_depth = slices * shape.d;
      desc.image_row_pitch = 0;
      desc.image_slice_pitch = 0;
      desc.num_mip_levels = 0;
      desc.num_samples = 0;
      desc.buffer = nullptr;

      cl_image_format format;
      format.image_channel_order = CL_RGBA;
      format.image_channel_data_type =
          DataTypeToChannelType(descriptor.data_type);

      cl_int error_code;
      cl_mem memory =
          CreateImage3DLegacy(context.context(), mem_flags, &format, &desc,
                              const_cast<void*>(data_ptr), &error_code);
      if (error_code != CL_SUCCESS) {
        return absl::UnknownError(
            absl::StrCat("Failed to create 3D texture (clCreateImage): ",
                         CLErrorCodeToString(error_code)));
      }

      *result = CLMemory(memory, true);
      return absl::OkStatus();
    }
    case TensorStorageType::TEXTURE_ARRAY: {
      cl_image_desc desc;
      desc.image_type = CL_MEM_OBJECT_IMAGE2D_ARRAY;
      desc.image_width = shape.w * shape.b;
      desc.image_height = shape.h;
      desc.image_depth = 0;
      desc.image_array_size = slices * shape.d;
      desc.image_row_pitch = 0;
      desc.image_slice_pitch = 0;
      desc.num_mip_levels = 0;
      desc.num_samples = 0;
      desc.buffer = nullptr;

      cl_image_format format;
      format.image_channel_order = CL_RGBA;
      format.image_channel_data_type =
          DataTypeToChannelType(descriptor.data_type);

      cl_int error_code;
      cl_mem memory =
          clCreateImage(context.context(), mem_flags, &format, &desc,
                        const_cast<void*>(data_ptr), &error_code);
      if (error_code != CL_SUCCESS) {
        return absl::UnknownError(
            absl::StrCat("Failed to create 2D texture array (clCreateImage): ",
                         CLErrorCodeToString(error_code)));
      }

      *result = CLMemory(memory, true);
      return absl::OkStatus();
    }

    case TensorStorageType::SINGLE_TEXTURE_2D: {
      if (slices != 1) {
        return absl::InvalidArgumentError(absl::StrCat(
            "SINGLE_TEXTURE_2D support only channels in range [1-4], but ",
            shape.c, "was provided"));
      }
      cl_image_desc desc;
      desc.image_type = CL_MEM_OBJECT_IMAGE2D;
      desc.image_width = shape.w * shape.b * shape.d;
      desc.image_height = shape.h;
      desc.image_depth = 0;
      desc.image_row_pitch = 0;
      desc.image_slice_pitch = 0;
      desc.num_mip_levels = 0;
      desc.num_samples = 0;
      desc.buffer = nullptr;

      cl_image_format format;
      if (context.IsFloatTexture2DSupported(shape.c, descriptor.data_type)) {
        format.image_channel_order = ToChannelOrder(shape.c);
        format.image_channel_data_type =
            DataTypeToChannelType(descriptor.data_type);
      } else {
        return absl::InvalidArgumentError(absl::StrCat(
            "This device doesn't support ", shape.c, "-channel textures."));
      }

      cl_int error_code;
      cl_mem memory =
          CreateImage2DLegacy(context.context(), mem_flags, &format, &desc,
                              const_cast<void*>(data_ptr), &error_code);
      if (error_code != CL_SUCCESS) {
        return absl::UnknownError(
            absl::StrCat("Failed to create single 2D texture (clCreateImage): ",
                         CLErrorCodeToString(error_code)));
      }

      *result = CLMemory(memory, true);
      return absl::OkStatus();
    }

    default:
      return absl::InternalError("Unsupported tensor storage type");
  }
}

absl::Status CreateImageBufferFromBuffer(const CLContext& context,
                                         cl_mem memory, DataType data_type,
                                         int width, cl_mem* result) {
  cl_image_format format;
  cl_image_desc desc;
  std::memset(&desc, 0, sizeof(desc));
  desc.image_type = CL_MEM_OBJECT_IMAGE1D_BUFFER;
  desc.image_width = width;
  desc.mem_object = memory;

  format.image_channel_data_type = DataTypeToChannelType(data_type);
  format.image_channel_order = CL_RGBA;

  cl_int error_code;
  *result = clCreateImage(context.context(), CL_MEM_READ_WRITE, &format, &desc,
                          nullptr, &error_code);
  if (error_code != CL_SUCCESS) {
    return absl::UnknownError(
        absl::StrCat("Failed to create Image from Buffer (clCreateImage): ",
                     CLErrorCodeToString(error_code)));
  }
  return absl::OkStatus();
}

absl::Status CreateImage2DFromBuffer(const CLContext& context, cl_mem memory,
                                     DataType data_type, int width, int height,
                                     int channels, int row_bytes_alignment,
                                     cl_mem* result) {
  if (!context.IsFloatTexture2DSupported(channels, data_type)) {
    return absl::InvalidArgumentError(absl::StrCat(
        "This device doesn't support ", channels, "-channel textures."));
  }

  cl_image_desc desc;
  desc.image_type = CL_MEM_OBJECT_IMAGE2D;
  desc.image_width = width;
  desc.image_height = height;
  desc.image_depth = 0;
  if (row_bytes_alignment == 0) {
    desc.image_row_pitch = 0;
  } else {
    const size_t bytes_per_row = width * channels * SizeOf(data_type);
    desc.image_row_pitch = AlignByN(bytes_per_row, row_bytes_alignment);
  }
  desc.image_slice_pitch = 0;
  desc.num_mip_levels = 0;
  desc.num_samples = 0;
  desc.mem_object = memory;

  cl_image_format format;
  format.image_channel_order = ToChannelOrder(channels);
  format.image_channel_data_type = DataTypeToChannelType(data_type);

  cl_int error_code;
  *result = CreateImage2DLegacy(context.context(), CL_MEM_READ_WRITE, &format,
                                &desc, nullptr, &error_code);
  if (error_code != CL_SUCCESS) {
    return absl::UnknownError(
        absl::StrCat("Failed to create Image2D from Buffer (clCreateImage): ",
                     CLErrorCodeToString(error_code)));
  }
  return absl::OkStatus();
}

absl::Status CreateTensor(const CLContext& context, const BHWDC& shape,
                          const TensorDescriptor& descriptor, cl_mem memory,
                          Tensor* result) {
  const bool memory_owner = memory == nullptr;
  if (memory_owner) {
    CLMemory mem;
    RETURN_IF_ERROR(
        AllocateTensorMemory(context, shape, descriptor, nullptr, &mem));
    memory = mem.Release();
  }
  if (descriptor.storage_type == TensorStorageType::IMAGE_BUFFER) {
    cl_mem image_memory;
    RETURN_IF_ERROR(CreateImageBufferFromBuffer(
        context, memory, descriptor.data_type,
        shape.b * shape.w * shape.h * shape.d * DivideRoundUp(shape.c, 4),
        &image_memory));
    *result = Tensor(memory, memory_owner, image_memory, shape, descriptor);
  } else {
    *result = Tensor(memory, memory_owner, shape, descriptor);
  }
  return absl::OkStatus();
}

absl::Status CreateTensorShared(const CLContext& context, const BHWDC& shape,
                                const TensorDescriptor& descriptor,
                                cl_mem memory, Tensor* result) {
  const bool memory_owner = false;
  if (descriptor.storage_type == TensorStorageType::IMAGE_BUFFER) {
    cl_mem image_memory;
    RETURN_IF_ERROR(CreateImageBufferFromBuffer(
        context, memory, descriptor.data_type,
        shape.b * shape.w * shape.h * shape.d * DivideRoundUp(shape.c, 4),
        &image_memory));
    *result = Tensor(memory, memory_owner, image_memory, shape, descriptor);
  } else {
    *result = Tensor(memory, memory_owner, shape, descriptor);
  }
  return absl::OkStatus();
}

}  // namespace

Tensor::Tensor(cl_mem memory, bool memory_owner, const BHWC& shape,
               const TensorDescriptor& descriptor)
    : memory_(memory),
      image_buffer_memory_(nullptr),
      memory_owner_(memory_owner),
      shape_(shape.b, shape.h, shape.w, 1, shape.c),
      descriptor_(descriptor) {}

Tensor::Tensor(cl_mem memory, bool memory_owner, const BHWDC& shape,
               const TensorDescriptor& descriptor)
    : memory_(memory),
      image_buffer_memory_(nullptr),
      memory_owner_(memory_owner),
      shape_(shape),
      descriptor_(descriptor) {}

Tensor::Tensor(cl_mem memory, bool memory_owner, cl_mem image_buffer_memory,
               const BHWC& shape, const TensorDescriptor& descriptor)
    : memory_(memory),
      image_buffer_memory_(image_buffer_memory),
      memory_owner_(memory_owner),
      shape_(shape.b, shape.h, shape.w, 1, shape.c),
      descriptor_(descriptor) {
  if (image_buffer_memory &&
      (descriptor.storage_type == TensorStorageType::TEXTURE_2D ||
       descriptor.storage_type == TensorStorageType::SINGLE_TEXTURE_2D)) {
    buffer_based_ = true;
  }
}

Tensor::Tensor(cl_mem memory, bool memory_owner, cl_mem image_buffer_memory,
               const BHWDC& shape, const TensorDescriptor& descriptor)
    : memory_(memory),
      image_buffer_memory_(image_buffer_memory),
      memory_owner_(memory_owner),
      shape_(shape),
      descriptor_(descriptor) {
  if (image_buffer_memory &&
      (descriptor.storage_type == TensorStorageType::TEXTURE_2D ||
       descriptor.storage_type == TensorStorageType::SINGLE_TEXTURE_2D)) {
    buffer_based_ = true;
  }
}

Tensor::Tensor(Tensor&& tensor)
    : memory_(tensor.memory_),
      image_buffer_memory_(tensor.image_buffer_memory_),
      memory_owner_(tensor.memory_owner_),
      buffer_based_(tensor.buffer_based_),
      shape_(tensor.shape_),
      descriptor_(tensor.descriptor_) {
  tensor.memory_ = nullptr;
  tensor.image_buffer_memory_ = nullptr;
}

Tensor& Tensor::operator=(Tensor&& tensor) {
  if (this != &tensor) {
    Release();
    std::swap(memory_, tensor.memory_);
    std::swap(image_buffer_memory_, tensor.image_buffer_memory_);
    std::swap(memory_owner_, tensor.memory_owner_);
    std::swap(buffer_based_, tensor.buffer_based_);
    std::swap(shape_, tensor.shape_);
    std::swap(descriptor_, tensor.descriptor_);
  }
  return *this;
}

void Tensor::Release() {
  // image_buffer_memory_ always owned by object
  if (image_buffer_memory_) {
    clReleaseMemObject(image_buffer_memory_);
    image_buffer_memory_ = nullptr;
  }
  if (memory_owner_ && memory_) {
    clReleaseMemObject(memory_);
    memory_ = nullptr;
  }
}

absl::Status Tensor::GetGPUResources(const GPUObjectDescriptor* obj_ptr,
                                     GPUResourcesWithValue* resources) const {
  const auto* buffer_desc = dynamic_cast<const BufferDescriptor*>(obj_ptr);
  if (buffer_desc) {
    if (descriptor_.storage_type != TensorStorageType::BUFFER) {
      return absl::InvalidArgumentError(
          "Tensor can be used with BufferDescriptor only with "
          "TensorStorageType::BUFFER.");
    }
    resources->buffers.push_back({"buffer", memory_});
    return absl::OkStatus();
  }
  const auto* texture2d_desc =
      dynamic_cast<const Texture2DDescriptor*>(obj_ptr);
  if (texture2d_desc) {
    if (descriptor_.storage_type != TensorStorageType::TEXTURE_2D) {
      return absl::InvalidArgumentError(
          "Tensor can be used with Texture2DDescriptor only with "
          "TensorStorageType::TEXTURE_2D.");
    }
    cl_mem mem = buffer_based_ ? image_buffer_memory_ : memory_;
    resources->images2d.push_back({"tex2d", mem});
    return absl::OkStatus();
  }
  const auto* tensor_desc = dynamic_cast<const TensorDescriptor*>(obj_ptr);
  if (!tensor_desc) {
    return absl::InvalidArgumentError("Expected TensorDescriptor on input.");
  }
  resources->ints.push_back(
      {"slice_stride", tensor_desc->GetSliceStrideSize(shape_)});
  if (descriptor_.HasAxis(Axis::WIDTH)) {
    resources->ints.push_back({"width", tensor_desc->GetWidthSize(shape_)});
  }
  if (descriptor_.HasAxis(Axis::HEIGHT)) {
    resources->ints.push_back({"height", Height()});
  }
  if (descriptor_.HasAxis(Axis::CHANNELS)) {
    resources->ints.push_back({"slices", Slices()});
    resources->ints.push_back({"channels", Channels()});
  }
  if (descriptor_.HasAxis(Axis::BATCH)) {
    resources->ints.push_back({"batch", Batch()});
  }
  if (descriptor_.HasAxis(Axis::DEPTH)) {
    resources->ints.push_back({"depth", Depth()});
  }

  if (descriptor_.storage_type == TensorStorageType::BUFFER) {
    resources->buffers.push_back({"buffer", memory_});
  } else if (descriptor_.storage_type == TensorStorageType::TEXTURE_2D ||
             descriptor_.storage_type == TensorStorageType::SINGLE_TEXTURE_2D) {
    cl_mem mem = buffer_based_ ? image_buffer_memory_ : memory_;
    resources->images2d.push_back({"image2d", mem});
  } else if (descriptor_.storage_type == TensorStorageType::TEXTURE_ARRAY) {
    resources->image2d_arrays.push_back({"image2d_array", memory_});
  } else if (descriptor_.storage_type == TensorStorageType::TEXTURE_3D) {
    resources->images3d.push_back({"image3d", memory_});
  } else if (descriptor_.storage_type == TensorStorageType::IMAGE_BUFFER) {
    if (obj_ptr->GetAccess() == AccessType::READ) {
      resources->image_buffers.push_back(
          {"image_buffer", image_buffer_memory_});
    } else {
      resources->buffers.push_back({"buffer", memory_});
    }
  }

  return absl::OkStatus();
}

int3 Tensor::GetFullTensorRegion() const {
  switch (descriptor_.storage_type) {
    case TensorStorageType::BUFFER:
    case TensorStorageType::TEXTURE_ARRAY:
    case TensorStorageType::TEXTURE_3D:
    case TensorStorageType::IMAGE_BUFFER:
      return {shape_.w * shape_.b, shape_.h, shape_.d * Slices()};
    case TensorStorageType::TEXTURE_2D:
      return {shape_.w * shape_.b * shape_.d, shape_.h * Slices(), 1};
    case TensorStorageType::SINGLE_TEXTURE_2D:
      return {shape_.w * shape_.b * shape_.d, shape_.h, 1};
    case TensorStorageType::UNKNOWN:
      return {-1, -1, -1};
  }
}

absl::Status Tensor::IsValid(const BHWC& shape) const {
  if (shape.b != shape_.b) {
    return absl::InvalidArgumentError(
        "Shape batch does not match tensor batch");
  }
  if (shape.w != shape_.w) {
    return absl::InvalidArgumentError(
        "Shape width does not match tensor width");
  }
  if (shape.h != shape_.h) {
    return absl::InvalidArgumentError(
        "Shape height does not match tensor height");
  }
  if (shape.c != shape_.c) {
    return absl::InvalidArgumentError(
        "Shape channels does not match tensor channels");
  }
  return absl::OkStatus();
}

absl::Status Tensor::IsValid(const BHWDC& shape) const {
  if (shape.b != shape_.b) {
    return absl::InvalidArgumentError(
        "Shape batch does not match tensor batch");
  }
  if (shape.w != shape_.w) {
    return absl::InvalidArgumentError(
        "Shape width does not match tensor width");
  }
  if (shape.h != shape_.h) {
    return absl::InvalidArgumentError(
        "Shape height does not match tensor height");
  }
  if (shape.d != shape_.d) {
    return absl::InvalidArgumentError(
        "Shape depth does not match tensor depth");
  }
  if (shape.c != shape_.c) {
    return absl::InvalidArgumentError(
        "Shape channels does not match tensor channels");
  }
  return absl::OkStatus();
}

int Tensor::GetAlignedChannels() const {
  return descriptor_.storage_type == TensorStorageType::SINGLE_TEXTURE_2D
             ? shape_.c
             : AlignByN(shape_.c, 4);
}

uint64_t Tensor::GetMemorySizeInBytes() const {
  const int flt_size = SizeOf(descriptor_.data_type);
  const int flt4_size = 4 * flt_size;
  switch (descriptor_.storage_type) {
    case TensorStorageType::BUFFER:
    case TensorStorageType::IMAGE_BUFFER:
    case TensorStorageType::TEXTURE_ARRAY:
    case TensorStorageType::TEXTURE_2D:
    case TensorStorageType::TEXTURE_3D:
      return flt4_size * shape_.b * shape_.w * shape_.h * shape_.d * Slices();
    case TensorStorageType::SINGLE_TEXTURE_2D:
      return flt_size * shape_.w * shape_.h * shape_.c * shape_.b * shape_.d;
    default:
      return 0;
  }
}

cl_mem Tensor::GetMemoryPtr() const {
  if (buffer_based_) {
    return image_buffer_memory_;
  } else {
    return descriptor_.storage_type == TensorStorageType::IMAGE_BUFFER
               ? image_buffer_memory_
               : memory_;
  }
}

cl_mem Tensor::GetMemoryPtrForWriting() const {
  if (buffer_based_) {
    return image_buffer_memory_;
  } else {
    return memory_;
  }
}

absl::Status Tensor::WriteData(
    CLCommandQueue* queue,
    const tflite::gpu::Tensor<Linear, DataType::FLOAT32>& src) {
  return WriteDataBHWDC(src.data.data(), queue);
}

absl::Status Tensor::WriteData(
    CLCommandQueue* queue,
    const tflite::gpu::Tensor<HWC, DataType::FLOAT32>& src) {
  return WriteDataBHWDC(src.data.data(), queue);
}

absl::Status Tensor::CreateFromDescriptor(const TensorDescriptor& desc,
                                          CLContext* context) {
  shape_ = desc.shape;
  descriptor_.data_type = desc.data_type;
  descriptor_.storage_type = desc.storage_type;
  descriptor_.layout = desc.layout;
  memory_owner_ = true;
  CLMemory memory;
  uint8_t* data_ptr = desc.data.empty()
                          ? nullptr
                          : const_cast<unsigned char*>(desc.data.data());
  RETURN_IF_ERROR(
      AllocateTensorMemory(*context, shape_, descriptor_, data_ptr, &memory));
  memory_ = memory.Release();
  if (desc.storage_type == TensorStorageType::IMAGE_BUFFER) {
    RETURN_IF_ERROR(CreateImageBufferFromBuffer(
        *context, memory_, desc.data_type,
        shape_.b * shape_.w * shape_.h * shape_.d * DivideRoundUp(shape_.c, 4),
        &image_buffer_memory_));
  }
  return absl::OkStatus();
}

absl::Status CreateTensor(const CLContext& context, const BHWC& shape,
                          const TensorDescriptor& descriptor, Tensor* result) {
  const BHWDC shape5D(shape.b, shape.h, shape.w, 1, shape.c);
  return CreateTensor(context, shape5D, descriptor, nullptr, result);
}

absl::Status CreateTensor(const CLContext& context, const BHWDC& shape,
                          const TensorDescriptor& descriptor, Tensor* result) {
  return CreateTensor(context, shape, descriptor, nullptr, result);
}

absl::Status CreateSharedTensor(const CLContext& context, cl_mem memory,
                                const BHWC& shape,
                                const TensorDescriptor& descriptor,
                                Tensor* result) {
  const BHWDC shape5D(shape.b, shape.h, shape.w, 1, shape.c);
  return CreateTensorShared(context, shape5D, descriptor, memory, result);
}

absl::Status CreateSharedTensor(const CLContext& context, cl_mem memory,
                                const BHWDC& shape,
                                const TensorDescriptor& descriptor,
                                Tensor* result) {
  return CreateTensorShared(context, shape, descriptor, memory, result);
}

absl::Status CreateSharedImage2DBufferTensor(const CLContext& context,
                                             cl_mem memory, const BHWC& shape,
                                             const TensorDescriptor& descriptor,
                                             int row_bytes_alignment,
                                             Tensor* result) {
  const int width = shape.b * shape.w;
  const int height =
      descriptor.storage_type == TensorStorageType::SINGLE_TEXTURE_2D
          ? shape.h
          : shape.h * DivideRoundUp(shape.c, 4);
  const int channels =
      descriptor.storage_type == TensorStorageType::SINGLE_TEXTURE_2D ? shape.c
                                                                      : 4;
  cl_mem image_memory;
  RETURN_IF_ERROR(CreateImage2DFromBuffer(context, memory, descriptor.data_type,
                                          width, height, channels,
                                          row_bytes_alignment, &image_memory));
  *result = Tensor(memory, false, image_memory, shape, descriptor);
  return absl::OkStatus();
}

absl::Status AllocateTensorMemory(const CLContext& context, const BHWC& shape,
                                  const TensorDescriptor& descriptor,
                                  CLMemory* result) {
  const BHWDC shape5D(shape.b, shape.h, shape.w, 1, shape.c);
  return AllocateTensorMemory(context, shape5D, descriptor, nullptr, result);
}

absl::Status AllocateTensorMemory(const CLContext& context, const BHWDC& shape,
                                  const TensorDescriptor& descriptor,
                                  CLMemory* result) {
  return AllocateTensorMemory(context, shape, descriptor, nullptr, result);
}

}  // namespace cl
}  // namespace gpu
}  // namespace tflite