/*
* This file is a part of Alchemy OS project.
* Copyright (C) 2011-2014, Sergey Basalaev <sbasalaev@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package alchemy.nec.asm;
import alchemy.evm.Opcodes;
import alchemy.nec.syntax.Null;
import alchemy.types.Float32;
import alchemy.types.Float64;
import alchemy.types.Int32;
import alchemy.types.Int64;
import alchemy.util.ArrayList;
import java.io.ByteArrayOutputStream;
import java.util.Enumeration;
import java.util.Hashtable;
/**
* Writes function bytecode.
* @author Sergey Basalaev
*/
public class FunctionWriter implements Opcodes {
private ByteArrayOutputStream data = new ByteArrayOutputStream();
private StringBuffer relocdata = new StringBuffer();
private StringBuffer dbgtable = new StringBuffer();
/** Keeps sequence of labels (from, to, handle). */
private ArrayList errdata = new ArrayList();
private Hashtable labeldata = new Hashtable();
private int stackpos = 0;
private int stackmax = 0;
private int varcount;
private ArrayList objects;
private AsmFunc func;
FunctionWriter(AsmFunc func, ArrayList objects, int arglen) {
this.objects = objects;
this.varcount = arglen;
this.func = func;
}
private void visitStack(int inc) {
stackpos += inc;
if (inc > 0 && stackmax < stackpos) stackmax = stackpos;
if (stackpos < 0) throw new IllegalStateException("Stack inconsistency: empty stack");
}
/** Visit name of the source file. */
public void visitSource(String name) {
if (dbgtable.length() > 0) throw new IllegalStateException("Source already visited");
int index = objects.indexOf(name);
if (index < 0) {
index = objects.size();
objects.add(name);
}
dbgtable.append((char)index);
}
/** Visit number of the line in the source file. */
public void visitLine(int num) {
int len = dbgtable.length();
if (len == 0) {
throw new IllegalStateException("Source is not visited");
}
if (num >= 0 && (len < 2 || dbgtable.charAt(len-2) != num)) {
dbgtable.append((char)num).append((char)data.size());
}
}
/** Visit instruction without arguments. */
public void visitInsn(int opcode) {
data.write(opcode);
switch (opcode) {
case AASTORE:
case BASTORE:
case CASTORE:
case SASTORE:
case ZASTORE:
case IASTORE:
case LASTORE:
case FASTORE:
case DASTORE:
visitStack(-3);
break;
case THROW:
case SETGLOBAL:
visitStack(-2);
break;
case ACMP:
case AALOAD:
case BALOAD:
case CALOAD:
case SALOAD:
case ZALOAD:
case IALOAD:
case LALOAD:
case FALOAD:
case DALOAD:
case DADD:
case DCMP:
case DDIV:
case DMOD:
case DMUL:
case DSUB:
case FADD:
case FCMP:
case FDIV:
case FMOD:
case FMUL:
case FSUB:
case IADD:
case IAND:
case ICMP:
case IDIV:
case IMOD:
case IMUL:
case IOR:
case ISHL:
case ISHR:
case ISUB:
case IUSHR:
case IXOR:
case LADD:
case LAND:
case LCMP:
case LDIV:
case LMOD:
case LMUL:
case LOR:
case LSUB:
case LSHL:
case LSHR:
case LUSHR:
case LXOR:
case POP:
case RETURN:
case GETGLOBALDEF:
visitStack(-1);
break;
case AALEN:
case BALEN:
case CALEN:
case SALEN:
case ZALEN:
case IALEN:
case LALEN:
case FALEN:
case DALEN:
case D2F:
case D2I:
case D2L:
case DNEG:
case F2D:
case F2I:
case F2L:
case FNEG:
case I2D:
case I2F:
case I2L:
case INEG:
case L2D:
case L2F:
case L2I:
case LNEG:
case NEWAA:
case NEWBA:
case NEWCA:
case NEWSA:
case NEWZA:
case NEWIA:
case NEWLA:
case NEWFA:
case NEWDA:
case NOP:
case RET_NULL:
case SWAP:
case I2C:
case I2B:
case I2S:
case GETGLOBAL:
break;
case DUP:
visitStack(1);
break;
case DUP2:
visitStack(2);
break;
default:
throw new IllegalArgumentException();
}
}
/** Visit CALL or CALV instruction. */
public void visitCallInsn(int opcode, int arglen) {
if (arglen < 0 || arglen > 255) throw new IllegalArgumentException();
if (arglen < 8) {
if (opcode == CALL) data.write(CALL_0 + arglen);
else data.write(CALV_0 + arglen);
} else {
data.write(opcode);
data.write(arglen);
}
visitStack(opcode == CALL ? -arglen : -arglen-1);
}
/** Visit CALLC or CALVC instruction. */
public void visitCallConstInsn(int opcode, int arglen, String signature) {
if (arglen < 0 || arglen > 255) throw new IllegalArgumentException();
if (arglen < 8) {
if (opcode == CALLC) data.write(CALLC_0 + arglen);
else data.write(CALVC_0 + arglen);
} else {
data.write(opcode);
data.write(arglen);
}
FuncObject f = new FuncObject(signature);
int index = objects.indexOf(f);
if (index < 0) {
index = objects.size();
objects.add(f);
}
relocdata.append((char)data.size());
data.write(index >> 8);
data.write(index);
visitStack(opcode == CALLC ? -arglen+1 : -arglen);
}
/** Visit LOAD or STORE instruction. */
public void visitVarInsn(int opcode, int var) {
if (var < 0 || var > 255) throw new IllegalArgumentException();
if (var >= varcount) varcount = var+1;
if (var < 8) {
if (opcode == LOAD) data.write(LOAD_0 + var);
else data.write(STORE_0 + var);
} else {
data.write(opcode);
data.write(var);
}
visitStack(opcode == LOAD ? 1 : -1);
}
public void visitIincInsn(int var, int incr) {
if (var < 0 || var > 255) throw new IllegalArgumentException();
if (var >= varcount) varcount = var+1;
if (incr < Byte.MIN_VALUE || incr > Byte.MAX_VALUE) throw new IllegalArgumentException();
data.write(IINC);
data.write(var);
data.write(incr);
}
public void visitNewMultiArray(int dimension, int type) {
if (dimension <= 0) throw new IllegalArgumentException();
data.write(NEWMULTIARRAY);
data.write(dimension);
data.write(type);
visitStack(dimension-1);
}
public void visitConcat(int n) {
if (n < 0 || n > 255) throw new IllegalArgumentException();
data.write(CONCAT);
data.write(n);
visitStack(-n+1);
}
public void visitLdFunc(String name) {
visitLdcInsn(new FuncObject(name));
}
/** Visit LDC instruction with given object.
* The class of argument must be one of Null,
* Boolean, Int32, Int64, Float32, Float64,
* String.
*/
public void visitLdcInsn(Object cnst) {
boolean written = false;
if (cnst instanceof Boolean) {
if (cnst.equals(Boolean.TRUE)) data.write(ICONST_1);
else data.write(ICONST_0);
written = true;
} else if (cnst instanceof Int32) {
int i = ((Int32)cnst).value;
if (i >= -1 && i <= 5) {
data.write(ICONST_0 + i);
written = true;
} else if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) {
data.write(BIPUSH);
data.write(i);
written = true;
} else if (i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) {
data.write(SIPUSH);
data.write(i >> 8);
data.write(i);
written = true;
}
} else if (cnst instanceof Int64) {
long l = ((Int64)cnst).value;
if (l == 0l) {
data.write(LCONST_0);
written = true;
} else if (l == 1l) {
data.write(LCONST_1);
written = true;
}
} else if (cnst instanceof Float32) {
float f = ((Float32)cnst).value;
if (f == 0f) {
data.write(FCONST_0);
written = true;
} else if (f == 1f) {
data.write(FCONST_1);
written = true;
} else if (f == 2f) {
data.write(FCONST_2);
written = true;
}
} else if (cnst instanceof Float64) {
double d = ((Float64)cnst).value;
if (d == 0d) {
data.write(DCONST_0);
written = true;
} else if (d == 1d) {
data.write(DCONST_1);
written = true;
}
} else if (cnst == Null.NULL) {
data.write(ACONST_NULL);
written = true;
}
if (!written) {
int index = objects.indexOf(cnst);
if (index < 0) {
index = objects.size();
objects.add(cnst);
}
data.write(LDC);
relocdata.append((char)data.size());
data.write(index >> 8);
data.write(index);
}
visitStack(1);
}
/**
* Marks current code position as pointer with label address.
*/
private void visitLabelPtr(Label label) {
if (label.addr >= 0) {
data.write(label.addr >> 8);
data.write(label.addr);
} else {
StringBuffer sb = (StringBuffer)labeldata.get(label);
if (sb == null) {
sb = new StringBuffer();
labeldata.put(label, sb);
}
sb.append((char)data.size());
data.write(-1);
data.write(-1);
}
if (label.stackpos < 0) {
label.stackpos = stackpos;
} else if (label.stackpos != stackpos) {
throw new IllegalStateException("Stack inconsistency: other jump expects different stack");
}
}
public void visitJumpInsn(int opcode, Label label) {
data.write(opcode);
switch (opcode) {
case GOTO:
break;
case IFEQ:
case IFGE:
case IFGT:
case IFLE:
case IFLT:
case IFNE:
case IFNULL:
case IFNNULL:
visitStack(-1);
break;
case IF_ICMPGE:
case IF_ICMPGT:
case IF_ICMPLE:
case IF_ICMPLT:
case IF_ACMPEQ:
case IF_ACMPNE:
visitStack(-2);
break;
default:
throw new IllegalArgumentException();
}
visitLabelPtr(label);
}
public void visitTableSwitch(int min, int max, Label dflt, Label[] jumps) {
data.write(Opcodes.TABLESWITCH);
visitStack(-1);
visitLabelPtr(dflt);
data.write(min >> 24);
data.write(min >> 16);
data.write(min >> 8);
data.write(min);
data.write(max >> 24);
data.write(max >> 16);
data.write(max >> 8);
data.write(max);
for (int i=0; i<=max-min; i++) {
visitLabelPtr(jumps[i]);
}
}
public void visitLookupSwitch(Label dflt, int[] cases, Label[] jumps) {
data.write(Opcodes.LOOKUPSWITCH);
visitStack(-1);
visitLabelPtr(dflt);
data.write(cases.length >> 8);
data.write(cases.length);
for (int i=0; i<cases.length; i++) {
data.write(cases[i] >> 24);
data.write(cases[i] >> 16);
data.write(cases[i] >> 8);
data.write(cases[i]);
visitLabelPtr(jumps[i]);
}
}
public void visitLabel(Label label) {
if (label.addr >= 0) throw new IllegalStateException("Label already visited");
if (label.stackpos < 0) {
label.stackpos = stackpos;
} else {
stackpos = label.stackpos;
}
label.addr = data.size();
}
public void visitTryCatchHandler(Label from, Label to) {
if (from.stackpos < 0)
throw new IllegalStateException("Try/Catch handler must be visited after corresponding block");
Label handler = new Label();
handler.stackpos = from.stackpos + 1;
visitLabel(handler);
errdata.add(from);
errdata.add(to);
errdata.add(handler);
}
public void visitEnd() {
byte[] code = data.toByteArray();
for (Enumeration e = labeldata.keys(); e.hasMoreElements(); ) {
Label label = (Label)e.nextElement();
if (label.addr < 0) throw new IllegalStateException("Label not visited");
StringBuffer sb = (StringBuffer)labeldata.get(label);
for (int i=0; i<sb.length(); i++) {
int addr = sb.charAt(i);
code[addr] = (byte)(label.addr >> 8);
code[addr+1] = (byte)label.addr;
}
}
func.code = code;
func.stacksize = this.stackmax;
func.varcount = this.varcount;
func.relocs = new char[relocdata.length()];
relocdata.getChars(0, relocdata.length(), func.relocs, 0);
if (dbgtable.length() > 0) {
func.dbgtable = new char[dbgtable.length()];
dbgtable.getChars(0, dbgtable.length(), func.dbgtable, 0);
}
if (errdata.size() > 0) {
StringBuffer sb = new StringBuffer();
for (int i=0; i<errdata.size(); i += 3) {
Label from = (Label)errdata.get(i);
if (from.addr < 0) throw new IllegalStateException("Label not visited");
Label to = (Label)errdata.get(i+1);
if (to.addr < 0) throw new IllegalStateException("Label not visited");
Label handle = (Label)errdata.get(i+2);
if (handle.addr < 0) throw new IllegalStateException("Label not visited");
sb.append((char)from.addr).append((char)to.addr).append((char)handle.addr).append((char)from.stackpos);
}
func.errtable = new char[errdata.size() / 3 * 4];
sb.getChars(0, sb.length(), func.errtable, 0);
}
}
}