com.sun.fortress.compiler.environments.TopLevelEnvGen.java Source code

Java tutorial

Introduction

Here is the source code for com.sun.fortress.compiler.environments.TopLevelEnvGen.java

Source

/*******************************************************************************
Copyright now,2010, Oracle and/or its affiliates.
All rights reserved.
    
    
Use is subject to license terms.
    
This distribution may include materials developed by third parties.
    
 ******************************************************************************/

package com.sun.fortress.compiler.environments;

import com.sun.fortress.compiler.NamingCzar;
import com.sun.fortress.compiler.StaticPhaseResult;
import com.sun.fortress.compiler.WellKnownNames;
import com.sun.fortress.compiler.index.*;
import com.sun.fortress.exceptions.StaticError;
import com.sun.fortress.exceptions.WrappedException;
import com.sun.fortress.interpreter.evaluator.BaseEnv;
import com.sun.fortress.nodes.*;
import com.sun.fortress.nodes_util.NodeUtil;
import com.sun.fortress.repository.ProjectProperties;
import com.sun.fortress.runtimeSystem.Naming;
import com.sun.fortress.useful.HasAt;
import com.sun.fortress.useful.Pair;
import edu.rice.cs.plt.collect.PredicateSet;
import edu.rice.cs.plt.collect.Relation;
import org.objectweb.asm.*;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

public class TopLevelEnvGen {

    private static final String STRING_INTERNALNAME = Type.getType(String.class).getInternalName();
    private static final String STRING_DESCRIPTOR = Type.getType(String.class).getDescriptor();

    public static final String API_ENV_SUFFIX = "ApiEnv";
    public static final String COMPONENT_ENV_SUFFIX = "ComponentEnv";

    public static class CompilationUnitResult extends StaticPhaseResult {
        private final Map<APIName, Pair<String, byte[]>> _compUnits;

        public CompilationUnitResult(Map<APIName, Pair<String, byte[]>> compUnits,
                Iterable<? extends StaticError> errors) {
            super(errors);
            _compUnits = compUnits;
        }

        public Map<APIName, Pair<String, byte[]>> generatedEnvs() {
            return _compUnits;
        }
    }

    /**
     * Given a list of components, generate a Java bytecode compiled environment
     * for each component.
     */
    public static CompilationUnitResult generateApiEnvs(Map<APIName, ApiIndex> apis) {

        Map<APIName, Pair<String, byte[]>> compiledApis = new HashMap<APIName, Pair<String, byte[]>>();
        HashSet<StaticError> errors = new HashSet<StaticError>();

        for (Map.Entry<APIName, ApiIndex> api : apis.entrySet()) {
            String className = NamingCzar.classNameForApiEnvironment(api.getKey());

            try {
                byte[] envClass = generateForCompilationUnit(className, api.getValue());
                compiledApis.put(api.getKey(), new Pair<String, byte[]>(className, envClass));
            } catch (StaticError staticError) {
                errors.add(staticError);
            }
        }

        if (errors.isEmpty()) {
            outputClassFiles(compiledApis, API_ENV_SUFFIX, errors);
        }

        return new CompilationUnitResult(compiledApis, errors);
    }

    /**
     * Given a list of components, generate a Java bytecode compiled environment
     * for each component.
     */
    public static CompilationUnitResult generateComponentEnvs(Map<APIName, ComponentIndex> components) {

        Map<APIName, Pair<String, byte[]>> compiledComponents = new HashMap<APIName, Pair<String, byte[]>>();
        HashSet<StaticError> errors = new HashSet<StaticError>();

        for (Map.Entry<APIName, ComponentIndex> component : components.entrySet()) {
            String className = NamingCzar.classNameForComponentEnvironment(component.getKey());
            try {
                byte[] envClass = generateForCompilationUnit(className, component.getValue());
                compiledComponents.put(component.getKey(), new Pair<String, byte[]>(className, envClass));
            } catch (StaticError staticError) {
                errors.add(staticError);
            }
        }

        if (errors.isEmpty()) {
            outputClassFiles(compiledComponents, COMPONENT_ENV_SUFFIX, errors);
        }

        return new CompilationUnitResult(compiledComponents, errors);
    }

    /**
     * Given one component, generate a Java bytecode compiled environment
     * for that component.
     */
    private static byte[] generateForCompilationUnit(String className, CompilationUnitIndex compUnitIndex) {

        /*
         *  With new ClassWriter(ClassWriter.COMPUTE_FRAMES) everything is
         *  computed automatically. You don't have to call visitFrame, but you
         *  must still call visitMaxs (arguments will be ignored and recomputed).
         *  Using these options is convenient but this has a cost: the COMPUTE_FRAMES option
         *  makes it two times slower.
         *
         *  Currently not a performance bottleneck.
         */
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        cw.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC + Opcodes.ACC_SUPER + Opcodes.ACC_FINAL, className, null,
                Type.getType(BaseEnv.class).getInternalName(), null);

        // Implementing "static reflection" for the interpreter
        EnvSymbolNames symbolNames = new EnvSymbolNames();

        writeFields(cw, compUnitIndex, symbolNames);
        writeMethodInit(cw, className);

        writeMethodGetRaw(cw, className, "getApiNull", EnvironmentClass.ENVIRONMENT, symbolNames);
        writeMethodPutRaw(cw, className, "putApi", EnvironmentClass.ENVIRONMENT, symbolNames);
        writeMethodGetRaw(cw, className, "getValueRaw", EnvironmentClass.FVALUE, symbolNames);
        writeMethodPutRaw(cw, className, "putValueRaw", EnvironmentClass.FVALUE, symbolNames);
        writeMethodGetRaw(cw, className, "getTypeNull", EnvironmentClass.FTYPE, symbolNames);
        writeMethodPutRaw(cw, className, "putTypeRaw", EnvironmentClass.FTYPE, symbolNames);
        writeEmptyMethods(cw, className);
        writeRemoveMethods(cw, className);
        writeDumpMethod(cw, className, symbolNames);
        cw.visitEnd();

        return (cw.toByteArray());
    }

    /**
     * Write the import statements as fields in this compiled environment.
     * This method will be invoked by writeFields().
     */
    private static void writeImportFields(ClassWriter cw, CompilationUnitIndex compUnitIndex,
            EnvSymbolNames symbolNames) {
        CompilationUnit comp = compUnitIndex.ast();
        final Set<String> importedApiNames = new HashSet<String>();

        for (Import imports : comp.getImports()) {
            if (imports instanceof ImportApi) {
                ImportApi importApi = (ImportApi) imports;
                for (AliasedAPIName api : importApi.getApis()) {
                    importedApiNames.add(NodeUtil.nameString(api.getApiName()));
                }
            } else if (imports instanceof ImportedNames) {
                ImportedNames importNames = (ImportedNames) imports;
                importedApiNames.add(NodeUtil.nameString(importNames.getApiName()));
            } else {
                throw StaticError.make("Unrecognized import type in bytecode generation", imports);
            }
        }

        // XXX: SUPER DUPER HACKY
        // Rewrite the AST to include these builtin imports!
        for (String builtinLib : WellKnownNames.defaultLibrary()) {
            importedApiNames.add(builtinLib);
        }

        // Any names that are exported, are also "imported", which is to say,
        // the disambiguator will generate references to them, so we had better
        // have an answer.
        if (comp instanceof Component) {
            Component ccomp = (Component) comp;
            for (APIName api : ccomp.getExports()) {
                importedApiNames.add(NodeUtil.nameString(api));
            }
        }

        namesToFields(EnvironmentClass.ENVIRONMENT, cw, symbolNames, importedApiNames);
    }

    /**
     * Write all the fields that will be used in this compiled environment
     */
    private static void writeFields(ClassWriter cw, CompilationUnitIndex compUnitIndex,
            EnvSymbolNames symbolNames) {

        // Create all variables as fields in the environment
        Set<String> idStringSet = new HashSet<String>();
        for (Id id : compUnitIndex.variables().keySet()) {
            String idString = NodeUtil.nameString(id);
            if (idString.equals("_")) {
                Variable v = compUnitIndex.variables().get(id);
                if (v instanceof DeclaredVariable) {
                    DeclaredVariable dv = (DeclaredVariable) v;
                    LValue lvb = dv.ast();
                    idString = WellKnownNames.tempForUnderscore(lvb.getName());
                } else {
                    throw new Error("unhandled case for _ variable");
                }
                // System.err.println(v);
                // apply further mangling.
            }
            idStringSet.add(idString);
        }
        namesToFields(EnvironmentClass.FVALUE, cw, symbolNames, idStringSet);

        // Create all functions as fields in the environment
        idStringSet.clear();
        for (IdOrOpOrAnonymousName id : compUnitIndex.functions().firstSet()) {
            String idString = NodeUtil.nameString(id);
            idStringSet.add(idString);
        }
        namesToFields(EnvironmentClass.FVALUE, cw, symbolNames, idStringSet);

        // Create all types as fields in the environment
        idStringSet.clear();
        for (Id id : compUnitIndex.typeConses().keySet()) {
            String idString = NodeUtil.nameString(id);
            idStringSet.add(idString);
        }
        namesToFields(EnvironmentClass.FTYPE, cw, symbolNames, idStringSet);

        // Special case for singleton objects; get to the object through
        // its type
        idStringSet.clear();
        for (Id id : compUnitIndex.typeConses().keySet()) {
            TypeConsIndex tci = compUnitIndex.typeConses().get(id);
            if (tci instanceof ObjectTraitIndex) {
                ObjectTraitIndex oti = (ObjectTraitIndex) tci;
                if (oti.constructor().isNone()) {
                    String idString = WellKnownNames.obfuscatedSingletonConstructorName(NodeUtil.nameString(id),
                            id);
                    idStringSet.add(idString);
                }
            }
        }
        namesToFields(EnvironmentClass.FVALUE, cw, symbolNames, idStringSet);

        handleWeirdToplevelDecls(cw, compUnitIndex, symbolNames);

        writeImportFields(cw, compUnitIndex, symbolNames);
    }

    /**
     * Emits additional names for overloaded functions,
     * for temporaries generated by multiple assignment/init,
     * and for object expressions.
     *
     * @param cw
     * @param compUnitIndex
     * @param symbolNames
     */
    private static void handleWeirdToplevelDecls(final ClassWriter cw, CompilationUnitIndex compUnitIndex,
            final EnvSymbolNames symbolNames) {
        CompilationUnit compUnit = compUnitIndex.ast();
        if (compUnit instanceof Component) {
            Component component = (Component) compUnit;
            List<Decl> decls = component.getDecls();
            for (Decl decl : decls) {
                if (decl instanceof _RewriteFnOverloadDecl) {
                    _RewriteFnOverloadDecl rewrite = (_RewriteFnOverloadDecl) decl;
                    String idString = NodeUtil.nameString(rewrite.getName());
                    nameToField(EnvironmentClass.FVALUE, cw, symbolNames, idString);
                } else if (decl instanceof VarDecl) {
                    // The interpreter rewrites a temporary for multiple assignment.
                    VarDecl vd = (VarDecl) decl;
                    List<LValue> lhs = vd.getLhs();
                    if (lhs.size() > 1) {
                        String idString = WellKnownNames.tempTupleName(vd);
                        nameToField(EnvironmentClass.FVALUE, cw, symbolNames, idString);
                    }
                }
            }

            // Scan for all object exprs in a component; the interpreter will
            // promote those to top level.
            NodeDepthFirstVisitor_void visitor = new NodeDepthFirstVisitor_void() {

                @Override
                public void forObjectExprOnly(ObjectExpr that) {
                    String idString = WellKnownNames.objectExprName(that);
                    nameToField(EnvironmentClass.FVALUE, cw, symbolNames, idString);
                    nameToField(EnvironmentClass.FTYPE, cw, symbolNames, idString);
                    // super.forObjectExprOnly(that);
                }

            };
            compUnit.accept(visitor);
        }
    }

    private static void namesToFields(EnvironmentClass nameSpace, ClassWriter cw, EnvSymbolNames symbolNames,
            Set<String> idStringSet) {
        for (String idString : idStringSet) {
            nameToField(nameSpace, cw, symbolNames, idString);
        }
    }

    private static void nameToField(EnvironmentClass nameSpace, ClassWriter cw, EnvSymbolNames symbolNames,
            String idString) {
        symbolNames.add(nameSpace, idString);
        idString = idString + nameSpace.namespace();
        cw.visitField(Opcodes.ACC_PUBLIC, Naming.mangleIdentifier(idString), nameSpace.descriptor(), null, null)
                .visitEnd();
        return;
    }

    /**
     * Generate the default constructor for this class.
     * This constructors calls the method setToplevel().
     * If this environment is not a top-level environment, then a default
     * constructor does not need to be created.  (ASM will generate a
     * default constructor).
     */
    private static void writeMethodInit(ClassWriter cw, String className) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getType(BaseEnv.class).getInternalName(), "<init>", "()V");
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, "setTopLevel", "()V");
        mv.visitInsn(Opcodes.RETURN);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(1, 1);
        mv.visitEnd();
    }

    /**
     * Implementing "static reflection" for the method getFooRaw so the
     * interpreter uses a switch instruction for ***GetRaw
     * based on the hash values of String names in this namespace.
     */
    private static void writeMethodGetRaw(ClassWriter cw, String className, String methodName,
            EnvironmentClass environmentClass, EnvSymbolNames symbolNames) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName,
                "(Ljava/lang/String;)" + environmentClass.descriptor(), null, null);
        mv.visitCode();

        Label beginFunction = new Label();
        mv.visitLabel(beginFunction);

        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I");
        mv.visitVarInsn(Opcodes.ISTORE, 2);
        Label beginLoop = new Label();
        mv.visitLabel(beginLoop);

        Relation<String, Integer> hashCodeRelation = symbolNames.makeHashCodeRelation(environmentClass);
        ArrayList<Integer> sortedCodes = new ArrayList<Integer>(hashCodeRelation.secondSet());
        Collections.sort(sortedCodes);
        Label returnNull = new Label();

        getRawHelper(mv, className, hashCodeRelation, environmentClass, sortedCodes, returnNull);

        mv.visitLabel(returnNull);
        mv.visitInsn(Opcodes.ACONST_NULL);
        mv.visitInsn(Opcodes.ARETURN);

        Label endFunction = new Label();
        mv.visitLabel(endFunction);
        mv.visitLocalVariable("this", Naming.internalToDesc(className), null, beginFunction, endFunction, 0);
        mv.visitLocalVariable("queryString", "Ljava/lang/String;", null, beginFunction, endFunction, 1);
        mv.visitLocalVariable("queryHashCode", "I", null, beginLoop, endFunction, 2);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(2, 3);
        mv.visitEnd();
    }

    private static void getRawHelper(MethodVisitor mv, String className, Relation<String, Integer> hashCodeRelation,
            EnvironmentClass environmentClass, List<Integer> sortedCodes, Label returnNull) {
        int[] codes = new int[sortedCodes.size()];
        Label[] labels = new Label[sortedCodes.size()];
        for (int i = 0; i < codes.length; i++) {
            codes[i] = sortedCodes.get(i);
            Label label = new Label();
            labels[i] = label;
        }
        mv.visitVarInsn(Opcodes.ILOAD, 2);
        mv.visitLookupSwitchInsn(returnNull, codes, labels);
        for (int i = 0; i < codes.length; i++) {
            mv.visitLabel(labels[i]);
            getRawBaseCase(mv, className, hashCodeRelation, environmentClass, codes[i], returnNull);
        }
    }

    private static void getRawBaseCase(MethodVisitor mv, String className,
            Relation<String, Integer> hashCodeRelation, EnvironmentClass environmentClass, int code,
            Label returnNull) {

        PredicateSet<String> strings = hashCodeRelation.matchSecond(code);
        for (String testString : strings) {
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitLdcInsn(testString);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
            Label afterReturn = new Label();
            mv.visitJumpInsn(Opcodes.IFEQ, afterReturn);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            String idString = testString + environmentClass.namespace();
            mv.visitFieldInsn(Opcodes.GETFIELD, className, Naming.mangleIdentifier(idString),
                    environmentClass.descriptor());
            mv.visitInsn(Opcodes.ARETURN);
            mv.visitLabel(afterReturn);
        }
        mv.visitJumpInsn(Opcodes.GOTO, returnNull);
    }

    /**
     * Implementing "static reflection" for the method putRaw so the
     * interpreter uses a switch instruction for ***PutRaw
     * based on the hash values of String names in this namespace.
     */
    private static void writeMethodPutRaw(ClassWriter cw, String className, String methodName,
            EnvironmentClass environmentClass, EnvSymbolNames symbolNames) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName,
                "(" + STRING_DESCRIPTOR + environmentClass.descriptor() + ")V", null, null);
        mv.visitCode();

        Label beginFunction = new Label();
        mv.visitLabel(beginFunction);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, STRING_INTERNALNAME, "hashCode", "()I");
        mv.visitVarInsn(Opcodes.ISTORE, 3);
        Label beginLoop = new Label();
        mv.visitLabel(beginLoop);

        Relation<String, Integer> hashCodeRelation = symbolNames.makeHashCodeRelation(environmentClass);
        ArrayList<Integer> sortedCodes = new ArrayList<Integer>(hashCodeRelation.secondSet());
        Collections.sort(sortedCodes);
        Label notFound = new Label();
        putRawHelper(mv, className, environmentClass, hashCodeRelation, sortedCodes, notFound);
        mv.visitLabel(notFound);
        mv.visitInsn(Opcodes.RETURN);
        Label endFunction = new Label();
        mv.visitLabel(endFunction);
        mv.visitLocalVariable("this", Naming.internalToDesc(className), null, beginFunction, endFunction, 0);
        mv.visitLocalVariable("queryString", STRING_DESCRIPTOR, null, beginFunction, endFunction, 1);
        mv.visitLocalVariable("value", environmentClass.descriptor(), null, beginFunction, endFunction, 2);
        mv.visitLocalVariable("queryHashCode", "I", null, beginLoop, endFunction, 3);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(2, 4);
        mv.visitEnd();
    }

    private static void putRawHelper(MethodVisitor mv, String className, EnvironmentClass environmentClass,
            Relation<String, Integer> hashCodeRelation, List<Integer> sortedCodes, Label notFound) {

        int[] codes = new int[sortedCodes.size()];
        Label[] labels = new Label[sortedCodes.size()];
        for (int i = 0; i < codes.length; i++) {
            codes[i] = sortedCodes.get(i);
            Label label = new Label();
            labels[i] = label;
        }
        mv.visitVarInsn(Opcodes.ILOAD, 3);
        mv.visitLookupSwitchInsn(notFound, codes, labels);
        for (int i = 0; i < codes.length; i++) {
            mv.visitLabel(labels[i]);
            putRawBaseCase(mv, className, hashCodeRelation, environmentClass, codes[i], notFound);
        }

    }

    private static void putRawBaseCase(MethodVisitor mv, String className,
            Relation<String, Integer> hashCodeRelation, EnvironmentClass environmentClass, int code,
            Label notFound) {

        PredicateSet<String> strings = hashCodeRelation.matchSecond(code);
        for (String testString : strings) {
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitLdcInsn(testString);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
            Label afterSetValue = new Label();
            mv.visitJumpInsn(Opcodes.IFEQ, afterSetValue);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            String idString = testString + environmentClass.namespace();
            mv.visitFieldInsn(Opcodes.PUTFIELD, className, Naming.mangleIdentifier(idString),
                    environmentClass.descriptor());
            mv.visitInsn(Opcodes.RETURN);
            mv.visitLabel(afterSetValue);
        }
        mv.visitJumpInsn(Opcodes.GOTO, notFound);
    }

    private static void writeNullGetter(ClassWriter cw, String className, String methodName, String signature) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName, signature, null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitInsn(Opcodes.ACONST_NULL);
        mv.visitInsn(Opcodes.ARETURN);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitLocalVariable("this", Naming.internalToDesc(className), null, l0, l1, 0);
        mv.visitLocalVariable("str", "Ljava/lang/String;", null, l0, l1, 1);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(1, 2);
        mv.visitEnd();
    }

    private static void writeNullSetter(ClassWriter cw, String className, String methodName, String signature) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName, signature, null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitInsn(Opcodes.RETURN);
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitLocalVariable("this", Naming.internalToDesc(className), null, l0, l1, 0);
        mv.visitLocalVariable("str", "Ljava/lang/String;", null, l0, l1, 1);
        mv.visitLocalVariable("f2", "Ljava/lang/Number;", null, l0, l1, 2);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(0, 3);
        mv.visitEnd();
    }

    private static void writeRemoveMethod(ClassWriter cw, String className, String methodName, String invokeMethod,
            EnvironmentClass environmentClass) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName, "(Ljava/lang/String;)V", null, null);
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitInsn(Opcodes.ACONST_NULL);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, invokeMethod,
                "(Ljava/lang/String;" + environmentClass.descriptor() + ")V");
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitInsn(Opcodes.RETURN);
        Label l2 = new Label();
        mv.visitLabel(l2);
        mv.visitLocalVariable("this", Naming.internalToDesc(className), null, l0, l2, 0);
        mv.visitLocalVariable("name", "Ljava/lang/String;", null, l0, l2, 1);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(3, 2);
        mv.visitEnd();
    }

    private static void writeEmptyMethods(ClassWriter cw, String className) {

        writeNullGetter(cw, className, "getBoolNull", "(Ljava/lang/String;)Ljava/lang/Boolean;");
        writeNullGetter(cw, className, "getIntNull", "(Ljava/lang/String;)Ljava/lang/Number;");
        writeNullGetter(cw, className, "getNatNull", "(Ljava/lang/String;)Ljava/lang/Number;");

        writeNullSetter(cw, className, "putBoolRaw", "(Ljava/lang/String;Ljava/lang/Boolean;)V");
        writeNullSetter(cw, className, "putIntRaw", "(Ljava/lang/String;Ljava/lang/Number;)V");
        writeNullSetter(cw, className, "putNatRaw", "(Ljava/lang/String;Ljava/lang/Number;)V");
    }

    private static void writeRemoveMethods(ClassWriter cw, String className) {
        writeRemoveMethod(cw, className, "removeVar", "putValueRaw", EnvironmentClass.FVALUE);
        writeRemoveMethod(cw, className, "removeType", "putTypeRaw", EnvironmentClass.FTYPE);
    }

    private static void writeDumpMethod(ClassWriter cw, String className, EnvSymbolNames symbolNames) {
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "dump",
                "(Ljava/lang/Appendable;)Ljava/lang/Appendable;", null, new String[] { "java/io/IOException" });
        mv.visitCode();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, className, "within", Type.getType(HasAt.class).getDescriptor());
        Label l1 = new Label();
        mv.visitJumpInsn(Opcodes.IFNULL, l1);
        Label l2 = new Label();
        mv.visitLabel(l2);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, className, "within", Type.getType(HasAt.class).getDescriptor());
        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, Type.getType(HasAt.class).getInternalName(), "at",
                "()Ljava/lang/String;");
        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
        mv.visitInsn(Opcodes.POP);
        Label l3 = new Label();
        mv.visitLabel(l3);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitLdcInsn("\n");
        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
        mv.visitInsn(Opcodes.POP);
        Label l4 = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, l4);
        mv.visitLabel(l1);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitLdcInsn("Not within anything.\n");
        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
        mv.visitInsn(Opcodes.POP);
        mv.visitLabel(l4);
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, className, "verboseDump", "Z");
        Label l5 = new Label();
        mv.visitJumpInsn(Opcodes.IFEQ, l5);
        int linebreaks = dumpFields(mv, className, EnvironmentClass.FVALUE, symbolNames, 0);
        dumpFields(mv, className, EnvironmentClass.FTYPE, symbolNames, linebreaks);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitLdcInsn("\n");
        mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
        mv.visitInsn(Opcodes.POP);
        mv.visitLabel(l5);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitInsn(Opcodes.ARETURN);
        Label l9 = new Label();
        mv.visitLabel(l9);
        mv.visitLocalVariable("this", Naming.internalToDesc(className), null, l0, l9, 0);
        mv.visitLocalVariable("a", "Ljava/lang/Appendable;", null, l0, l9, 1);
        // See comment above on ClassWriter.COMPUTE_FRAMES
        mv.visitMaxs(2, 2);
        mv.visitEnd();
    }

    private static int dumpFields(MethodVisitor mv, String className, EnvironmentClass eClass,
            EnvSymbolNames symbolNames, int linebreaks) {
        for (String fieldName : symbolNames.getSymbolNames(eClass)) {
            Label l6 = new Label();
            mv.visitLabel(l6);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitLdcInsn("(" + fieldName + " = ");
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                    "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
            mv.visitInsn(Opcodes.POP);
            Label l7 = new Label();
            mv.visitLabel(l7);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            String idString = fieldName + eClass.namespace();
            mv.visitFieldInsn(Opcodes.GETFIELD, className, Naming.mangleIdentifier(idString), eClass.descriptor());
            Label l8 = new Label();
            mv.visitJumpInsn(Opcodes.IFNULL, l8);
            Label l9 = new Label();
            mv.visitLabel(l9);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitFieldInsn(Opcodes.GETFIELD, className, Naming.mangleIdentifier(idString), eClass.descriptor());
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, eClass.internalName(), "toString", "()Ljava/lang/String;");
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                    "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
            mv.visitInsn(Opcodes.POP);
            Label afterNull = new Label();
            mv.visitJumpInsn(Opcodes.GOTO, afterNull);
            mv.visitLabel(l8);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitLdcInsn("null");
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                    "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
            mv.visitInsn(Opcodes.POP);
            mv.visitLabel(afterNull);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitLdcInsn(") ");
            mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                    "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
            mv.visitInsn(Opcodes.POP);
            linebreaks = (linebreaks + 1) % 5;
            if (linebreaks == 0) {
                mv.visitVarInsn(Opcodes.ALOAD, 1);
                mv.visitLdcInsn("\n");
                mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/lang/Appendable", "append",
                        "(Ljava/lang/CharSequence;)Ljava/lang/Appendable;");
                mv.visitInsn(Opcodes.POP);
            }
        }

        return linebreaks;
    }

    private static void outputClassFiles(Map<APIName, Pair<String, byte[]>> compiledCompUnits, String classSuffix,
            HashSet<StaticError> errors) {
        for (Map.Entry<APIName, Pair<String, byte[]>> component : compiledCompUnits.entrySet()) {
            Pair<String, byte[]> compOutput = component.getValue();
            String fileName = ProjectProperties.ENVIRONMENT_CACHE_DIR + File.separator + compOutput.getA()
                    + ".class";
            outputClassFile(compOutput.getB(), fileName, errors);
        }
    }

    /**
     * Given a Java bytecode class stored in a byte array, save that
     * class into a file on disk.
     *
     * @throws IOException
     */
    private static void outputClassFile(byte[] bytecode, String fileName, HashSet<StaticError> errors) {
        FileOutputStream outStream = null;
        try {
            outStream = new FileOutputStream(new File(fileName));
            outStream.write(bytecode);
        } catch (IOException e) {
            errors.add(new WrappedException(e));
        } finally {
            try {
                if (outStream != null)
                    outStream.close();
            } catch (IOException e) {
                errors.add(new WrappedException(e));
            }
        }
    }

}