st.redline.compiler.ByteCodeEmitter.java Source code

Java tutorial

Introduction

Here is the source code for st.redline.compiler.ByteCodeEmitter.java

Source

/* Redline Smalltalk, Copyright (c) James C. Ladd. All rights reserved. See LICENSE in the root of this distribution. */
package st.redline.compiler;

import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.objectweb.asm.*;
import st.redline.classloader.SmalltalkClassLoader;
import st.redline.classloader.Source;

import java.math.BigDecimal;
import java.util.List;
import java.util.Stack;
import java.util.Vector;

import static st.redline.compiler.EmitterNode.*;
import static st.redline.compiler.SmalltalkParser.*;
import static st.redline.compiler.Trace.trace;

class ByteCodeEmitter implements Emitter, Opcodes {

    private static Log LOG = LogFactory.getLog(ByteCodeEmitter.class);
    private static final String[] SIGNATURES = { "(Ljava/lang/String;)Lst/redline/kernel/PrimObject;",
            "(Lst/redline/kernel/PrimObject;Ljava/lang/String;)Lst/redline/kernel/PrimObject;",
            "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Ljava/lang/String;)Lst/redline/kernel/PrimObject;",
            "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Ljava/lang/String;)Lst/redline/kernel/PrimObject;",
            "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Ljava/lang/String;)Lst/redline/kernel/PrimObject;",
            "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Ljava/lang/String;)Lst/redline/kernel/PrimObject;" };
    private static final int BYTECODE_VERSION;
    static {
        int compareTo18 = new BigDecimal(System.getProperty("java.specification.version"))
                .compareTo(new BigDecimal("1.8"));
        if (compareTo18 >= 0) {
            BYTECODE_VERSION = V1_8;
        } else {
            throw new RuntimeException("Java 1.8 or above required.");
        }
    }
    private final String SEND_MESSAGES_SIG = "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimContext;)Lst/redline/kernel/PrimObject;";
    private final String METHOD_SIG = "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimContext;)Lst/redline/kernel/PrimObject;";

    protected ClassWriter cw;
    protected MethodVisitor mv;
    protected Source source;
    private byte[] classBytes;
    private boolean sendToSuper = false;
    private Stack<String> blockAnswerNames;
    private Label blockTryStartLabel;
    private Label blockTryEndLabel;
    private Label blockCatchLabel;

    ByteCodeEmitter() {
        cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
    }

    protected boolean isTraceEnabled(Log log) {
        return log.isTraceEnabled();
    }

    @Override
    public byte[] generatedBytes() {
        return classBytes;
    }

    @Override
    public void openClass(Source source) {
        this.source = source;
        if (isTraceEnabled(LOG))
            LOG.trace(source.fullClassName());
        cw.visit(BYTECODE_VERSION, ACC_PUBLIC + ACC_SUPER, source.fullClassName(), null, superclassName(),
                new String[] { "st/redline/classloader/Script" });
        cw.visitSource(source.className() + source.fileExtension(), null);
        // Add support for the Lambda's we typically create to support blocks.
        cw.visitInnerClass("java/lang/invoke/MethodHandles$Lookup", "java/lang/invoke/MethodHandles", "Lookup",
                ACC_PUBLIC + ACC_FINAL + ACC_STATIC);

        makeJavaClassInitializer();
        makePublicSendMessagesMethod();
        openPrivateSendMessagesMethod();
    }

    private void makePublicSendMessagesMethod() {
        // This method takes the public call to sendMessage with arg of Smalltalk instance
        // and calls the private sendMessages instance with instances of PrimObject and PrimContext
        // - Smalltalk instance is put into the context.
        mv = cw.visitMethod(ACC_PUBLIC, "sendMessages", "(Lst/redline/Smalltalk;)Lst/redline/kernel/PrimObject;",
                null, null);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, "st/redline/kernel/PrimObject");
        mv.visitTypeInsn(NEW, "st/redline/kernel/PrimContext");
        mv.visitInsn(DUP);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKESPECIAL, "st/redline/kernel/PrimContext", "<init>", "(Lst/redline/Smalltalk;)V",
                false);
        mv.visitMethodInsn(INVOKEVIRTUAL, source.fullClassName(), "sendMessages", SEND_MESSAGES_SIG, false);
        mv.visitInsn(ARETURN);
        mv.visitMaxs(5, 2);
        mv.visitEnd();
    }

    private void openPrivateSendMessagesMethod() {
        if (isTraceEnabled(LOG))
            LOG.trace("");
        mv = cw.visitMethod(ACC_PUBLIC, "sendMessages", SEND_MESSAGES_SIG, null, null);
        mv.visitCode();

        // Add value to top of stack - see emit(Message)
        mv.visitVarInsn(ALOAD, 1);
    }

    private String superclassName() {
        return "java/lang/Object";
    }

    private void makeJavaClassInitializer() {
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        mv.visitCode();

        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitLineNumber(1, l0);

        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, superclassName(), "<init>", "()V", false);

        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    @Override
    public void closeClass(boolean returnRequired) {
        if (isTraceEnabled(LOG))
            LOG.trace(source.fullClassName());
        closePrivateSendMessagesMethod(returnRequired);
        cw.visitEnd();
        classBytes = cw.toByteArray();
    }

    @Override
    public Emitter blockEmitter() {
        return new BlockByteCodeEmitter(source, cw);
    }

    @Override
    public void openBlock(int blockId, boolean isMethodBlock) {
        throw new RuntimeException("Not a Block Emitter");
    }

    @Override
    public String closeBlock(int blockId) {
        throw new RuntimeException("Not a Block Emitter");
    }

    @Override
    public void emit(Statement statement) {
        if (isTraceEnabled(LOG))
            LOG.trace(statement);
        blockAnswerNames = new Stack<>();
        emit(statement.messages());
        if (statement.containsAnswer())
            handleAnswer();
    }

    public void handleAnswer() {
        mv.visitInsn(ARETURN);
    }

    public void emitInitTemporaries(int index) {
        pushContext();
        pushIntConst(index);
        mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "initTemporaries", "(I)V", false);
    }

    private void emit(Vector<Message> messages) {
        int index = 0;
        int size = messages.size();
        while (index < size)
            if (messages.get(index).isAssignment())
                index = emitAssignment(index, messages);
            else
                emit(messages.get(index++));
    }

    private int emitAssignment(int index, Vector<Message> messages) {
        if (isTraceEnabled(LOG))
            LOG.trace(trace(messages.get(index).receiver()));
        EmitterNode receiver = messages.get(index).receiver();
        if (receiver.type() != SYNTHETIC_TEMPORARY)
            throw new RuntimeException("Attempt to assign to non-temporary.");
        if ((messages.size() - index) < 2)
            throw new RuntimeException("Missing assignment expression to temporary.");
        TerminalNode node = receiver.value();
        visitLine(mv, node.getSymbol().getLine());
        pushContext();
        pushIntConst(receiver.index());
        pushIntConst(receiver.index());
        index++;
        // Emit first part of right hand side of assignment.
        emit(messages.get(index));
        index++;
        // Emit rest of the right hand side of assignment.
        while (messages.get(index).isTail())
            emit(messages.get(index++));
        // Do assignment here.
        mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "temporaryAtPut",
                "(ILst/redline/kernel/PrimObject;)Lst/redline/kernel/PrimObject;", false);
        return index;
    }

    private void emit(Message message) {
        if (message.isToJVM())
            emitInlineJVMInstruction(message);
        else {
            // Before each Message we pop the top value off the stack, as it may
            // be the value left from a previous message send.
            // UNLESS the message is a messageTail in which case we leave it.
            if (!message.isTail()) {
                emitPop();
                emitReceiver(message.receiver());
            } else if (message.isCascade())
                emitDup();

            emitArguments(message.arguments());
            emitSelector(message.selectors());
            if (message.selectors().size() != 0)
                emitPerform(message.arguments().size());

            if (message.isTail() && message.isCascade())
                emitPop();

            // record the block answer names for next 'perform:'
            // - we see the block answer names before we see a selector performed.
            if (message.hasBlockAnswer())
                blockAnswerNames.addAll(message.blockAnswerNames());
        }
    }

    private void emitInlineJVMInstruction(Message message) {
        TerminalNode firstSelector = message.selectors().get(0).value();
        if (isTraceEnabled(LOG))
            LOG.trace(firstSelector.getText());
        visitLine(mv, message.isCascade() ? firstSelector.getSymbol().getLine()
                : message.receiver().value().getSymbol().getLine());
        switch (firstSelector.getText()) {
        case "fieldInsn:": {
            if (message.selectors().size() != 4 || message.arguments().size() != 4)
                throw new RuntimeException("JVM fieldInsn expects 4 keywords and 4 arguments.");
            List<EmitterNode> arguments = message.arguments();
            int opcode = toFieldOpcode(arguments.get(0).text());
            String owner = arguments.get(1).text();
            String name = arguments.get(2).text();
            String descr = arguments.get(3).text();
            mv.visitFieldInsn(opcode, unquote(owner), unquote(name), unquote(descr));
        }
            break;
        case "insn:": {
            if (message.selectors().size() != 1 || message.arguments().size() != 1)
                throw new RuntimeException("JVM insn expects 1 keyword and 1 argument.");
            List<EmitterNode> arguments = message.arguments();
            int opcode = toInsnOpcode(arguments.get(0).text());
            mv.visitInsn(opcode);
        }
            break;
        case "ldcInsn:": {
            if (message.selectors().size() != 1 || message.arguments().size() != 1)
                throw new RuntimeException("JVM ldcInsn expects 1 keyword and 1 argument.");
            List<EmitterNode> arguments = message.arguments();
            Object constant = toLdcConstant(arguments.get(0));
            mv.visitLdcInsn(constant);
        }
            break;
        case "methodInsn:": {
            if (message.selectors().size() != 5 || message.arguments().size() != 5)
                throw new RuntimeException("JVM methodInsn expects 5 keywords and 5 arguments.");
            List<EmitterNode> arguments = message.arguments();
            int opcode = toMethodOpcode(arguments.get(0).text());
            String owner = arguments.get(1).text();
            String name = arguments.get(2).text();
            String descr = arguments.get(3).text();
            boolean iface = Boolean.valueOf(arguments.get(4).text());
            mv.visitMethodInsn(opcode, unquote(owner), unquote(name), unquote(descr), iface);
        }
            break;
        case "typeInsn:": {
            if (message.selectors().size() != 2 || message.arguments().size() != 2)
                throw new RuntimeException("JVM typeInsn expects 2 keyword and 2 argument.");
            List<EmitterNode> arguments = message.arguments();
            int opcode = toTypeOpcode(arguments.get(0).text());
            String type = arguments.get(1).text();
            mv.visitTypeInsn(opcode, unquote(type));
        }
            break;
        case "varInsn:": {
            if (message.selectors().size() != 2 || message.arguments().size() != 2)
                throw new RuntimeException("JVM varInsn expects 2 keyword and 2 argument.");
            List<EmitterNode> arguments = message.arguments();
            int opcode = toVarOpcode(arguments.get(0).text());
            int var = Integer.valueOf(arguments.get(1).text());
            mv.visitVarInsn(opcode, var);
        }
            break;
        case "storeVar:": {
            if (message.selectors().size() != 1 || message.arguments().size() != 1)
                throw new RuntimeException("JVM assignTo: expects 1 keyword and 1 argument.");
            List<EmitterNode> arguments = message.arguments();
            EmitterNode var = arguments.get(0);
            switch (var.type()) {
            case SYNTHETIC_TEMPORARY: {
                pushContext();
                mv.visitInsn(SWAP);
                pushIntConst(var.index());
                mv.visitInsn(SWAP);
                mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "temporaryAtPut",
                        "(ILst/redline/kernel/PrimObject;)Lst/redline/kernel/PrimObject;", false);
            }
                break;
            case SYNTHETIC_INSTANCE_VARIABLE:
            case SYNTHETIC_CLASS_VARIABLE:
                throw new RuntimeException("JVM storeVar: does not yest support instance and class variables.");
            default:
                throw new RuntimeException(
                        "JVM storeVar: expects a temp variables, instance variables, or class variables.");
            }
        }
            break;
        case "loadVar:": {
            if (message.selectors().size() != 1 || message.arguments().size() != 1)
                throw new RuntimeException("JVM loadVar: expects 1 keyword and 1 argument.");
            List<EmitterNode> arguments = message.arguments();
            EmitterNode var = arguments.get(0);
            if (var.type() != RESERVED_WORD && (var.type() < EmitterNode.SYNTHETIC_TEMPORARY
                    || var.type() > EmitterNode.SYNTHETIC_REFERENCE))
                throw new RuntimeException(
                        "JVM loadVar: expects a global references, args, temp variables, instance variables, class variables, and pseudo variables.");
            TerminalNode node = var.value();
            emitObject(var, node);
        }
            break;
        default:
            throw new RuntimeException("JVM instruction '" + firstSelector.getText() + "' not currently handled.");
        }
    }

    private String unquote(String string) {
        if (string.startsWith("'"))
            return string.substring(1, string.length() - 1);
        else if (string.equals("nil"))
            return null;
        return string;
    }

    private Number javaNumber(String string) {
        if (isTraceEnabled(LOG))
            LOG.warn("Only Integer and Float supported right now.");
        if (string.contains("."))
            return Float.valueOf(string);
        else if (string.startsWith("16r"))
            return Integer.valueOf(string.substring(3), 16);
        else
            return Integer.valueOf(string);
    }

    private Object toLdcConstant(EmitterNode emitterNode) {
        switch (emitterNode.type()) {
        case STRING:
        case HASH:
            return unquote(emitterNode.text());
        case DIGIT:
        case HEX:
            return javaNumber(emitterNode.text());
        case CHARACTER_CONSTANT:
            return emitterNode.text().charAt(1);
        case RESERVED_WORD:
            return Boolean.valueOf(emitterNode.text());
        default:
            throw new RuntimeException("JVM constant type not recognized." + emitterNode.type());
        }
    }

    private int toInsnOpcode(String text) {
        String code = text.toUpperCase();
        switch (code) {
        case "DUP":
            return DUP;
        case "POP":
            return POP;
        case "ARETURN":
            return ARETURN;
        default:
            throw new RuntimeException("JVM insn opcode '" + code + "' not recognized.");
        }
    }

    private int toTypeOpcode(String text) {
        String code = text.toUpperCase();
        switch (code) {
        case "NEW":
            return NEW;
        case "CHECKCAST":
            return CHECKCAST;
        default:
            throw new RuntimeException("JVM type opcode '" + code + "' not recognized.");
        }
    }

    private int toVarOpcode(String text) {
        String code = text.toUpperCase();
        switch (code) {
        case "ALOAD":
            return ALOAD;
        case "ASTORE":
            return ASTORE;
        default:
            throw new RuntimeException("JVM var opcode '" + code + "' not recognized.");
        }
    }

    private int toFieldOpcode(String text) {
        String code = text.toUpperCase();
        switch (code) {
        case "GETSTATIC":
            return GETSTATIC;
        case "PUTSTATIC":
            return PUTSTATIC;
        case "GETFIELD":
            return GETFIELD;
        case "PUTFIELD":
            return PUTFIELD;
        default:
            throw new RuntimeException("JVM field opcode '" + code + "' not recognized.");
        }
    }

    private int toMethodOpcode(String text) {
        String code = text.toUpperCase();
        switch (code) {
        case "INVOKEVIRTUAL":
            return INVOKEVIRTUAL;
        case "INVOKESPECIAL":
            return INVOKESPECIAL;
        case "INVOKESTATIC":
            return INVOKESTATIC;
        case "INVOKEINTERFACE":
            return INVOKEINTERFACE;
        default:
            throw new RuntimeException("JVM method opcode '" + code + "' not recognized.");
        }
    }

    private void emitReceiver(EmitterNode receiver) {
        if (!receiver.isList())
            emitObject(receiver, receiver.value());
        else
            emitObject(receiver, receiver.values());
    }

    private void emitObject(EmitterNode emitterNode, TerminalNode node) {
        visitLine(mv, node.getSymbol().getLine());
        switch (emitterNode.type()) {
        case STRING:
            emitCreateString(removeSingleQuotes(node.getText()));
            break;
        case HASH:
            emitCreateSymbol(node.getText());
            break;
        case CHARACTER_CONSTANT:
            emitCreateCharacter(removeLeadingChar(node.getText()));
            break;
        case RESERVED_WORD:
            emitPseudoVariable(node.getText());
            break;
        case SYNTHETIC_TEMPORARY:
            emitTemporaryGet(node, emitterNode.index());
            break;
        case SYNTHETIC_ARGUMENT:
            emitArgumentGet(node, emitterNode.index());
            break;
        case SYNTHETIC_REFERENCE:
            emitResolveReference(node.getText());
            break;
        case SYNTHETIC_BLOCK_CREATE:
            emitResolveBlock(node, emitterNode);
            break;
        case SYNTHETIC_METHOD_CREATE:
            emitResolveMethod(node, emitterNode);
            break;
        default:
            throw new RuntimeException("Unknown Emitter Type: " + emitterNode.type());
        }
    }

    private void emitObject(EmitterNode emitterNode, List<TerminalNode> nodes) {
        visitLine(mv, nodes.get(0).getSymbol().getLine());
        switch (emitterNode.type()) {
        case HASH:
            emitCreateSymbol(concatText(nodes));
            break;
        case DIGIT:
            emitCreateInteger(concatText(nodes));
            break;
        default:
            throw new RuntimeException("Unknown Emitter Type: " + emitterNode.type());
        }
    }

    private void emitResolveBlock(TerminalNode node, EmitterNode emitterNode) {
        if (isTraceEnabled(LOG))
            LOG.trace("resolveBlock: " + emitterNode.index());
        emitResolveMethodOrBlock("st/redline/kernel/PrimBlock", node, emitterNode);
    }

    private void emitResolveMethod(TerminalNode node, EmitterNode emitterNode) {
        if (isTraceEnabled(LOG))
            LOG.trace("resolveMethod: " + emitterNode.index());
        emitResolveMethodOrBlock("st/redline/kernel/PrimMethod", node, emitterNode);
    }

    private void emitResolveMethodOrBlock(String type, TerminalNode node, EmitterNode emitterNode) {
        String blockName = makeBlockName(emitterNode.index());
        visitLine(mv, node.getSymbol().getLine());
        mv.visitTypeInsn(NEW, type);
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, type, "<init>", "()V", false);
        mv.visitInvokeDynamicInsn("apply", "()Lst/redline/kernel/TriFunction;", new Handle(Opcodes.H_INVOKESTATIC,
                "java/lang/invoke/LambdaMetafactory", "metafactory",
                "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"),
                new Object[] {
                        Type.getType("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
                        new Handle(Opcodes.H_INVOKESTATIC, source.fullClassName(), blockName,
                                "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimContext;)Lst/redline/kernel/PrimObject;"),
                        Type.getType(
                                "(Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimObject;Lst/redline/kernel/PrimContext;)Lst/redline/kernel/PrimObject;") });
        mv.visitMethodInsn(INVOKEVIRTUAL, type, "function",
                "(Lst/redline/kernel/TriFunction;)Lst/redline/kernel/PrimObject;", false);
    }

    private String makeBlockName(int index) {
        return "Block" + index;
    }

    private void emitResolveReference(String value) {
        if (isTraceEnabled(LOG))
            LOG.trace("resolve: " + value + " for: " + source.className() + " in: " + source.packageName());
        emitSmalltalkCall("resolve", value, source.className(), source.packageName());
    }

    private void emitSelector(List<EmitterNode> selectors) {
        if (selectors.isEmpty())
            return;
        TerminalNode firstSelector = selectors.get(0).value();
        visitLine(mv, firstSelector.getSymbol().getLine());
        String selector = "";
        for (EmitterNode node : selectors)
            selector = selector + node.text();
        mv.visitLdcInsn(selector);
    }

    private void emitPerform(int argumentCount) {
        emitBlockAnswerTry();
        String method = sendToSuper ? "superPerform" : "perform";
        sendToSuper = false;
        mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimObject", method, SIGNATURES[argumentCount], false);
        emitBlockAnswerCatch();
    }

    private void emitArguments(List<EmitterNode> arguments) {
        if (arguments.isEmpty())
            return;
        for (EmitterNode node : arguments)
            if (node.value() != null)
                emitObject(node, node.value());
            else
                emitObject(node, node.values());
    }

    private String concatText(List<TerminalNode> nodes) {
        String text = "";
        for (TerminalNode node : nodes)
            text = text + node.getText();
        return text;
    }

    private void emitPseudoVariable(String value) {
        switch (value) {
        case "self":
            emitPushReceiver();
            break;
        case "true":
        case "false":
            emitSmalltalkCall("booleanSingleton", value);
            break;
        case "nil":
            emitSmalltalkCall("nilSingleton", value);
            break;
        case "super":
            sendToSuper = true;
            break;
        default:
            throw new RuntimeException("Unhandled Pseudo Variable: " + value);
        }
    }

    private void emitTemporaryGet(TerminalNode node, int index) {
        visitLine(mv, node.getSymbol().getLine());
        pushContext();
        pushIntConst(index);
        mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "temporaryAt",
                "(I)Lst/redline/kernel/PrimObject;", false);
    }

    private void emitArgumentGet(TerminalNode node, int index) {
        visitLine(mv, node.getSymbol().getLine());
        pushContext();
        pushIntConst(index);
        mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "argumentAt",
                "(I)Lst/redline/kernel/PrimObject;", false);
    }

    private void emitDup() {
        mv.visitInsn(DUP);
    }

    private void emitPop() {
        mv.visitInsn(POP);
    }

    private void emitPushReceiver() {
        mv.visitVarInsn(ALOAD, 1);
    }

    private void pushContext() {
        mv.visitVarInsn(ALOAD, 2);
    }

    private void emitCreateCharacter(String value) {
        emitSmalltalkCall("createCharacter", value);
    }

    private void emitCreateInteger(String value) {
        emitSmalltalkCall("createInteger", value);
    }

    private void emitCreateSymbol(String value) {
        emitSmalltalkCall("createSymbol", value);
    }

    private void emitCreateString(String value) {
        emitSmalltalkCall("createString", value);
    }

    private void emitSmalltalkCall(String method, String value) {
        pushSmalltalk();
        mv.visitLdcInsn(value);
        mv.visitMethodInsn(INVOKEINTERFACE, "st/redline/Smalltalk", method,
                "(Ljava/lang/String;)Lst/redline/kernel/PrimObject;", true);
    }

    private void emitSmalltalkCall(String method, String value1, String value2, String value3) {
        pushSmalltalk();
        mv.visitLdcInsn(value1);
        mv.visitLdcInsn(value2);
        mv.visitLdcInsn(value3);
        mv.visitMethodInsn(INVOKEINTERFACE, "st/redline/Smalltalk", method,
                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lst/redline/kernel/PrimObject;", true);
    }

    private void emitBlockAnswerTry() {
        if (blockAnswerNames.empty())
            return;
        if (isTraceEnabled(LOG))
            LOG.trace(blockAnswerNames);
        blockTryStartLabel = new Label();
        blockTryEndLabel = new Label();
        blockCatchLabel = new Label();
        for (String blockAnswerName : blockAnswerNames)
            mv.visitTryCatchBlock(blockTryStartLabel, blockTryEndLabel, blockCatchLabel, blockAnswerName);
        mv.visitLabel(blockTryStartLabel);
    }

    private void emitBlockAnswerCatch() {
        if (blockAnswerNames.empty())
            return;
        if (isTraceEnabled(LOG))
            LOG.trace(blockAnswerNames);
        for (String blockAnswerName : blockAnswerNames) {
            mv.visitJumpInsn(GOTO, blockTryEndLabel);
            mv.visitLabel(blockCatchLabel);
            mv.visitMethodInsn(INVOKEVIRTUAL, blockAnswerName, "answer", "()Lst/redline/kernel/PrimObject;", false);
        }
        mv.visitLabel(blockTryEndLabel);
    }

    private String removeLeadingChar(String text) {
        return text.substring(1, text.length());
    }

    private String removeSingleQuotes(String text) {
        return text.substring(1, text.length() - 1);
    }

    private void pushSmalltalk() {
        pushContext();
        mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "smalltalk", "()Lst/redline/Smalltalk;",
                false);
    }

    private void closePrivateSendMessagesMethod(boolean returnRequired) {
        if (isTraceEnabled(LOG))
            LOG.trace("");
        if (returnRequired) {
            mv.visitInsn(ARETURN);
        }
        mv.visitMaxs(1, 4);
        mv.visitEnd();
    }

    private void visitLine(MethodVisitor mv, int line) {
        // We adjust the line number as the pre-processor may have prepended source lines.
        int adjustedSourceLine = line - source.countOfLinesAddedByPreprocessor();
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitLineNumber(adjustedSourceLine, l0);
    }

    private void pushIntConst(int value) {
        switch (value) {
        case 0:
            mv.visitInsn(ICONST_0);
            break;
        case 1:
            mv.visitInsn(ICONST_1);
            break;
        case 2:
            mv.visitInsn(ICONST_2);
            break;
        case 3:
            mv.visitInsn(ICONST_3);
            break;
        case 4:
            mv.visitInsn(ICONST_4);
            break;
        case 5:
            mv.visitInsn(ICONST_5);
            break;
        default:
            if (value > 5 && value < 128)
                mv.visitIntInsn(BIPUSH, value);
            else // SIPUSH not supported yet.
                throw new IllegalStateException("Push of integer value " + value + " not yet supported.");
        }
    }

    private class BlockByteCodeEmitter extends ByteCodeEmitter {

        private String blockName;
        private String blockAnswerClassName = null;
        private boolean isMethodBlock;

        BlockByteCodeEmitter(Source source, ClassWriter cw) {
            this.source = source;
            this.cw = cw;
        }

        @Override
        public void openBlock(int blockId, boolean isMethodBlock) {
            if (isTraceEnabled(LOG))
                LOG.trace(blockId);
            this.blockName = makeBlockName(blockId);
            this.isMethodBlock = isMethodBlock;
            mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, blockName, METHOD_SIG, null, null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 1);

            //            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            //            mv.visitLdcInsn(isMethodBlock ? "inside Smalltalk method" : "inside Smalltalk Block");
            //            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
        }

        @Override
        public String closeBlock(int blockId) {
            if (isTraceEnabled(LOG))
                LOG.trace(blockId);
            mv.visitInsn(ARETURN);
            mv.visitMaxs(1, 3);
            mv.visitEnd();
            return blockAnswerClassName;
        }

        public void handleAnswer() {
            blockAnswerClassName = source.packageName().replaceAll("\\.", "/") + '/' + source.className()
                    + blockName;
            if (!isMethodBlock) {
                mv.visitInsn(DUP);
                createBlockAnswerThrow();
                createBlockAnswerClass();
            } else {
                mv.visitInsn(ARETURN);
            }
        }

        private void createBlockAnswerClass() {
            ClassWriter cw = new ClassWriter(0);
            cw.visit(52, ACC_PUBLIC + ACC_SUPER, blockAnswerClassName, null, "st/redline/kernel/PrimBlockAnswer",
                    null);
            MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(Lst/redline/kernel/PrimObject;)V", null,
                    null);
            mv.visitCode();
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKESPECIAL, "st/redline/kernel/PrimBlockAnswer", "<init>",
                    "(Lst/redline/kernel/PrimObject;)V", false);
            mv.visitInsn(RETURN);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
            cw.visitEnd();
            byte[] classData = cw.toByteArray();
            classLoader().defineBlockClass(null, classData, 0, classData.length);
        }

        private SmalltalkClassLoader classLoader() {
            return (SmalltalkClassLoader) Thread.currentThread().getContextClassLoader();
        }

        private void createBlockAnswerThrow() {
            mv.visitVarInsn(ASTORE, 3);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitVarInsn(ALOAD, 3);
            mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "blockAnswer",
                    "(Lst/redline/kernel/PrimObject;)V", false);
            mv.visitTypeInsn(NEW, blockAnswerClassName);
            mv.visitInsn(DUP);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "st/redline/kernel/PrimContext", "blockAnswer",
                    "()Lst/redline/kernel/PrimObject;", false);
            mv.visitMethodInsn(INVOKESPECIAL, blockAnswerClassName, "<init>", "(Lst/redline/kernel/PrimObject;)V",
                    false);
            mv.visitInsn(ATHROW);
        }
    }
}