com.offbynull.coroutines.instrumenter.PackStateGenerators.java Source code

Java tutorial

Introduction

Here is the source code for com.offbynull.coroutines.instrumenter.PackStateGenerators.java

Source

/*
 * Copyright (c) 2016, Kasra Faghihi, All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3.0 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.
 */
package com.offbynull.coroutines.instrumenter;

import com.offbynull.coroutines.instrumenter.asm.VariableTable.Variable;
import com.offbynull.coroutines.instrumenter.generators.DebugGenerators.MarkerType;
import static com.offbynull.coroutines.instrumenter.generators.DebugGenerators.debugMarker;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.merge;
import static com.offbynull.coroutines.instrumenter.generators.GenericGenerators.mergeIf;
import org.apache.commons.lang3.Validate;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;

/**
 * Utility class to generate bytecode instructions that pack/unpack storage arrays for the operand stack and local variables table into an
 * Object array.
 * @author Kasra Faghihi
 */
final class PackStateGenerators {
    private PackStateGenerators() {
        // do nothing
    }

    // Why are we packing/unpacking in to a single Object[]? It turns out that if we compare ...
    //
    // 1. Assigning each storage array to a field in MethodState.
    //   ... vs ...
    // 2. Creating a new Object[] and shoving all the storage arrays in there, then assigning that Object[] to a single field in
    //    MethodState.
    //
    // Item number 2 seems noticably faster. I'm not exactly sure why this is, and it may not apply to every platform, but we're going to
    // exploit this micro-optimization for the time being.
    //
    // See ObjectArrayVsHolderBenchmark class to see the benchmark used to determine this.

    public static InsnList packStorageArrays(MarkerType markerType, Frame<BasicValue> frame, Variable containerVar,
            StorageVariables localsStorageVars, StorageVariables operandStackStorageVars) {
        Validate.notNull(markerType);
        Validate.notNull(frame);
        Validate.notNull(containerVar);
        Validate.notNull(localsStorageVars);
        Validate.notNull(operandStackStorageVars);

        Variable localsIntsVar = localsStorageVars.getIntStorageVar();
        Variable localsFloatsVar = localsStorageVars.getFloatStorageVar();
        Variable localsLongsVar = localsStorageVars.getLongStorageVar();
        Variable localsDoublesVar = localsStorageVars.getDoubleStorageVar();
        Variable localsObjectsVar = localsStorageVars.getObjectStorageVar();
        Variable stackIntsVar = operandStackStorageVars.getIntStorageVar();
        Variable stackFloatsVar = operandStackStorageVars.getFloatStorageVar();
        Variable stackLongsVar = operandStackStorageVars.getLongStorageVar();
        Variable stackDoublesVar = operandStackStorageVars.getDoubleStorageVar();
        Variable stackObjectsVar = operandStackStorageVars.getObjectStorageVar();

        StorageSizes stackSizes = OperandStackStateGenerators.computeSizes(frame, 0, frame.getStackSize());
        StorageSizes localsSizes = LocalsStateGenerators.computeSizes(frame);

        // Why are we using size > 0 vs checking to see if var != null?
        //
        // REMEMBER THAT the analyzer will determine the variable slots to create for storage array based on its scan of EVERY
        // continuation/suspend point in the method. Imagine the method that we're instrumenting is this...
        //
        // public void example(Continuation c, String arg1) {
        //     String var1 = "hi";
        //     c.suspend();     
        //
        //     System.out.println(var1);
        //     int var2 = 5;
        //     c.suspend();
        //
        //     System.out.println(var1 + var2);
        // }
        //
        // There are two continuation/suspend points. The analyzer determines that method will need to assign variable slots for
        // localsObjectsVar+localsIntsVar. All the other locals vars will be null.
        //
        // If we ended up using var != null instead of size > 0, things would mess up on the first suspend(). The only variable initialized
        // at the first suspend is var1. As such, LocalStateGenerator ONLY CREATES AN ARRAY FOR localsObjectsVar. It doesn't touch
        // localsIntsVar because, at the first suspend(), var2 is UNINITALIZED. Nothing has been set to that variable slot.
        //
        //
        // The same thing applies to the operand stack. It doesn't make sense to create arrays for operand stack types that don't exist yet
        // at a continuation point, even though they may exist at other continuation points furhter down

        // Storage arrays in to locals container
        return merge(
                debugMarker(markerType, "Packing storage arrays for locals and operand stack in to an Object[]"),
                new LdcInsnNode(10), new TypeInsnNode(Opcodes.ANEWARRAY, "java/lang/Object"),
                new VarInsnNode(Opcodes.ASTORE, containerVar.getIndex()),
                mergeIf(localsSizes.getIntsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting locals ints in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(0), // [Object[], 0]
                                new VarInsnNode(Opcodes.ALOAD, localsIntsVar.getIndex()), // [Object[], 0, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(localsSizes.getFloatsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting locals floats in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(1), // [Object[], 1]
                                new VarInsnNode(Opcodes.ALOAD, localsFloatsVar.getIndex()), // [Object[], 1, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(localsSizes.getLongsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting locals longs in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(2), // [Object[], 2]
                                new VarInsnNode(Opcodes.ALOAD, localsLongsVar.getIndex()), // [Object[], 2, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(localsSizes.getDoublesSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting locals doubles in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(3), // [Object[], 3]
                                new VarInsnNode(Opcodes.ALOAD, localsDoublesVar.getIndex()), // [Object[], 3, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(localsSizes.getObjectsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting locals objects in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(4), // [Object[], 4]
                                new VarInsnNode(Opcodes.ALOAD, localsObjectsVar.getIndex()), // [Object[], 4, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(stackSizes.getIntsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting stack ints in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(5), // [Object[], 5]
                                new VarInsnNode(Opcodes.ALOAD, stackIntsVar.getIndex()), // [Object[], 5, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(stackSizes.getFloatsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting stack floats in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(6), // [Object[], 6]
                                new VarInsnNode(Opcodes.ALOAD, stackFloatsVar.getIndex()), // [Object[], 6, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(stackSizes.getLongsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting stack longs in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(7), // [Object[], 7]
                                new VarInsnNode(Opcodes.ALOAD, stackLongsVar.getIndex()), // [Object[], 7, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(stackSizes.getDoublesSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting stack doubles in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(8), // [Object[], 8]
                                new VarInsnNode(Opcodes.ALOAD, stackDoublesVar.getIndex()), // [Object[], 8, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }),
                mergeIf(stackSizes.getObjectsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Putting stack objects in to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(9), // [Object[], 9]
                                new VarInsnNode(Opcodes.ALOAD, stackObjectsVar.getIndex()), // [Object[], 9, val]
                                new InsnNode(Opcodes.AASTORE), // []
                        }));
    }

    public static InsnList unpackLocalsStorageArrays(MarkerType markerType, Frame<BasicValue> frame,
            Variable containerVar, StorageVariables localsStorageVars) {
        Validate.notNull(markerType);
        Validate.notNull(containerVar);
        Validate.notNull(localsStorageVars);

        Variable localsIntsVar = localsStorageVars.getIntStorageVar();
        Variable localsFloatsVar = localsStorageVars.getFloatStorageVar();
        Variable localsLongsVar = localsStorageVars.getLongStorageVar();
        Variable localsDoublesVar = localsStorageVars.getDoubleStorageVar();
        Variable localsObjectsVar = localsStorageVars.getObjectStorageVar();

        StorageSizes localsSizes = LocalsStateGenerators.computeSizes(frame);

        // Why are we using size > 0 vs checking to see if var != null?
        //
        // REMEMBER THAT the analyzer will determine the variable slots to create for storage array based on its scan of EVERY
        // continuation/suspend point in the method. Imagine the method that we're instrumenting is this...
        //
        // public void example(Continuation c, String arg1) {
        //     String var1 = "hi";
        //     c.suspend();     
        //
        //     System.out.println(var1);
        //     int var2 = 5;
        //     c.suspend();
        //
        //     System.out.println(var1 + var2);
        // }
        //
        // There are two continuation/suspend points. The analyzer determines that method will need to assign variable slots for
        // localsObjectsVar+localsIntsVar. All the other locals vars will be null.
        //
        // If we ended up using var != null instead of size > 0, things would mess up on the first suspend(). The only variable initialized
        // at the first suspend is var1. As such, LocalStateGenerator ONLY CREATES AN ARRAY FOR localsObjectsVar. It doesn't touch
        // localsIntsVar because, at the first suspend(), var2 is UNINITALIZED. Nothing has been set to that variable slot.
        //
        //
        // The same thing applies to the operand stack. It doesn't make sense to create arrays for operand stack types that don't exist yet
        // at a continuation point, even though they may exist at other continuation points furhter down

        // Storage arrays from locals container
        return merge(
                debugMarker(markerType, "Unpacking storage arrays for locals and operand stack from an Object[]"),
                mergeIf(localsSizes.getIntsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting locals ints from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(0), // [Object[], 0]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, localsIntsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, localsIntsVar.getIndex()), // []
                        }),
                mergeIf(localsSizes.getFloatsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting locals floats from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(1), // [Object[], 1]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, localsFloatsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, localsFloatsVar.getIndex()), // []
                        }),
                mergeIf(localsSizes.getLongsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting locals longs from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(2), // [Object[], 2]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, localsLongsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, localsLongsVar.getIndex()), // []
                        }),
                mergeIf(localsSizes.getDoublesSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting locals doubles from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(3), // [Object[], 3]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, localsDoublesVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, localsDoublesVar.getIndex()), // []
                        }),
                mergeIf(localsSizes.getObjectsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting locals objects from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(4), // [Object[], 4]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, localsObjectsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, localsObjectsVar.getIndex()), // []
                        }));
    }

    public static InsnList unpackOperandStackStorageArrays(MarkerType markerType, Frame<BasicValue> frame,
            Variable containerVar, StorageVariables operandStackStorageVars) {
        Validate.notNull(markerType);
        Validate.notNull(containerVar);
        Validate.notNull(operandStackStorageVars);

        Variable stackIntsVar = operandStackStorageVars.getIntStorageVar();
        Variable stackFloatsVar = operandStackStorageVars.getFloatStorageVar();
        Variable stackLongsVar = operandStackStorageVars.getLongStorageVar();
        Variable stackDoublesVar = operandStackStorageVars.getDoubleStorageVar();
        Variable stackObjectsVar = operandStackStorageVars.getObjectStorageVar();

        StorageSizes stackSizes = OperandStackStateGenerators.computeSizes(frame, 0, frame.getStackSize());

        // Why are we using size > 0 vs checking to see if var != null?
        //
        // REMEMBER THAT the analyzer will determine the variable slots to create for storage array based on its scan of EVERY
        // continuation/suspend point in the method. Imagine the method that we're instrumenting is this...
        //
        // public void example(Continuation c, String arg1) {
        //     String var1 = "hi";
        //     c.suspend();     
        //
        //     System.out.println(var1);
        //     int var2 = 5;
        //     c.suspend();
        //
        //     System.out.println(var1 + var2);
        // }
        //
        // There are two continuation/suspend points. The analyzer determines that method will need to assign variable slots for
        // localsObjectsVar+localsIntsVar. All the other locals vars will be null.
        //
        // If we ended up using var != null instead of size > 0, things would mess up on the first suspend(). The only variable initialized
        // at the first suspend is var1. As such, LocalStateGenerator ONLY CREATES AN ARRAY FOR localsObjectsVar. It doesn't touch
        // localsIntsVar because, at the first suspend(), var2 is UNINITALIZED. Nothing has been set to that variable slot.
        //
        //
        // The same thing applies to the operand stack. It doesn't make sense to create arrays for operand stack types that don't exist yet
        // at a continuation point, even though they may exist at other continuation points furhter down

        // Storage arrays from locals container
        return merge(debugMarker(markerType, "Unpacking storage arrays for operand stack from an Object[]"),
                mergeIf(stackSizes.getIntsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting stack ints from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(5), // [Object[], 5]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, stackIntsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, stackIntsVar.getIndex()), // []
                        }),
                mergeIf(stackSizes.getFloatsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting stack floats from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(6), // [Object[], 6]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, stackFloatsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, stackFloatsVar.getIndex()), // []
                        }),
                mergeIf(stackSizes.getLongsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting stack longs from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(7), // [Object[], 7]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, stackLongsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, stackLongsVar.getIndex()), // []
                        }),
                mergeIf(stackSizes.getDoublesSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting stack doubles from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(8), // [Object[], 8]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, stackDoublesVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, stackDoublesVar.getIndex()), // []
                        }),
                mergeIf(stackSizes.getObjectsSize() > 0,
                        () -> new Object[] { debugMarker(markerType, "Getting stack objects from to container"),
                                new VarInsnNode(Opcodes.ALOAD, containerVar.getIndex()), // [Object[]]
                                new LdcInsnNode(9), // [Object[], 9]
                                new InsnNode(Opcodes.AALOAD), // [val]
                                new TypeInsnNode(Opcodes.CHECKCAST, stackObjectsVar.getType().getInternalName()), // [val] REQ BY JVM SO TYPE IS KNOWN
                                new VarInsnNode(Opcodes.ASTORE, stackObjectsVar.getIndex()), // []
                        }));
    }
}