com.asakusafw.dag.compiler.codegen.AsmUtil.java Source code

Java tutorial

Introduction

Here is the source code for com.asakusafw.dag.compiler.codegen.AsmUtil.java

Source

/**
 * Copyright 2011-2017 Asakusa Framework Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.asakusafw.dag.compiler.codegen;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

import com.asakusafw.dag.api.processor.VertexProcessorContext;
import com.asakusafw.dag.compiler.model.graph.VertexElement;
import com.asakusafw.lang.compiler.api.reference.DataModelReference;
import com.asakusafw.lang.compiler.api.reference.PropertyReference;
import com.asakusafw.lang.compiler.model.description.ArrayTypeDescription;
import com.asakusafw.lang.compiler.model.description.BasicTypeDescription;
import com.asakusafw.lang.compiler.model.description.ClassDescription;
import com.asakusafw.lang.compiler.model.description.Descriptions;
import com.asakusafw.lang.compiler.model.description.EnumConstantDescription;
import com.asakusafw.lang.compiler.model.description.ImmediateDescription;
import com.asakusafw.lang.compiler.model.description.MethodDescription;
import com.asakusafw.lang.compiler.model.description.TypeDescription;
import com.asakusafw.lang.compiler.model.description.TypeDescription.TypeKind;
import com.asakusafw.lang.compiler.model.graph.OperatorProperty;
import com.asakusafw.lang.utils.common.Arguments;
import com.asakusafw.lang.utils.common.Invariants;
import com.asakusafw.lang.utils.common.Lang;
import com.asakusafw.lang.utils.common.Tuple;
import com.asakusafw.runtime.core.Result;
import com.asakusafw.runtime.value.ValueOption;

/**
 * Utilities for ASM.
 * @since 0.4.0
 * @version 0.4.1
 */
public final class AsmUtil {

    /**
     * The constructor method name.
     */
    public static final String CONSTRUCTOR_NAME = "<init>"; //$NON-NLS-1$

    private static final ClassDescription RESULT_TYPE = Descriptions.classOf(Result.class);

    private static final ClassDescription VALUE_OPTION_TYPE = Descriptions.classOf(ValueOption.class);

    private AsmUtil() {
        return;
    }

    /**
     * Returns the ASM {@link Type} object.
     * @param aClass the Java reflective object
     * @return the corresponded {@link Type} object
     */
    public static Type typeOf(Class<?> aClass) {
        Arguments.requireNonNull(aClass);
        return Type.getType(aClass);
    }

    /**
     * Returns the ASM {@link Type} object.
     * @param type the target type description
     * @return the corresponded {@link Type} object
     */
    public static Type typeOf(TypeDescription type) {
        Arguments.requireNonNull(type);
        TypeDescription e = type.getErasure();
        switch (e.getTypeKind()) {
        case BASIC:
            switch (((BasicTypeDescription) e).getBasicTypeKind()) {
            case BOOLEAN:
                return Type.BOOLEAN_TYPE;
            case BYTE:
                return Type.BYTE_TYPE;
            case CHAR:
                return Type.CHAR_TYPE;
            case DOUBLE:
                return Type.DOUBLE_TYPE;
            case FLOAT:
                return Type.FLOAT_TYPE;
            case INT:
                return Type.INT_TYPE;
            case LONG:
                return Type.LONG_TYPE;
            case SHORT:
                return Type.SHORT_TYPE;
            case VOID:
                return Type.VOID_TYPE;
            default:
                throw new AssertionError(type);
            }
        case ARRAY:
            return Type.getType('[' + typeOf(((ArrayTypeDescription) e).getComponentType()).getDescriptor());
        case CLASS:
            return Type.getObjectType(((ClassDescription) e).getInternalName());
        default:
            throw new AssertionError(type);
        }
    }

    /**
     * Returns the ASM {@link Type} object.
     * @param reference the data model reference
     * @return the corresponded {@link Type} object
     */
    public static Type typeOf(DataModelReference reference) {
        Arguments.requireNonNull(reference);
        return typeOf(reference.getDeclaration());
    }

    /**
     * Returns the type category.
     * @param type the target type
     * @return the category
     */
    public static int categoryOf(TypeDescription type) {
        Arguments.requireNonNull(type);
        if (type.getTypeKind() == TypeKind.BASIC) {
            switch (((BasicTypeDescription) type).getBasicTypeKind()) {
            case VOID:
                return 0;
            case LONG:
            case DOUBLE:
                return 2;
            default:
                return 1;
            }
        }
        return 1;
    }

    /**
     * Returns the T_LOAD opcode.
     * @param type the target type
     * @return the opcode
     */
    public static int loadOpcodeOf(TypeDescription type) {
        Arguments.requireNonNull(type);
        if (type.getTypeKind() == TypeKind.BASIC) {
            switch (((BasicTypeDescription) type).getBasicTypeKind()) {
            case BOOLEAN:
            case BYTE:
            case CHAR:
            case SHORT:
            case INT:
                return Opcodes.ILOAD;
            case LONG:
                return Opcodes.LLOAD;
            case FLOAT:
                return Opcodes.FLOAD;
            case DOUBLE:
                return Opcodes.DLOAD;
            default:
                throw new AssertionError(type);
            }
        }
        return Opcodes.ALOAD;
    }

    /**
     * Creates a new {@link ClassWriter}.
     * @param aClass the target class
     * @param superClass the super class
     * @param interfaces the interface types
     * @return the created class writer
     */
    public static ClassWriter newWriter(ClassDescription aClass, Class<?> superClass, Class<?>... interfaces) {
        Arguments.require(superClass.isInterface() == false);
        Lang.forEach(interfaces, c -> Arguments.require(c.isInterface()));
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, aClass.getInternalName(), null,
                typeOf(superClass).getInternalName(),
                Stream.of(interfaces).map(c -> typeOf(c).getInternalName()).toArray(String[]::new));
        return writer;
    }

    /**
     * Adds an empty constructor.
     * @param writer the target class
     * @param superClass the super class
     */
    public static void defineEmptyConstructor(ClassWriter writer, Class<?> superClass) {
        defineEmptyConstructor(writer, superClass, Lang.discard());
    }

    /**
     * Adds an empty constructor.
     * @param writer the target class
     * @param block the constructor block
     */
    public static void defineEmptyConstructor(ClassWriter writer, Consumer<MethodVisitor> block) {
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE), null, null);
        block.accept(method);
        method.visitInsn(Opcodes.RETURN);
        method.visitMaxs(0, 0);
        method.visitEnd();
    }

    /**
     * Adds an empty constructor.
     * @param writer the target class
     * @param superClass the super class
     * @param body the constructor body
     */
    public static void defineEmptyConstructor(ClassWriter writer, Class<?> superClass,
            Consumer<MethodVisitor> body) {
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE), null, null);
        method.visitVarInsn(Opcodes.ALOAD, 0);
        method.visitMethodInsn(Opcodes.INVOKESPECIAL, typeOf(superClass).getInternalName(), CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE), false);
        body.accept(method);
        method.visitInsn(Opcodes.RETURN);
        method.visitMaxs(0, 0);
        method.visitEnd();
    }

    /**
     * Adds an adapter constructor.
     * @param writer the target class
     * @param superClass the super class
     * @param body the constructor body
     */
    public static void defineAdapterConstructor(ClassWriter writer, Class<?> superClass,
            Consumer<MethodVisitor> body) {
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE, typeOf(VertexProcessorContext.class)), null, null);
        method.visitVarInsn(Opcodes.ALOAD, 0);
        method.visitVarInsn(Opcodes.ALOAD, 1);
        method.visitMethodInsn(Opcodes.INVOKESPECIAL, typeOf(superClass).getInternalName(), CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE, typeOf(VertexProcessorContext.class)), false);
        body.accept(method);
        method.visitInsn(Opcodes.RETURN);
        method.visitMaxs(0, 0);
        method.visitEnd();
    }

    /**
     * Adds a getter-like method.
     * @param writer the target class
     * @param type the method result type
     * @param name the method name
     * @param body the method body
     */
    public static void defineGetter(ClassWriter writer, Type type, String name, Consumer<MethodVisitor> body) {
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, name, Type.getMethodDescriptor(type), null,
                new String[0]);
        body.accept(method);
        method.visitMaxs(0, 0);
        method.visitEnd();
    }

    /**
     * Adds a constant value on to the top of the stack.
     * @param method the current method visitor
     * @param value the target value
     */
    public static void getConst(MethodVisitor method, Object value) {
        if (value == null) {
            method.visitInsn(Opcodes.ACONST_NULL);
        } else if (value instanceof Boolean) {
            getInt(method, ((Boolean) value).booleanValue() ? 1 : 0);
        } else if (value instanceof Byte) {
            getInt(method, ((Byte) value).byteValue());
        } else if (value instanceof Short) {
            getInt(method, ((Short) value).shortValue());
        } else if (value instanceof Character) {
            getInt(method, ((Character) value).charValue());
        } else if (value instanceof Integer) {
            getInt(method, ((Integer) value).intValue());
        } else if (value instanceof Long) {
            getLong(method, ((Long) value).longValue());
        } else if (value instanceof Float) {
            getFloat(method, ((Float) value).floatValue());
        } else if (value instanceof Double) {
            getDouble(method, ((Double) value).doubleValue());
        } else if (value instanceof ImmediateDescription) {
            getConst(method, ((ImmediateDescription) value).getValue());
        } else if (value instanceof EnumConstantDescription) {
            getEnumConstant(method, (EnumConstantDescription) value);
        } else if (value instanceof TypeDescription) {
            method.visitLdcInsn(typeOf((TypeDescription) value));
        } else {
            method.visitLdcInsn(value);
        }
    }

    /**
     * Adds a string array on to the top of the stack.
     * @param method the current method visitor
     * @param values the array elements
     */
    public static void getArray(MethodVisitor method, String[] values) {
        getArray(method, typeOf(String.class), values);
    }

    /**
     * Adds an array on to the top of the stack.
     * @param method the current method visitor
     * @param elementType the element type
     * @param values the array elements
     * @since 0.4.1
     */
    public static void getArray(MethodVisitor method, Type elementType, Object[] values) {
        getInt(method, values.length);
        method.visitTypeInsn(Opcodes.ANEWARRAY, elementType.getInternalName());
        for (int index = 0; index < values.length; index++) {
            method.visitInsn(Opcodes.DUP);
            getInt(method, index);
            getConst(method, values[index]);
            method.visitInsn(Opcodes.AASTORE);
        }
    }

    /**
     * Adds an array on to the top of the stack.
     * @param method the current method visitor
     * @param elementType the element type
     * @param values the array elements
     * @since 0.4.1
     */
    public static void getArray(MethodVisitor method, Type elementType, LocalVarRef[] values) {
        getInt(method, values.length);
        method.visitTypeInsn(Opcodes.ANEWARRAY, elementType.getInternalName());
        for (int index = 0; index < values.length; index++) {
            method.visitInsn(Opcodes.DUP);
            getInt(method, index);
            values[index].load(method);
            method.visitInsn(Opcodes.AASTORE);
        }
    }

    /**
     * Adds a value list on to the top of the stack.
     * @param method the current method visitor
     * @param values the array elements
     */
    public static void getList(MethodVisitor method, Collection<?> values) {
        getInt(method, values.size());
        method.visitTypeInsn(Opcodes.ANEWARRAY, typeOf(Object.class).getInternalName());
        int index = 0;
        for (Object value : values) {
            method.visitInsn(Opcodes.DUP);
            getInt(method, index++);
            getConst(method, value);
            method.visitInsn(Opcodes.AASTORE);
        }
        method.visitMethodInsn(Opcodes.INVOKESTATIC, typeOf(Arrays.class).getInternalName(), "asList",
                Type.getMethodDescriptor(typeOf(List.class), typeOf(Object[].class)), false);
    }

    /**
     * Adds a constant value on to the top of the stack.
     * @param method the current method visitor
     * @param value the target value
     */
    public static void getInt(MethodVisitor method, int value) {
        if (-1 <= value && value <= 5) {
            method.visitInsn(Opcodes.ICONST_0 + value);
        } else if (Byte.MIN_VALUE <= value && value <= Byte.MAX_VALUE) {
            method.visitIntInsn(Opcodes.BIPUSH, value);
        } else if (Short.MIN_VALUE <= value && value <= Short.MAX_VALUE) {
            method.visitIntInsn(Opcodes.SIPUSH, value);
        } else {
            method.visitLdcInsn(value);
        }
    }

    /**
     * Adds a constant value on to the top of the stack.
     * @param method the current method visitor
     * @param value the target value
     */
    public static void getLong(MethodVisitor method, long value) {
        if (0L <= value && value <= 1L) {
            method.visitInsn((int) (Opcodes.LCONST_0 + value));
        } else {
            method.visitLdcInsn(value);
        }
    }

    /**
     * Adds a constant value on to the top of the stack.
     * @param method the current method visitor
     * @param value the target value
     */
    public static void getFloat(MethodVisitor method, float value) {
        if (value == 0f || value == 1f || value == 2f) {
            method.visitInsn(Opcodes.FCONST_0 + (int) value);
        } else {
            method.visitLdcInsn(value);
        }
    }

    /**
     * Adds a constant value on to the top of the stack.
     * @param method the current method visitor
     * @param value the target value
     */
    public static void getDouble(MethodVisitor method, double value) {
        if (value == 0d || value == 1d) {
            method.visitInsn(Opcodes.DCONST_0 + (int) value);
        } else {
            method.visitLdcInsn(value);
        }
    }

    /**
     * Adds an new instance of the target type on to the top of the stack.
     * @param method the current method visitor
     * @param type the target type
     */
    public static void getNew(MethodVisitor method, TypeDescription type) {
        Type t = typeOf(type);
        method.visitTypeInsn(Opcodes.NEW, t.getInternalName());
        method.visitInsn(Opcodes.DUP);
        method.visitMethodInsn(Opcodes.INVOKESPECIAL, t.getInternalName(), CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE), false);
    }

    /**
     * Adds a {@code get*Option()} method invocation.
     * @param method the target method
     * @param property the target property
     */
    public static void getOption(MethodVisitor method, PropertyReference property) {
        MethodDescription decl = property.getDeclaration();
        assert decl.getParameterTypes().isEmpty();
        method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, decl.getDeclaringClass().getInternalName(), decl.getName(),
                Type.getMethodDescriptor(typeOf(property.getType())), false);
    }

    /**
     * Invokes {@code ValueOption#isNull()}.
     * @param method the target method
     * @param dataType the data type
     */
    public static void getNullity(MethodVisitor method, TypeDescription dataType) {
        method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, typeOf(dataType).getInternalName(), "isNull",
                Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false);
    }

    /**
     * Invokes {@code ValueOption#copyFrom(ValueOption)}.
     * @param method the target method
     * @param dataType the data type
     */
    public static void copyOption(MethodVisitor method, TypeDescription dataType) {
        method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, typeOf(dataType).getInternalName(), "copyFrom",
                Type.getMethodDescriptor(Type.VOID_TYPE, typeOf(dataType)), false);
    }

    /**
     * Sets {@code null} to a {@code ValueOption}.
     * @param method the target method
     */
    public static void setNullOption(MethodVisitor method) {
        method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, VALUE_OPTION_TYPE.getInternalName(), "setNull",
                Type.getMethodDescriptor(typeOf(VALUE_OPTION_TYPE)), false);
    }

    /**
     * Invokes {@code DataModel#reset()}.
     * @param method the target method
     * @param dataType the data type
     */
    public static void resetDataModel(MethodVisitor method, TypeDescription dataType) {
        method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, typeOf(dataType).getInternalName(), "reset",
                Type.getMethodDescriptor(Type.VOID_TYPE), false);
    }

    /**
     * Invokes {@code DataModel#copyFrom(DataModel)}.
     * @param method the target method
     * @param dataType the data type
     */
    public static void copyDataModel(MethodVisitor method, TypeDescription dataType) {
        method.visitMethodInsn(Opcodes.INVOKEVIRTUAL, typeOf(dataType).getInternalName(), "copyFrom",
                Type.getMethodDescriptor(Type.VOID_TYPE, typeOf(dataType)), false);
    }

    /**
     * Adds enum constant access.
     * @param method the target method
     * @param value the target enum constant
     */
    public static void getEnumConstant(MethodVisitor method, Enum<?> value) {
        Type type = typeOf(value.getDeclaringClass());
        method.visitFieldInsn(Opcodes.GETSTATIC, type.getInternalName(), value.name(),
                typeOf(value.getDeclaringClass()).getDescriptor());
    }

    /**
     * Adds enum constant access.
     * @param method the target method
     * @param value the target enum constant
     */
    public static void getEnumConstant(MethodVisitor method, EnumConstantDescription value) {
        Type type = typeOf(value.getDeclaringClass());
        method.visitFieldInsn(Opcodes.GETSTATIC, type.getInternalName(), value.getName(),
                typeOf(value.getDeclaringClass()).getDescriptor());
    }

    /**
     * Adds {@link Result#add(Object)} method invocation.
     * @param method the current method visitor
     */
    public static void invokeResultAdd(MethodVisitor method) {
        method.visitMethodInsn(Opcodes.INVOKEINTERFACE, typeOf(RESULT_TYPE).getInternalName(), "add",
                Type.getMethodDescriptor(Type.VOID_TYPE, typeOf(Object.class)), true);
    }

    /**
     * Adds {@link Result#add(Object)} method.
     * @param writer the current writer
     * @param callback the callback
     */
    public static void defineResultAdd(ClassVisitor writer, Consumer<MethodVisitor> callback) {
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, "add",
                Type.getMethodDescriptor(Type.VOID_TYPE, typeOf(Object.class)), null, null);
        callback.accept(method);
        method.visitInsn(Opcodes.RETURN);
        method.visitMaxs(0, 0);
        method.visitEnd();
    }

    /**
     * Adds constant {@code toString()} method.
     * @param writer the current writer
     * @param value the constant value
     */
    public static void defineToString(ClassVisitor writer, String value) {
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, "toString",
                Type.getMethodDescriptor(typeOf(String.class)), null, null);
        getConst(method, value);
        method.visitInsn(Opcodes.ARETURN);
        method.visitMaxs(0, 0);
        method.visitEnd();
    }

    /**
     * Adds a constructor with dependencies.
     * @param aClass the target class
     * @param writer the current writer
     * @param dependencies the target dependencies
     * @param callback the callback for building the extra constructor statements
     * @return the dependency map
     */
    public static List<Tuple<VertexElement, FieldRef>> defineDependenciesConstructor(ClassDescription aClass,
            ClassVisitor writer, Iterable<? extends VertexElement> dependencies, Consumer<MethodVisitor> callback) {
        return defineDependenciesConstructor0(aClass, writer, dependencies, method -> {
            method.visitVarInsn(Opcodes.ALOAD, 0);
            method.visitMethodInsn(Opcodes.INVOKESPECIAL, typeOf(Object.class).getInternalName(), CONSTRUCTOR_NAME,
                    Type.getMethodDescriptor(Type.VOID_TYPE), false);
        }, callback);
    }

    /**
     * Adds a constructor with dependencies.
     * @param <T> the property type
     * @param context the current context
     * @param properties the dependency properties
     * @param aClass the target class
     * @param writer the current writer
     * @param callback the callback for building the extra constructor statements
     * @return the field map
     * @since 0.4.1
     */
    public static <T extends OperatorProperty> Map<T, FieldRef> defineDependenciesConstructor(
            OperatorNodeGenerator.Context context, List<? extends T> properties, ClassDescription aClass,
            ClassVisitor writer, Consumer<MethodVisitor> callback) {
        return defineDependenciesConstructor(context, properties, aClass, writer, method -> {
            method.visitVarInsn(Opcodes.ALOAD, 0);
            method.visitMethodInsn(Opcodes.INVOKESPECIAL, typeOf(Object.class).getInternalName(), CONSTRUCTOR_NAME,
                    Type.getMethodDescriptor(Type.VOID_TYPE), false);
        }, callback);
    }

    /**
     * Adds a constructor with dependencies.
     * @param <T> the property type
     * @param context the current context
     * @param properties the dependency properties
     * @param aClass the target class
     * @param writer the current writer
     * @param superConstructor super constructor invocation
     * @param callback the callback for building the extra constructor statements
     * @return the field map
     * @since 0.4.1
     */
    public static <T extends OperatorProperty> Map<T, FieldRef> defineDependenciesConstructor(
            OperatorNodeGenerator.Context context, List<? extends T> properties, ClassDescription aClass,
            ClassVisitor writer, Consumer<MethodVisitor> superConstructor, Consumer<MethodVisitor> callback) {
        List<Tuple<VertexElement, FieldRef>> fields = defineDependenciesConstructor0(aClass, writer,
                context.getDependencies(properties), superConstructor, callback);
        Invariants.require(fields.size() == properties.size());
        Map<T, FieldRef> results = new LinkedHashMap<>();
        int index = 0;
        for (T property : properties) {
            Tuple<VertexElement, FieldRef> pair = fields.get(index++);
            VertexElement element = context.getDependency(property);
            Invariants.require(pair.left() == element);
            results.put(property, pair.right());
        }
        return results;
    }

    private static List<Tuple<VertexElement, FieldRef>> defineDependenciesConstructor0(ClassDescription aClass,
            ClassVisitor writer, Iterable<? extends VertexElement> dependencies,
            Consumer<MethodVisitor> superConstructor, Consumer<MethodVisitor> callback) {
        List<Tuple<VertexElement, FieldRef>> results = new ArrayList<>();
        int index = 0;
        int varIndex = 1;
        List<Type> parameterTypes = new ArrayList<>();
        List<Consumer<MethodVisitor>> ctor = new ArrayList<>();
        for (VertexElement element : dependencies) {
            int current = index++;
            int currentVar = varIndex;
            String name = getDependencyId(current);
            Type type = typeOf(element.getRuntimeType());
            parameterTypes.add(type);
            FieldRef ref = defineField(writer, aClass, name, type);
            ctor.add(v -> {
                v.visitVarInsn(Opcodes.ALOAD, 0);
                v.visitVarInsn(loadOpcodeOf(element.getRuntimeType()), currentVar);
                putField(v, ref);
            });
            varIndex += categoryOf(element.getRuntimeType());
            results.add(new Tuple<>(element, ref));
        }
        MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, CONSTRUCTOR_NAME,
                Type.getMethodDescriptor(Type.VOID_TYPE, parameterTypes.stream().toArray(Type[]::new)), null, null);
        superConstructor.accept(method);
        ctor.forEach(c -> c.accept(method));
        callback.accept(method);
        method.visitInsn(Opcodes.RETURN);
        method.visitMaxs(0, 0);
        method.visitEnd();
        return results;
    }

    /**
     * Returns the ID for the target dependency.
     * @param index the dependency index
     * @return the ID
     */
    public static String getDependencyId(int index) {
        return String.format("dep%d", index); //$NON-NLS-1$
    }

    /**
     * Defines a new field.
     * @param writer the current class visitor
     * @param target the declaring type
     * @param name the field name
     * @param type the field type
     * @return the defined field ref
     */
    public static FieldRef defineField(ClassVisitor writer, ClassDescription target, String name, Type type) {
        writer.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, name, type.getDescriptor(), null, null);
        return new FieldRef(target, name, type);
    }

    /**
     * Performs {@code PUTFIELD} instruction.
     * @param method the target method
     * @param field the target field ref
     */
    public static void putField(MethodVisitor method, FieldRef field) {
        method.visitFieldInsn(Opcodes.PUTFIELD, field.declaring.getInternalName(), field.name,
                field.type.getDescriptor());
    }

    /**
     * Performs {@code GETFIELD} instruction.
     * @param method the target method
     * @param field the target field ref
     */
    public static void getField(MethodVisitor method, FieldRef field) {
        method.visitFieldInsn(Opcodes.GETFIELD, field.declaring.getInternalName(), field.name,
                field.type.getDescriptor());
    }

    /**
     * Performs {@code GETFIELD} instruction.
     * @param method the target method
     * @param declaring the field declaring class
     * @param name the field name
     * @param type the field type
     * @return the target field
     */
    public static FieldRef getField(MethodVisitor method, ClassDescription declaring, String name, Type type) {
        FieldRef ref = new FieldRef(declaring, name, type);
        getField(method, ref);
        return ref;
    }

    /**
     * Performs {@code T_STORE} instruction.
     * @param method the target method
     * @param sort the target type sort
     * @param index the target local variable index
     * @return the target local variable ref
     */
    public static LocalVarRef putLocalVar(MethodVisitor method, int sort, int index) {
        int load;
        int store;
        switch (sort) {
        case Type.BOOLEAN:
        case Type.BYTE:
        case Type.SHORT:
        case Type.CHAR:
        case Type.INT:
            load = Opcodes.ILOAD;
            store = Opcodes.ISTORE;
            break;
        case Type.LONG:
            load = Opcodes.LLOAD;
            store = Opcodes.LSTORE;
            break;
        case Type.FLOAT:
            load = Opcodes.FLOAD;
            store = Opcodes.FSTORE;
            break;
        case Type.DOUBLE:
            load = Opcodes.DLOAD;
            store = Opcodes.DSTORE;
            break;
        case Type.ARRAY:
        case Type.OBJECT:
            load = Opcodes.ALOAD;
            store = Opcodes.ASTORE;
            break;
        default:
            throw new AssertionError(sort);
        }
        method.visitVarInsn(store, index);
        return new LocalVarRef(load, index);
    }

    /**
     * Cast the local variable.
     * @param method the writer
     * @param index the variable index
     * @param type the target type
     * @return the target variable
     */
    public static LocalVarRef cast(MethodVisitor method, int index, TypeDescription type) {
        method.visitVarInsn(Opcodes.ALOAD, index);
        method.visitTypeInsn(Opcodes.CHECKCAST, AsmUtil.typeOf(type).getInternalName());
        return putLocalVar(method, Type.OBJECT, index);
    }

    /**
     * Represents a value.
     */
    @FunctionalInterface
    public interface ValueRef {

        /**
         * Loads this variable onto top of the stack.
         * @param method the target method
         */
        void load(MethodVisitor method);
    }

    /**
     * Represents a local var.
     */
    public static class LocalVarRef implements ValueRef {

        final int opcode;

        final int operand;

        /**
         * Creates a new instance.
         * @param opcode opcode
         * @param operand operand
         */
        public LocalVarRef(int opcode, int operand) {
            this.opcode = opcode;
            this.operand = operand;
        }

        @Override
        public void load(MethodVisitor method) {
            method.visitVarInsn(opcode, operand);
        }
    }

    /**
     * Represents a field.
     */
    public static class FieldRef implements ValueRef {

        final ClassDescription declaring;

        final String name;

        final Type type;

        FieldRef(ClassDescription declaring, String name, Type type) {
            this.declaring = declaring;
            this.name = name;
            this.type = type;
        }

        @Override
        public void load(MethodVisitor method) {
            method.visitVarInsn(Opcodes.ALOAD, 0);
            getField(method, this);
        }

        /**
         * Returns the type.
         * @return the type
         */
        public Type getType() {
            return type;
        }
    }
}