All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sandius.rembulan.compiler.gen.asm.BytecodeEmitVisitor Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2016 Miroslav Janíček
 *
 * 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 net.sandius.rembulan.compiler.gen.asm;

import net.sandius.rembulan.ByteString;
import net.sandius.rembulan.Table;
import net.sandius.rembulan.Variable;
import net.sandius.rembulan.compiler.CompilerSettings;
import net.sandius.rembulan.compiler.FunctionId;
import net.sandius.rembulan.compiler.analysis.SlotAllocInfo;
import net.sandius.rembulan.compiler.analysis.TypeInfo;
import net.sandius.rembulan.compiler.gen.ClassNameTranslator;
import net.sandius.rembulan.compiler.gen.asm.helpers.ASMUtils;
import net.sandius.rembulan.compiler.gen.asm.helpers.BoxedPrimitivesMethods;
import net.sandius.rembulan.compiler.gen.asm.helpers.ConversionMethods;
import net.sandius.rembulan.compiler.gen.asm.helpers.DispatchMethods;
import net.sandius.rembulan.compiler.gen.asm.helpers.ExecutionContextMethods;
import net.sandius.rembulan.compiler.gen.asm.helpers.ReturnBufferMethods;
import net.sandius.rembulan.compiler.gen.asm.helpers.TableMethods;
import net.sandius.rembulan.compiler.gen.asm.helpers.VariableMethods;
import net.sandius.rembulan.compiler.ir.*;
import net.sandius.rembulan.runtime.ExecutionContext;
import net.sandius.rembulan.runtime.ReturnBuffer;
import net.sandius.rembulan.util.Check;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import static net.sandius.rembulan.compiler.gen.asm.helpers.DispatchMethods.*;
import static org.objectweb.asm.Opcodes.*;

class BytecodeEmitVisitor extends CodeVisitor {

	private final ASMBytecodeEmitter context;
	private final RunMethod runMethod;

	private final SlotAllocInfo slots;
	private final TypeInfo types;

	private final int segmentIdx;
	private final RunMethod.LabelResolver resolver;

	final Map labels;
	private final ArrayList resumptionPoints;

	private final InsnList il;
	private final List locals;

	private final List instanceLevelClosures;

	private final List constFields;

	public BytecodeEmitVisitor(
			ASMBytecodeEmitter context,
			RunMethod runMethod,
			SlotAllocInfo slots,
			TypeInfo types,
			List instanceLevelClosures,
			List constFields,
			int segmentIdx,
			RunMethod.LabelResolver resolver) {

		this.context = Check.notNull(context);
		this.runMethod = Check.notNull(runMethod);
		this.slots = Check.notNull(slots);
		this.types = Check.notNull(types);

		this.segmentIdx = segmentIdx;
		this.resolver = Check.notNull(resolver);

		this.labels = new HashMap<>();
		this.resumptionPoints = new ArrayList<>();

		this.il = new InsnList();
		this.locals = new ArrayList<>();

		this.instanceLevelClosures = Check.notNull(instanceLevelClosures);
		this.constFields = Check.notNull(constFields);
	}

	private boolean isSub() {
		return segmentIdx >= 0;
	}

	public InsnList instructions() {
		return il;
	}

	public List locals() {
		return locals;
	}

	public List instanceLevelClosures() {
		return instanceLevelClosures;
	}

	public List constFields() {
		return constFields;
	}

	protected int slot(AbstractVal v) {
		return runMethod.slotOffset() + slots.slotOf(v);
	}

	protected int slot(Var v) {
		return runMethod.slotOffset() + slots.slotOf(v);
	}

	protected int nextLocalVariableIndex() {
		return runMethod.slotOffset() + slots.numSlots();
	}

	private LabelNode l(Object o) {
		LabelNode l = labels.get(o);

		if (l != null) {
			return l;
		}
		else {
			LabelNode nl = new LabelNode();
			labels.put(o, nl);
			return nl;
		}
	}

	private RunMethod.ConstFieldInstance newConstFieldInstance(final Object constValue, int idx) {
		Check.notNull(constValue);

		String fieldName = "_k_" + idx;

		final Type t;
		if (constValue instanceof Double || constValue instanceof Long) {
			t = Type.getType(constValue.getClass());
			return new RunMethod.ConstFieldInstance(constValue, fieldName, context.thisClassType(), t) {
				@Override
				public void doInstantiate(InsnList il) {
					il.add(BoxedPrimitivesMethods.loadBoxedConstant(constValue));
				}
			};
		}
		else if (constValue instanceof ByteString) {
			t = Type.getType(constValue.getClass());
			return new RunMethod.ConstFieldInstance(constValue, fieldName, context.thisClassType(), Type.getType(ByteString.class)) {
				@Override
				public void doInstantiate(InsnList il) {
					il.add(newByteString((ByteString) constValue));
				}
			};
		}
		else {
			throw new UnsupportedOperationException("Illegal constant: " + constValue);
		}

	}

	private static InsnList newByteString(ByteString value) {
		InsnList il = new InsnList();
		il.add(new LdcInsnNode(value.toRawString()));
		il.add(new MethodInsnNode(INVOKESTATIC,
				Type.getInternalName(ByteString.class),
				"fromRaw",
				Type.getMethodDescriptor(
						Type.getType(ByteString.class),
						Type.getType(String.class)),
				false));
		return il;
	}

	private InsnList loadCachedConst(Object constValue) {
		for (RunMethod.ConstFieldInstance cfi : constFields) {
			if (cfi.value().equals(constValue)) {
				return cfi.accessInsns();
			}
		}

		RunMethod.ConstFieldInstance cfi = newConstFieldInstance(constValue, constFields.size());
		constFields.add(cfi);
		return cfi.accessInsns();
	}

	public AbstractInsnNode loadExecutionContext() {
		return new VarInsnNode(ALOAD, runMethod.LV_CONTEXT);
	}

	static AbstractInsnNode loadReturnBuffer() {
		return new MethodInsnNode(
				INVOKEINTERFACE,
				Type.getInternalName(ExecutionContext.class),
				"getReturnBuffer",
				Type.getMethodDescriptor(
						Type.getType(ReturnBuffer.class)),
				true);
	}

	public InsnList retrieve_0() {
		InsnList il = new InsnList();

		il.add(loadExecutionContext());
		il.add(loadReturnBuffer());
		il.add(ReturnBufferMethods.get(0));

		return il;
	}

	public InsnList loadUpvalueRef(UpVar uv) {
		InsnList il = new InsnList();

		il.add(new VarInsnNode(ALOAD, 0));
		il.add(new FieldInsnNode(
				GETFIELD,
				context.thisClassType().getInternalName(),
				context.getUpvalueFieldName(uv),
				Type.getDescriptor(Variable.class)));

		return il;
	}

	private InsnList saveState(int state) {
		InsnList il = new InsnList();
		il.add(ASMUtils.loadInt(state));
		il.add(new VarInsnNode(ISTORE, runMethod.LV_RESUME));
		return il;
	}

	public void visitBlocks(List blocks) {
		Iterator it = blocks.iterator();
		while (it.hasNext()) {
			BasicBlock b = it.next();
			visit(b);
		}
	}

	class ResumptionPoint {

		public final int index;

		private ResumptionPoint(int index) {
			this.index = index;
		}

		public LabelNode label() {
			return l(this);
		}

		public InsnList save() {
			int st = !isSub()
					? index + 1
					: segmentIdx << RunMethod.ST_SHIFT_SEGMENT | (index + 1);
			return saveState(st);
		}

		public InsnList resume() {
			InsnList il = new InsnList();

			il.add(label());
			il.add(ASMUtils.frameSame());

			return il;
		}
	}

	protected ResumptionPoint newResumptionPoint() {
		int idx = resumptionPoints.size();
		ResumptionPoint rp = new ResumptionPoint(idx);
		resumptionPoints.add(rp.label());
		return rp;
	}

	public boolean isResumable() {
		return isSub() || resumptionPoints.size() > 0;
	}

	public List resumptionLabels() {
		return resumptionPoints;
	}

	private InsnList _return() {
		InsnList il = new InsnList();
		if (!isSub()) {
			il.add(new InsnNode(RETURN));
		}
		else {
			il.add(new InsnNode(ACONST_NULL));
			il.add(new InsnNode(ARETURN));
		}
		return il;
	}

	private InsnList _nonLocalGoto(Label label) {
		InsnList il = new InsnList();
		int st = resolver.labelStateIndex(label);
		il.add(saveState(st));
		il.add(runMethod.createSnapshot());
		il.add(new InsnNode(ARETURN));
		return il;
	}

	private InsnList _goto(Label label) {
		InsnList il = new InsnList();
		if (!isSub() || resolver.isLocalLabel(label)) {
			il.add(new JumpInsnNode(GOTO, l(label)));
		}
		else {
			il.add(_nonLocalGoto(label));
		}
		return il;
	}

	private InsnList _next(Label label) {
		InsnList il = new InsnList();
		if (!isSub() || resolver.isLocalLabel(label)) {
			// no-op
		}
		else {
			il.add(_nonLocalGoto(label));
		}
		return il;
	}

	@Override
	public void visit(PhiStore node) {
		il.add(new VarInsnNode(ALOAD, slot(node.src())));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(PhiLoad node) {
		il.add(new VarInsnNode(ALOAD, slot(node.src())));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(VarInit node) {
		if (types.isReified(node.var())) {
			il.add(new TypeInsnNode(NEW, Type.getInternalName(Variable.class)));
			il.add(new InsnNode(DUP));
			il.add(new VarInsnNode(ALOAD, slot(node.src())));
			il.add(VariableMethods.constructor());
			il.add(new VarInsnNode(ASTORE, slot(node.var())));
		}
		else {
			il.add(new VarInsnNode(ALOAD, slot(node.src())));
			il.add(new VarInsnNode(ASTORE, slot(node.var())));
		}
	}

	@Override
	public void visit(VarStore node) {
		if (types.isReified(node.var())) {
			il.add(new VarInsnNode(ALOAD, slot(node.var())));
			il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Variable.class)));
			il.add(new VarInsnNode(ALOAD, slot(node.src())));
			il.add(VariableMethods.set());
		}
		else {
			il.add(new VarInsnNode(ALOAD, slot(node.src())));
			il.add(new VarInsnNode(ASTORE, slot(node.var())));
		}
	}

	@Override
	public void visit(VarLoad node) {
		if (types.isReified(node.var())) {
			il.add(new VarInsnNode(ALOAD, slot(node.var())));
			il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Variable.class)));
			il.add(VariableMethods.get());
		}
		else {
			il.add(new VarInsnNode(ALOAD, slot(node.var())));
		}
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(UpLoad node) {
		il.add(loadUpvalueRef(node.upval()));
		il.add(VariableMethods.get());
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(UpStore node) {
		il.add(loadUpvalueRef(node.upval()));
		il.add(new VarInsnNode(ALOAD, slot(node.src())));
		il.add(VariableMethods.set());
	}

	@Override
	public void visit(LoadConst.Nil node) {
		il.add(new InsnNode(ACONST_NULL));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(LoadConst.Bool node) {
		il.add(BoxedPrimitivesMethods.loadBoxedBoolean(node.value()));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(LoadConst.Int node) {
		if (context.compilerSettings.constCaching()) {
			il.add(loadCachedConst(node.value()));
		}
		else {
			il.add(ASMUtils.loadLong(node.value()));
			il.add(BoxedPrimitivesMethods.box(Type.LONG_TYPE, Type.getType(Long.class)));
		}
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(LoadConst.Flt node) {
		if (context.compilerSettings.constCaching()) {
			il.add(loadCachedConst(node.value()));
		}
		else {
			il.add(ASMUtils.loadDouble(node.value()));
			il.add(BoxedPrimitivesMethods.box(Type.DOUBLE_TYPE, Type.getType(Double.class)));
		}
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(LoadConst.Str node) {
		// use byte strings?
		if (context.compilerSettings.byteStrings()) {
			if (context.compilerSettings.constCaching()) {
				il.add(loadCachedConst(node.value()));
			}
			else {
				il.add(newByteString(node.value()));
			}
		}
		else {
			// java.lang.String
			il.add(new LdcInsnNode(node.value()));
		}

		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	private static String dispatchMethodName(BinOp.Op op) {
		switch (op) {
			case ADD:    return OP_ADD;
			case SUB:    return OP_SUB;
			case MUL:    return OP_MUL;
			case MOD:    return OP_MOD;
			case POW:    return OP_POW;
			case DIV:    return OP_DIV;
			case IDIV:   return OP_IDIV;
			case BAND:   return OP_BAND;
			case BOR:    return OP_BOR;
			case BXOR:   return OP_BXOR;
			case SHL:    return OP_SHL;
			case SHR:    return OP_SHR;

			case CONCAT: return OP_CONCAT;

			case EQ:     return OP_EQ;
			case NEQ:    return OP_NEQ;
			case LT:     return OP_LT;
			case LE:     return OP_LE;

			default:     throw new IllegalArgumentException("Illegal binary operation: " + op);
		}
	}

	private static String dispatchMethodName(UnOp.Op op) {
		switch (op) {
			case UNM:  return OP_UNM;
			case BNOT: return OP_BNOT;
			case LEN:  return OP_LEN;
			default:   throw new IllegalArgumentException("Illegal unary operation: " + op);
		}
	}

	@Override
	public void visit(BinOp node) {
		ResumptionPoint rp = newResumptionPoint();
		il.add(rp.save());

		il.add(loadExecutionContext());
		il.add(new VarInsnNode(ALOAD, slot(node.left())));
		il.add(new VarInsnNode(ALOAD, slot(node.right())));
		il.add(DispatchMethods.dynamic(dispatchMethodName(node.op()), 2));

		il.add(rp.resume());
		il.add(retrieve_0());
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(UnOp node) {
		if (node.op() == UnOp.Op.NOT) {
			il.add(new VarInsnNode(ALOAD, slot(node.arg())));
			il.add(ConversionMethods.booleanValueOf());
			il.add(new InsnNode(ICONST_1));
			il.add(new InsnNode(IXOR));
			il.add(BoxedPrimitivesMethods.box(Type.BOOLEAN_TYPE, Type.getType(Boolean.class)));
		}
		else {
			ResumptionPoint rp = newResumptionPoint();
			il.add(rp.save());

			il.add(loadExecutionContext());
			il.add(new VarInsnNode(ALOAD, slot(node.arg())));
			il.add(DispatchMethods.dynamic(dispatchMethodName(node.op()), 1));

			il.add(rp.resume());
			il.add(retrieve_0());
		}

		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(TabNew node) {
		il.add(loadExecutionContext());
		il.add(ExecutionContextMethods.newTable(node.array(), node.hash()));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(TabGet node) {
		ResumptionPoint rp = newResumptionPoint();
		il.add(rp.save());

		il.add(loadExecutionContext());
		il.add(new VarInsnNode(ALOAD, slot(node.obj())));
		il.add(new VarInsnNode(ALOAD, slot(node.key())));
		il.add(DispatchMethods.index());

		il.add(rp.resume());
		il.add(retrieve_0());
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(TabSet node) {
		ResumptionPoint rp = newResumptionPoint();
		il.add(rp.save());

		il.add(loadExecutionContext());
		il.add(new VarInsnNode(ALOAD, slot(node.obj())));
		il.add(new VarInsnNode(ALOAD, slot(node.key())));
		il.add(new VarInsnNode(ALOAD, slot(node.value())));
		il.add(DispatchMethods.setindex());

		il.add(rp.resume());
	}

	@Override
	public void visit(TabRawSet node) {
		il.add(new VarInsnNode(ALOAD, slot(node.obj())));
		il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Table.class)));
		il.add(new VarInsnNode(ALOAD, slot(node.key())));
		il.add(new VarInsnNode(ALOAD, slot(node.value())));
		il.add(TableMethods.rawset());
	}

	@Override
	public void visit(TabRawSetInt node) {
		il.add(new VarInsnNode(ALOAD, slot(node.obj())));
		il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Table.class)));
		il.add(ASMUtils.loadLong(node.idx()));
		il.add(new VarInsnNode(ALOAD, slot(node.value())));
		il.add(TableMethods.rawset_int());
	}

	@Override
	public void visit(TabRawAppendMulti node) {
		/*
		 In Java terms, we're translating this into the following loop:

			Table tab;
			ReturnBuffer rbuf = context.getReturnBuffer();
			int i = 0;
			context.registerTicks(rbuf.size());  // only when we care about ticks spent
			while (i < rbuf.size()) {
				tab.rawset(OFFSET + i, rbuf.get(i));
				i++;
			}
		*/

		LabelNode begin = new LabelNode();
		LabelNode end = new LabelNode();
		LabelNode top = new LabelNode();

		int lv_idx_tab = nextLocalVariableIndex();
		int lv_idx_stack = nextLocalVariableIndex() + 1;
		int lv_idx_i = nextLocalVariableIndex() + 2;

		locals.add(new LocalVariableNode("tab", Type.getDescriptor(Table.class), null, begin, end, lv_idx_tab));
		locals.add(new LocalVariableNode("rbuf", Type.getDescriptor(ReturnBuffer.class), null, begin, end, lv_idx_stack));
		locals.add(new LocalVariableNode("i", Type.INT_TYPE.getDescriptor(), null, begin, end, lv_idx_i));

		il.add(begin);

		il.add(new VarInsnNode(ALOAD, slot(node.obj())));
		il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Table.class)));
		il.add(new VarInsnNode(ASTORE, lv_idx_tab));

		il.add(loadExecutionContext());
		il.add(loadReturnBuffer());
		il.add(new VarInsnNode(ASTORE, lv_idx_stack));

		il.add(ASMUtils.loadInt(0));
		il.add(new VarInsnNode(ISTORE, lv_idx_i));

		// context.registerTicks(rbuf.size());
		if (countingTicks()) {
			il.add(loadExecutionContext());
			il.add(new VarInsnNode(ALOAD, lv_idx_stack));
			il.add(ReturnBufferMethods.size());
			il.add(ExecutionContextMethods.registerTicks());
		}

		il.add(top);
		il.add(new FrameNode(F_APPEND, 3, new Object[] {
					Type.getInternalName(Table.class),
					Type.getInternalName(ReturnBuffer.class),
					Opcodes.INTEGER
				}, 0, null));

		il.add(new VarInsnNode(ILOAD, lv_idx_i));
		il.add(new VarInsnNode(ALOAD, lv_idx_stack));
		il.add(ReturnBufferMethods.size());
		il.add(new JumpInsnNode(IF_ICMPGE, end));

		il.add(new VarInsnNode(ALOAD, lv_idx_tab));

		// OFFSET + i
		il.add(ASMUtils.loadLong(node.firstIdx()));
		il.add(new VarInsnNode(ILOAD, lv_idx_i));
		il.add(new InsnNode(I2L));
		il.add(new InsnNode(LADD));

		// stack.get(i)
		il.add(new VarInsnNode(ALOAD, lv_idx_stack));
		il.add(new VarInsnNode(ILOAD, lv_idx_i));
		il.add(ReturnBufferMethods.get());

		// tab.rawset(offset + i, stack.get(i))
		il.add(TableMethods.rawset_int());

		// increment i
		il.add(new IincInsnNode(lv_idx_i, 1));

		il.add(new JumpInsnNode(GOTO, top));


		il.add(end);
		il.add(new FrameNode(F_CHOP, 3, null, 0, null));
	}

	@Override
	public void visit(Vararg node) {
		il.add(loadExecutionContext());
		il.add(loadReturnBuffer());
		il.add(new VarInsnNode(ALOAD, runMethod.LV_VARARGS));
		il.add(ReturnBufferMethods.setTo(0));
	}

	private int loadVList(VList vl, int maxKind) {
		if (vl.isMulti()) {
			// variable number of arguments, stored on stack

			if (vl.addrs().size() == 0) {
				// no prefix, simply take the stack contents as an array
				il.add(loadExecutionContext());
				il.add(loadReturnBuffer());
				il.add(ReturnBufferMethods.toArray());
				return 0;
			}
			else {
				// a non-empty prefix followed by the stack contents

				LabelNode begin = new LabelNode();
				LabelNode end = new LabelNode();

				int lv_idx_stack = nextLocalVariableIndex();
				int lv_idx_args = nextLocalVariableIndex() + 1;

				Type arrayType = ASMUtils.arrayTypeFor(Object.class);

				locals.add(new LocalVariableNode("stack", arrayType.getDescriptor(), null, begin, end, lv_idx_stack));
				locals.add(new LocalVariableNode("args", arrayType.getDescriptor(), null, begin, end, lv_idx_args));

				il.add(begin);

				// get stack contents as an array
				il.add(loadExecutionContext());
				il.add(loadReturnBuffer());
				il.add(ReturnBufferMethods.toArray());
				il.add(new VarInsnNode(ASTORE, lv_idx_stack));

				// compute the overall arg list length
				il.add(new VarInsnNode(ALOAD, lv_idx_stack));
				il.add(new InsnNode(ARRAYLENGTH));
				il.add(ASMUtils.loadInt(vl.addrs().size()));
				il.add(new InsnNode(IADD));

				// instantiate the actual arg list (length is on stack top)
				il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));
				il.add(new VarInsnNode(ASTORE, lv_idx_args));

				// fill in the prefix
				int idx = 0;
				for (Val v : vl.addrs()) {
					il.add(new VarInsnNode(ALOAD, lv_idx_args));
					il.add(ASMUtils.loadInt(idx++));
					il.add(new VarInsnNode(ALOAD, slot(v)));
					il.add(new InsnNode(AASTORE));
				}

				// call System.arraycopy(stack, 0, args, prefix_length, stack.length)
				il.add(new VarInsnNode(ALOAD, lv_idx_stack));
				il.add(ASMUtils.loadInt(0));
				il.add(new VarInsnNode(ALOAD, lv_idx_args));
				il.add(ASMUtils.loadInt(vl.addrs().size()));
				il.add(new VarInsnNode(ALOAD, lv_idx_stack));
				il.add(new InsnNode(ARRAYLENGTH));
				il.add(new MethodInsnNode(
						INVOKESTATIC,
						Type.getInternalName(System.class),
						"arraycopy",
						Type.getMethodDescriptor(
								Type.VOID_TYPE,
								Type.getType(Object.class), Type.INT_TYPE, Type.getType(Object.class), Type.INT_TYPE, Type.INT_TYPE),
						false));

				// push result to stack
				il.add(new VarInsnNode(ALOAD, lv_idx_args));
				il.add(end);

				return 0;
			}
		}
		else {
			// fixed number of arguments

			int k = vl.addrs().size() + 1;
			if (k <= maxKind) {
				// pass arguments on the JVM stack
				for (Val v : vl.addrs()) {
					il.add(new VarInsnNode(ALOAD, slot(v)));
				}
				return k;
			}
			else {
				// pass arguments packed in an array
				il.add(ASMUtils.loadInt(vl.addrs().size()));
				il.add(new TypeInsnNode(ANEWARRAY, Type.getInternalName(Object.class)));

				int idx = 0;
				for (Val v : vl.addrs()) {
					il.add(new InsnNode(DUP));
					il.add(ASMUtils.loadInt(idx++));
					il.add(new VarInsnNode(ALOAD, slot(v)));
					il.add(new InsnNode(AASTORE));
				}

				return 0;
			}
		}
	}

	@Override
	public void visit(Ret node) {
		il.add(loadExecutionContext());
		il.add(loadReturnBuffer());
		int kind = loadVList(node.args(), ReturnBufferMethods.MAX_SETTO_KIND);  // values
		il.add(ReturnBufferMethods.setTo(kind));
		il.add(_return());
	}

	@Override
	public void visit(TCall node) {
		il.add(loadExecutionContext());
		il.add(loadReturnBuffer());
		il.add(new VarInsnNode(ALOAD, slot(node.target())));  // call target
		int kind = loadVList(node.args(), ReturnBufferMethods.MAX_TAILCALL_KIND);  // call args
		il.add(ReturnBufferMethods.tailCall(kind));
		il.add(_return());
	}

	@Override
	public void visit(Call node) {
		ResumptionPoint rp = newResumptionPoint();
		il.add(rp.save());

		il.add(loadExecutionContext());
		il.add(new VarInsnNode(ALOAD, slot(node.fn())));  // call target
		int kind = loadVList(node.args(), DispatchMethods.MAX_CALL_KIND);  // call args
		il.add(DispatchMethods.call(kind));

		il.add(rp.resume());
	}

	@Override
	public void visit(MultiGet node) {
		il.add(loadExecutionContext());
		il.add(loadReturnBuffer());
		il.add(ReturnBufferMethods.get(node.idx()));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(Label node) {
		il.add(l(node));
		il.add(ASMUtils.frameSame());
	}

	@Override
	public void visit(Jmp node) {
		il.add(_goto(node.jmpDest()));
	}

	private class ClosureUse {

		private final FunctionId id;
		private final List upvals;

		private final String fieldName;  // may be null

		private ClosureUse(FunctionId id, List upvals, int idx) {
			this.id = Check.notNull(id);
			this.upvals = Check.notNull(upvals);

			if (isClosed() && !isPure()) {
				this.fieldName = context.addFieldName("c_" + idx);
			}
			else {
				this.fieldName = null;
			}
		}

		public boolean isClosed() {
			for (AbstractVar uv : upvals) {
				if (uv instanceof Var) {
					return false;
				}
			}
			return true;
		}

		public boolean isPure() {
			return upvals.isEmpty();
		}

		public RunMethod.ClosureFieldInstance toClosureFieldInstance() {
			assert (this.isClosed());

			FieldNode fieldNode = instanceFieldNode();

			InsnList il = new InsnList();
			il.add(new VarInsnNode(ALOAD, 0));
			il.add(instantiationInsns());
			il.add(new FieldInsnNode(
					PUTFIELD,
					context.thisClassType().getInternalName(),
					instanceFieldName(),
					instanceType().getDescriptor()));

			return new RunMethod.ClosureFieldInstance(instanceFieldNode(), il);
		}

		private InsnList instantiationInsns() {
			InsnList il = new InsnList();

			ClassNameTranslator tr = context.classNameTranslator;

			Type fnType = ASMUtils.typeForClassName(id.toClassName(tr));

			il.add(new TypeInsnNode(NEW, fnType.getInternalName()));
			il.add(new InsnNode(DUP));
			for (AbstractVar var : upvals) {
				if (var instanceof UpVar) {
					il.add(loadUpvalueRef((UpVar) var));
				}
				else {
					Var v = (Var) var;
					assert (context.types.isReified(v));
					il.add(new VarInsnNode(ALOAD, slot(v)));
					il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Variable.class)));
				}
			}

			Type[] ctorArgTypes = new Type[upvals.size()];
			Arrays.fill(ctorArgTypes, Type.getType(Variable.class));

			il.add(ASMUtils.ctor(fnType, ctorArgTypes));

			return il;
		}

		private String instanceFieldName() {
			return fieldName;
		}

		private Type instanceType() {
			return ASMUtils.typeForClassName(id.toClassName(context.classNameTranslator));
		}

		private FieldNode instanceFieldNode() {
			return new FieldNode(
					ACC_PRIVATE + ACC_FINAL,
					instanceFieldName(),
					instanceType().getDescriptor(),
					null,
					null);
		}

		private InsnList fetchInstanceInsns() {
			InsnList il = new InsnList();

			if (this.isClosed()) {
				if (this.isPure()) {
					il.add(new FieldInsnNode(
							GETSTATIC,
							instanceType().getInternalName(),
							ASMBytecodeEmitter.instanceFieldName(),
							instanceType().getDescriptor()));
				}
				else {
					il.add(new VarInsnNode(ALOAD, 0));
					il.add(new FieldInsnNode(
							GETFIELD,
							context.thisClassType().getInternalName(),
							instanceFieldName(),
							instanceType().getDescriptor()));
				}
			}
			else {
				il.add(instantiationInsns());
			}

			return il;
		}

	}

	@Override
	public void visit(Closure node) {
		ClosureUse cu = new ClosureUse(node.id(), node.args(), instanceLevelClosures.size());

		if (cu.isClosed() && !cu.isPure()) {
			instanceLevelClosures.add(cu.toClosureFieldInstance());
		}

		il.add(cu.fetchInstanceInsns());
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(ToNumber node) {
		il.add(new VarInsnNode(ALOAD, slot(node.src())));
		il.add(ConversionMethods.toNumericalValue(node.desc()));
		il.add(new VarInsnNode(ASTORE, slot(node.dest())));
	}

	@Override
	public void visit(ToNext node) {
		il.add(_next(node.label()));
	}

	private Label destLabel;

	@Override
	public void visit(Branch branch) {
		assert (destLabel == null);

		try {
			destLabel = branch.jmpDest();
			branch.condition().accept(this);
			il.add(_next(branch.next()));
		}
		finally {
			destLabel = null;
		}
	}

	@Override
	public void visit(Branch.Condition.Nil cond) {
		assert (destLabel != null);
		il.add(new VarInsnNode(ALOAD, slot(cond.addr())));

		if (!isSub() || resolver.isLocalLabel(destLabel)) {
			// local jump
			il.add(new JumpInsnNode(IFNULL, l(destLabel)));
		}
		else {
			// non-local jump
			LabelNode l_nojump = new LabelNode();
			il.add(new JumpInsnNode(IFNONNULL, l_nojump));
			il.add(_nonLocalGoto(destLabel));
			il.add(l_nojump);
			il.add(new FrameNode(F_SAME, 0, null, 0, null));
		}
	}

	@Override
	public void visit(Branch.Condition.Bool cond) {
		assert (destLabel != null);
		il.add(new VarInsnNode(ALOAD, slot(cond.addr())));
		il.add(ConversionMethods.booleanValueOf());

		if (!isSub() || resolver.isLocalLabel(destLabel)) {
			// local jump
			il.add(new JumpInsnNode(cond.expected() ? IFNE : IFEQ, l(destLabel)));
		}
		else {
			// non-local jump
			LabelNode l_nojump = new LabelNode();
			il.add(new JumpInsnNode(cond.expected() ? IFEQ : IFNE, l_nojump));
			il.add(_nonLocalGoto(destLabel));
			il.add(l_nojump);
			il.add(new FrameNode(F_SAME, 0, null, 0, null));
		}

	}

	@Override
	public void visit(Branch.Condition.NumLoopEnd cond) {
		assert (destLabel != null);
		il.add(new VarInsnNode(ALOAD, slot(cond.var())));
		il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Number.class)));
		il.add(new VarInsnNode(ALOAD, slot(cond.limit())));
		il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Number.class)));
		il.add(new VarInsnNode(ALOAD, slot(cond.step())));
		il.add(new TypeInsnNode(CHECKCAST, Type.getInternalName(Number.class)));
		il.add(DispatchMethods.continueLoop());

		if (!isSub() || resolver.isLocalLabel(destLabel)) {
			// local jump
			il.add(new JumpInsnNode(IFEQ, l(destLabel)));
		}
		else {
			// non-local jump
			LabelNode l_nojump = new LabelNode();
			il.add(new JumpInsnNode(IFNE, l_nojump));
			il.add(_nonLocalGoto(destLabel));
			il.add(l_nojump);
			il.add(new FrameNode(F_SAME, 0, null, 0, null));
		}
	}

	private void staticCpuWithdraw(int cost) {
		switch (context.compilerSettings.cpuAccountingMode()) {
			case NO_CPU_ACCOUNTING: {
				// no-op
				break;
			}

			case IN_EVERY_BASIC_BLOCK: {
				ResumptionPoint rp = newResumptionPoint();
				il.add(rp.save());

				il.add(loadExecutionContext());
				il.add(new InsnNode(DUP));
				il.add(ASMUtils.loadInt(cost));
				il.add(ExecutionContextMethods.registerTicks());
				il.add(ExecutionContextMethods.checkCallYield());

				il.add(rp.resume());
				break;
			}

			default: throw new UnsupportedOperationException("Unsupported CPU accounting mode: " + context.compilerSettings.cpuAccountingMode());
		}
	}

	// do we care about counting ticks?
	private boolean countingTicks() {
		return (context.compilerSettings.cpuAccountingMode()
				!= CompilerSettings.CPUAccountingMode.NO_CPU_ACCOUNTING);
	}

	@Override
	public void visit(CPUWithdraw node) {
		staticCpuWithdraw(node.cost());
	}

	@Override
	public void visit(Line node) {
		LabelNode l = new LabelNode();
		il.add(l);
		il.add(new LineNumberNode(node.lineNumber(), l));
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy