// ex: se sts=4 sw=4 expandtab: /* * Yeti language compiler java bytecode generator. * * Copyright (c) 2007-2013 Madis Janson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package yeti.lang.compiler; import yeti.renamed.asmx.*; import java.util.Map; import java.util.HashMap; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import yeti.lang.Num; import yeti.lang.RatNum; import yeti.lang.IntNum; import yeti.lang.BigNum; final class Constants implements Opcodes { final Map constants = new HashMap(); private Ctx sb; Map structClasses = new HashMap(); int anonymousClassCounter; String sourceName; Ctx ctx; List unstoredClasses = new ArrayList(); Constants(String sourceName, String sourceFile) { if (sourceFile != null) { } else if (sourceName != null) { int p = sourceName.lastIndexOf('/'); if (p < 0) p = sourceName.lastIndexOf('\\'); sourceFile = p < 0 ? sourceName : sourceName.substring(p + 1); } else { sourceFile = "<>"; } this.sourceName = sourceFile; } private void constField(int mode, String name, Code code, String descr) { ctx.cw.visitField(mode, name, descr, null, null).visitEnd(); if (sb == null) sb = ctx.newMethod(ACC_STATIC, "<clinit>", "()V"); code.gen(sb); sb.fieldInsn(PUTSTATIC, ctx.className, name, descr); } void registerConstant(Object key, Code code, Ctx ctx_) { String descr = 'L' + Code.javaType(code.type.deref()) + ';'; String name = (String) constants.get(key); if (name == null) { name = "_".concat(Integer.toString(ctx.fieldCounter++)); constField(ACC_STATIC | ACC_FINAL | ACC_SYNTHETIC, name, code, descr); constants.put(key, name); } ctx_.fieldInsn(GETSTATIC, ctx.className, name, descr); } void close() { if (sb != null) { sb.insn(RETURN); sb.closeMethod(); } } // first value in array must be empty void stringArray(Ctx ctx_, String[] array) { if (sb == null) sb = ctx.newMethod(ACC_STATIC, "<clinit>", "()V"); array[0] = "Strings"; List key = Arrays.asList(array); String name = (String) constants.get(key); if (name == null) { name = "_".concat(Integer.toString(ctx.fieldCounter++)); ctx.cw.visitField(ACC_STATIC | ACC_FINAL | ACC_SYNTHETIC, name, "[Ljava/lang/String;", null, null).visitEnd(); sb.intConst(array.length - 1); sb.typeInsn(ANEWARRAY, "java/lang/String"); for (int i = 1; i < array.length; ++i) { sb.insn(DUP); sb.intConst(i - 1); sb.ldcInsn(array[i]); sb.insn(AASTORE); } sb.fieldInsn(PUTSTATIC, ctx.className, name, "[Ljava/lang/String;"); constants.put(key, name); } ctx_.fieldInsn(GETSTATIC, ctx.className, name, "[Ljava/lang/String;"); } // generates [Ljava/lang/String;[Z into stack, using constant cache void structInitArg(Ctx ctx_, StructField[] fields, int fieldCount, boolean nomutable) { String[] fieldNameArr = new String[fieldCount + 1]; char[] mutableArr = new char[fieldNameArr.length]; mutableArr[0] = '@'; int i, mutableCount = 0; for (i = 1; i < fieldNameArr.length; ++i) { StructField f = fields[i - 1]; fieldNameArr[i] = f.name; if (f.mutable || f.property > 0) { mutableArr[i] = '\001'; ++mutableCount; } } stringArray(ctx_, fieldNameArr); if (nomutable || mutableCount == 0) { ctx_.insn(ACONST_NULL); return; } String key = new String(mutableArr); String name = (String) constants.get(key); if (name == null) { name = "_".concat(Integer.toString(ctx.fieldCounter++)); ctx.cw.visitField(ACC_STATIC | ACC_FINAL | ACC_SYNTHETIC, name, "[Z", null, null).visitEnd(); sb.intConst(fieldCount); sb.visitIntInsn(NEWARRAY, T_BOOLEAN); for (i = 0; i < fieldCount; ++i) { sb.insn(DUP); sb.intConst(i); sb.intConst(mutableArr[i + 1]); sb.insn(BASTORE); } sb.fieldInsn(PUTSTATIC, ctx.className, name, "[Z"); constants.put(key, name); } ctx_.fieldInsn(GETSTATIC, ctx.className, name, "[Z"); } } final class Ctx implements Opcodes { Compiler compilation; String className; ClassWriter cw; private MethodVisitor m; private int lastInsn = -1; private String lastType; Constants constants; Map usedMethodNames; int localVarCount; int fieldCounter; int lastLine; int tainted; // you are inside loop, natural laws a broken Ctx(Compiler compilation, Constants constants, ClassWriter writer, String className) { this.compilation = compilation; this.constants = constants; this.cw = writer; this.className = className; } Ctx newClass(int flags, String name, String extend, String[] interfaces, int line) { Ctx ctx = new Ctx(compilation, constants, new YClassWriter(compilation.classWriterFlags), name); ctx.usedMethodNames = new HashMap(); ctx.cw.visit(V1_4, flags, name, null, extend == null ? "java/lang/Object" : extend, interfaces); ctx.cw.visitSource(constants.sourceName, null); compilation.addClass(name, ctx, line); return ctx; } String methodName(String name) { Map used = usedMethodNames; if (name == null) name = "_" + used.size(); else if (used.containsKey(name) || name.startsWith("_")) name += used.size(); used.put(name, null); return name; } Ctx newMethod(int flags, String name, String type) { Ctx ctx = new Ctx(compilation, constants, cw, className); ctx.usedMethodNames = usedMethodNames; ctx.m = cw.visitMethod(flags, name, type, null, null); ctx.m.visitCode(); return ctx; } void markInnerClass(Ctx outer, int access) { String fn = className.substring(outer.className.length() + 1); outer.cw.visitInnerClass(className, outer.className, fn, access); cw.visitInnerClass(className, outer.className, fn, access); } void closeMethod() { insn(-1); m.visitMaxs(0, 0); m.visitEnd(); } void createInit(int mod, String parent) { MethodVisitor m = cw.visitMethod(mod, "<init>", "()V", null, null); // super() m.visitVarInsn(ALOAD, 0); m.visitMethodInsn(INVOKESPECIAL, parent, "<init>", "()V", false); m.visitInsn(RETURN); m.visitMaxs(0, 0); m.visitEnd(); } void intConst(int n) { if (n >= -1 && n <= 5) { insn(n + 3); } else { insn(-1); if (n >= -32768 && n <= 32767) m.visitIntInsn(n >= -128 && n <= 127 ? BIPUSH : SIPUSH, n); else m.visitLdcInsn(new Integer(n)); } } void visitLine(int line) { if (line != 0 && lastLine != line) { Label label = new Label(); m.visitLabel(label); m.visitLineNumber(line, label); lastLine = line; } } void genBoolean(Label label) { fieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"); Label end = new Label(); m.visitJumpInsn(GOTO, end); m.visitLabel(label); m.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "FALSE", "Ljava/lang/Boolean;"); m.visitLabel(end); } void insn(int opcode) { if (lastInsn != -1 && lastInsn != -2) { if (lastInsn == ACONST_NULL && opcode == POP) { lastInsn = -1; return; } m.visitInsn(lastInsn); } lastInsn = opcode; } void varInsn(int opcode, int var) { insn(-1); m.visitVarInsn(opcode, var); } Ctx load(int var) { insn(-1); m.visitVarInsn(ALOAD, var); return this; } void visitIntInsn(int opcode, int param) { insn(-1); if (opcode != IINC) m.visitIntInsn(opcode, param); else m.visitIincInsn(param, -1); } void typeInsn(int opcode, String type) { if (opcode == CHECKCAST) { if (lastInsn == -2 && type.equals(lastType) || lastInsn == ACONST_NULL) return; // no cast necessary insn(-2); lastType = type; } else { insn(-1); } m.visitTypeInsn(opcode, type); } void captureCast(String type) { if (type.charAt(0) == 'L') type = type.substring(1, type.length() - 1); if (!type.equals("java/lang/Object")) typeInsn(CHECKCAST, type); } void visitInit(String type, String descr) { insn(-2); m.visitMethodInsn(INVOKESPECIAL, type, "<init>", descr, false); lastType = type; } void forceType(String type) { insn(-2); lastType = type; } void fieldInsn(int opcode, String owner, String name, String desc) { if (owner == null || name == null || desc == null) throw new IllegalArgumentException("fieldInsn(" + opcode + ", " + owner + ", " + name + ", " + desc + ")"); insn(-1); m.visitFieldInsn(opcode, owner, name, desc); if ((opcode == GETSTATIC || opcode == GETFIELD) && desc.charAt(0) == 'L') { lastInsn = -2; lastType = desc.substring(1, desc.length() - 1); } } void methodInsn(int opcode, String owner, String name, String desc) { insn(-1); m.visitMethodInsn(opcode, owner, name, desc, opcode == INVOKEINTERFACE); } void visitApply(Code arg, int line) { arg.gen(this); insn(-1); visitLine(line); m.visitMethodInsn(INVOKEVIRTUAL, "yeti/lang/Fun", "apply", "(Ljava/lang/Object;)Ljava/lang/Object;", false); } void jumpInsn(int opcode, Label label) { insn(-1); m.visitJumpInsn(opcode, label); } void visitLabel(Label label) { if (lastInsn != -2) insn(-1); m.visitLabel(label); } void ldcInsn(Object cst) { insn(-1); m.visitLdcInsn(cst); if (cst instanceof String) { lastInsn = -2; lastType = "java/lang/String"; } } void tryCatchBlock(Label start, Label end, Label handler, String type) { insn(-1); m.visitTryCatchBlock(start, end, handler, type); } void switchInsn(int min, int max, Label dflt, int[] keys, Label[] labels) { insn(-1); if (keys == null) m.visitTableSwitchInsn(min, max, dflt, labels); else m.visitLookupSwitchInsn(dflt, keys, labels); } void constant(Object key, Code code) { constants.registerConstant(key, code, this); } void popn(int n) { if ((n & 1) != 0) insn(POP); for (; n >= 2; n -= 2) insn(POP2); } } abstract class Code implements Opcodes { // constants used by flagop static final int CONST = 1; static final int PURE = 2; // for bindrefs, mark as used lvalue static final int ASSIGN = 4; static final int INT_NUM = 8; // Comparision operators use this for some optimisation. static final int EMPTY_LIST = 0x10; // Check for no capturing. XXX WARNING. // You should really do DIRECT_BIND queries only BEFORE using those // bindings (that's, at the start of closure gen or earlier) OR at // least ensure having the capture set copied, as Capture instances // mark themselves uncaptured when they discover being a direct binding // during flagop. This could be a problem when the binding ref was // generated as captured earlier AND the call site omits later // the ones that became uncaptured by DIRECT_BIND flagop. If it should be // discovered to be unavoidable, a special "almost-uncaptured" Capture // state could be introduced that would affect only Capture.gen(). // Note that most closures will do DIRECT_BIND query by mergeCaptures. static final int DIRECT_BIND = 0x20; // normal constant is also pure and don't need capturing static final int STD_CONST = CONST | PURE | DIRECT_BIND; // this which is not captured static final int DIRECT_THIS = 0x40; // capture that requires bounding function to initialize its module static final int MODULE_REQUIRED = 0x80; // code object is a list range static final int LIST_RANGE = 0x100; YType type; boolean polymorph; /** * Generates into ctx a bytecode that (when executed in the JVM) * results in a value pushed into stack. * That value is of course the value of that code snippet * after evaluation. */ abstract void gen(Ctx ctx); // Some "functions" may have special kinds of apply Code apply(Code arg, YType res, int line) { return new Apply(res, this, arg, line); } Code apply2nd(final Code arg2, final YType t, int line) { return new Code() { { type = t; } void gen(Ctx ctx) { ctx.typeInsn(NEW, "yeti/lang/Bind2nd"); ctx.insn(DUP); Code.this.gen(ctx); arg2.gen(ctx); ctx.visitInit("yeti/lang/Bind2nd", "(Ljava/lang/Object;Ljava/lang/Object;)V"); } }; } // Not used currently. Should allow some custom behaviour // on binding (possibly useful for inline-optimisations). /*BindRef bindRef() { return null; }*/ // When the code is a lvalue, then this method returns code that // performs the lvalue assigment of the value given as argument. Code assign(Code value) { return null; } // Boolean codes have ability to generate jumps. void genIf(Ctx ctx, Label to, boolean ifTrue) { gen(ctx); ctx.fieldInsn(GETSTATIC, "java/lang/Boolean", "TRUE", "Ljava/lang/Boolean;"); ctx.jumpInsn(ifTrue ? IF_ACMPEQ : IF_ACMPNE, to); } // should be used, if only int is ever needed void genInt(Ctx ctx, int line, boolean longValue) { gen(ctx); ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/Num"); if (longValue) ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", "longValue", "()J"); else ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Num", "intValue", "()I"); } // Used to tell that this code is at tail position in a function. // Useful for doing tail call optimisations. void markTail() { } boolean flagop(int flag) { return false; } // Used for sharing embedded constant objects Object valueKey() { return this; } // Called by bind for direct bindings // bindings can use this for "preparation" boolean prepareConst(Ctx ctx) { return flagop(CONST); } static final String javaType(YType t) { t = t.deref(); switch (t.type) { case YetiType.STR: return "java/lang/String"; case YetiType.NUM: return "yeti/lang/Num"; case YetiType.CHAR: return "java/lang/Character"; case YetiType.FUN: return "yeti/lang/Fun"; case YetiType.STRUCT: return "yeti/lang/Struct"; case YetiType.VARIANT: return "yeti/lang/Tag"; case YetiType.MAP: { int k = t.param[2].deref().type; if (k != YetiType.LIST_MARKER) return "java/lang/Object"; if (t.param[1].deref().type == YetiType.NUM) return "yeti/lang/MList"; return "yeti/lang/AList"; } case YetiType.JAVA: return t.javaType.className(); } return "java/lang/Object"; } static final char[] mangle = "jQh$oBz apCmds cSlegqt".toCharArray(); static final String mangle(String s) { char[] a = s.toCharArray(); char[] to = new char[a.length * 2]; int l = 0; for (int i = 0, cnt = a.length; i < cnt; ++i, ++l) { char c = a[i]; if (c > ' ' && c < 'A' && (to[l + 1] = mangle[c - 33]) != ' ') { } else if (c == '^') { to[l + 1] = 'v'; } else if (c == '|') { to[l + 1] = 'I'; } else if (c == '~') { to[l + 1] = '_'; } else { to[l] = c; continue; } to[l++] = '$'; } return new String(to, 0, l); } } interface CodeGen { void gen2(Ctx ctx, Code param, int line); } class SimpleCode extends Code { private Code param; private int line; private CodeGen impl; SimpleCode(CodeGen impl, Code param, YType type, int line) { this.impl = impl; this.param = param; this.line = line; this.type = type == null ? YetiType.UNIT_TYPE : type; } void gen(Ctx ctx) { impl.gen2(ctx, param, line); } } abstract class BindRef extends Code { Binder binder; BindExpr.Ref origin; // some bindrefs care about being captured. most wont. CaptureWrapper capture() { return null; } // As what java types the values of this should be captured. // Same as CaptureWrapper.captureType() String captureType() { if (origin != null) return ((BindExpr) binder).captureType(); return 'L' + javaType(type) + ';'; } // unshare. normally bindrefs are not shared // Capture shares refs and therefore has to copy for unshareing BindRef unshare() { return this; } Code unref(boolean force) { return null; } // Some bindings can be forced into direct mode void forceDirect() { throw new UnsupportedOperationException(); } Code apply(Code arg, YType res, int line) { Apply a = new Apply(res, this, arg, line); if ((a.ref = origin) != null) origin.arity = 1; return a; } } final class BindWrapper extends BindRef { private BindRef ref; BindWrapper(BindRef ref) { this.ref = ref; this.binder = ref.binder; this.type = ref.type; this.polymorph = ref.polymorph; this.origin = ref.origin; } CaptureWrapper capture() { return ref.capture(); } boolean flagop(int fl) { return (fl & (PURE | ASSIGN | DIRECT_BIND)) != 0 && ref.flagop(fl); } void gen(Ctx ctx) { ref.gen(ctx); } } class StaticRef extends BindRef { private String className; private String fieldName; private int line; boolean method; StaticRef(String className, String fieldName, YType type, Binder binder, boolean polymorph, int line) { this.type = type; this.binder = binder; this.className = className; this.fieldName = fieldName; this.polymorph = polymorph; this.line = line; } StaticRef(String fun, YType type, boolean polymorph, int line) { this("yeti/lang/std", fun, type, null, polymorph, line); method = true; } void gen(Ctx ctx) { ctx.visitLine(line); String t = javaType(type); if (method) { ctx.methodInsn(INVOKESTATIC, className, fieldName, "()L" + t + ';'); ctx.forceType(t); } else { ctx.fieldInsn(GETSTATIC, className, fieldName, 'L' + t + ';'); } } Object valueKey() { return (method ? "MREF:" : "SREF:") + className + '.' + fieldName; } boolean flagop(int fl) { return (fl & (DIRECT_BIND | CONST)) != 0; } static boolean std(Code ref, String fun) { if (!(ref instanceof StaticRef)) return false; StaticRef r = (StaticRef) ref; return r.method && "yeti/lang/std".equals(r.className) && fun.equals(r.fieldName); } } final class NumericConstant extends Code implements CodeGen { Num num; NumericConstant(Num num) { type = YetiType.NUM_TYPE; this.num = num; } boolean flagop(int fl) { return ((fl & INT_NUM) != 0 && num instanceof IntNum) || (fl & STD_CONST) != 0; } void genInt(Ctx ctx, int lineno, boolean longValue) { if (longValue) ctx.ldcInsn(new Long(num.longValue())); else ctx.intConst(num.intValue()); } private static final class Impl extends Code { String jtype, sig; Object val; void gen(Ctx ctx) { ctx.typeInsn(NEW, jtype); ctx.insn(DUP); ctx.ldcInsn(val); if (val instanceof String) ctx.intConst(10); ctx.visitInit(jtype, sig); } } public void gen2(Ctx ctx, Code param, int line) { ctx.typeInsn(NEW, "yeti/lang/RatNum"); ctx.insn(DUP); RatNum rat = ((RatNum) num).reduce(); ctx.intConst(rat.numerator()); ctx.intConst(rat.denominator()); ctx.visitInit("yeti/lang/RatNum", "(II)V"); } void gen(Ctx ctx) { if (ctx.constants.constants.containsKey(num)) { ctx.constant(num, this); return; } if (num instanceof RatNum) { ctx.constant(num, new SimpleCode(this, null, YetiType.NUM_TYPE, 0)); return; } Impl v = new Impl(); if (num instanceof IntNum) { v.jtype = "yeti/lang/IntNum"; if (IntNum.__1.compareTo(num) <= 0 && IntNum._9.compareTo(num) >= 0) { ctx.fieldInsn(GETSTATIC, v.jtype, IntNum.__1.equals(num) ? "__1" : IntNum.__2.equals(num) ? "__2" : "_" + num, "Lyeti/lang/IntNum;"); ctx.forceType("yeti/lang/Num"); return; } v.val = new Long(num.longValue()); v.sig = "(J)V"; } else if (num instanceof BigNum) { v.jtype = "yeti/lang/BigNum"; v.val = num.toString(); v.sig = "(Ljava/lang/String;I)V"; } else { v.jtype = "yeti/lang/FloatNum"; v.val = new Double(num.doubleValue()); v.sig = "(D)V"; } v.type = YetiType.NUM_TYPE; ctx.constant(num, v); } Object valueKey() { return num; } } final class StringConstant extends Code { String str; StringConstant(String str) { type = YetiType.STR_TYPE; this.str = str; } void gen(Ctx ctx) { ctx.ldcInsn(str); } boolean flagop(int fl) { return (fl & STD_CONST) != 0; } Object valueKey() { return str; } } final class UnitConstant extends BindRef { private final Object NULL = new Object(); UnitConstant(YType type) { this.type = type == null ? YetiType.UNIT_TYPE : type; } void gen(Ctx ctx) { ctx.insn(ACONST_NULL); } boolean flagop(int fl) { return (fl & STD_CONST) != 0; } Object valueKey() { return NULL; } } final class BooleanConstant extends BindRef { boolean val; BooleanConstant(boolean val) { type = YetiType.BOOL_TYPE; this.val = val; } boolean flagop(int fl) { return (fl & STD_CONST) != 0; } void gen(Ctx ctx) { ctx.fieldInsn(GETSTATIC, "java/lang/Boolean", val ? "TRUE" : "FALSE", "Ljava/lang/Boolean;"); } void genIf(Ctx ctx, Label to, boolean ifTrue) { if (val == ifTrue) ctx.jumpInsn(GOTO, to); } Object valueKey() { return Boolean.valueOf(val); } } final class ConcatStrings extends Code { Code[] param; ConcatStrings(Code[] param) { type = YetiType.STR_TYPE; this.param = param; } void gen(Ctx ctx) { boolean arr = false; if (param.length > 2) { arr = true; ctx.intConst(param.length); ctx.typeInsn(ANEWARRAY, "java/lang/String"); } for (int i = 0; i < param.length; ++i) { if (arr) { ctx.insn(DUP); ctx.intConst(i); } param[i].gen(ctx); boolean valueOf = param[i].type.deref().type != YetiType.STR; if (valueOf) ctx.methodInsn(INVOKESTATIC, "java/lang/String", "valueOf", "(Ljava/lang/Object;)Ljava/lang/String;"); if (arr) ctx.insn(AASTORE); else if (!valueOf) ctx.typeInsn(CHECKCAST, "java/lang/String"); } if (arr) ctx.methodInsn(INVOKESTATIC, "yeti/lang/Core", "concat", "([Ljava/lang/String;)Ljava/lang/String;"); else if (param.length == 2) ctx.methodInsn(INVOKEVIRTUAL, "java/lang/String", "concat", "(Ljava/lang/String;)Ljava/lang/String;"); ctx.forceType("java/lang/String"); } } final class NewExpr extends JavaExpr { private YetiType.ClassBinding extraArgs; NewExpr(JavaType.Method init, Code[] args, YetiType.ClassBinding extraArgs, int line) { super(null, init, args, line); type = init.classType; this.extraArgs = extraArgs; } void gen(Ctx ctx) { String name = method.classType.javaType.className(); ctx.typeInsn(NEW, name); ctx.insn(DUP); genCall(ctx, extraArgs.getCaptures(), INVOKESPECIAL); ctx.forceType(name); } } final class NewArrayExpr extends Code { private Code count; private int line; NewArrayExpr(YType type, Code count, int line) { this.type = type; this.count = count; this.line = line; } void gen(Ctx ctx) { if (count != null) count.genInt(ctx, line, false); ctx.visitLine(line); if (type.param[0].type != YetiType.JAVA) { // array of arrays ctx.typeInsn(ANEWARRAY, JavaType.descriptionOf(type.param[0])); return; } JavaType jt = type.param[0].javaType; int t; switch (jt.description.charAt(0)) { case 'B': t = T_BYTE; break; case 'C': t = T_CHAR; break; case 'D': t = T_DOUBLE; break; case 'F': t = T_FLOAT; break; case 'I': t = T_INT; break; case 'J': t = T_LONG; break; case 'S': t = T_SHORT; break; case 'Z': t = T_BOOLEAN; break; case 'L': ctx.typeInsn(ANEWARRAY, jt.className()); return; default: throw new IllegalStateException("ARRAY<" + jt.description + '>'); } ctx.visitIntInsn(NEWARRAY, t); } } final class MethodCall extends JavaExpr { private JavaType classType; private boolean useAccessor, invokeSuper; MethodCall(Code object, JavaType.Method method, Code[] args, int line) { super(object, method, args, line); type = method.convertedReturnType(); } void visitInvoke(Ctx ctx, int invokeInsn) { String descr = method.descr(null); String name = method.name; if (useAccessor) { if (invokeInsn == INVOKEINTERFACE) invokeInsn = INVOKEVIRTUAL; name = classType.implementation.getAccessor(method, descr, invokeSuper); } ctx.methodInsn(invokeInsn, classType.className(), name, descr); } private void _gen(Ctx ctx) { classType = method.classType.javaType; int ins = object == null ? INVOKESTATIC : classType.isInterface() ? INVOKEINTERFACE : INVOKEVIRTUAL; if (object != null) { object.gen(ctx); if (ins != INVOKEINTERFACE) classType = object.type.deref().javaType; } if (classType.implementation != null) { JavaType ct = classType.implementation.classType.deref().javaType; invokeSuper = classType != ct; // XXX: not checking for package access. shouldn't matter. useAccessor = (invokeSuper || (method.access & ACC_PROTECTED) != 0) && !object.flagop(DIRECT_THIS); if (useAccessor) classType = ct; else if (ins == INVOKEVIRTUAL && invokeSuper) ins = INVOKESPECIAL; } if (object != null && (ins != INVOKEINTERFACE || ctx.compilation.isGCJ)) { ctx.typeInsn(CHECKCAST, classType.className()); } genCall(ctx, null, ins); } void gen(Ctx ctx) { _gen(ctx); if (method.returnType.type == YetiType.UNIT) ctx.insn(ACONST_NULL); else convertValue(ctx, method.returnType); } void genIf(Ctx ctx, Label to, boolean ifTrue) { if (method.returnType.javaType != null && method.returnType.javaType.description == "Z") { _gen(ctx); ctx.jumpInsn(ifTrue ? IFNE : IFEQ, to); } else { super.genIf(ctx, to, ifTrue); } } } final class ClassField extends JavaExpr implements CodeGen { private JavaType.Field field; ClassField(Code object, JavaType.Field field, int line) { super(object, null, null, line); this.type = field.convertedType(); this.field = field; } void gen(Ctx ctx) { JavaType classType = field.classType.javaType; if (object != null) { object.gen(ctx); classType = object.type.deref().javaType; } ctx.visitLine(line); String descr = JavaType.descriptionOf(field.type); String className = classType.className(); if (object != null) ctx.typeInsn(CHECKCAST, className); // XXX: not checking for package access. shouldn't matter. if ((field.access & ACC_PROTECTED) != 0 && classType.implementation != null && !object.flagop(DIRECT_THIS)) { // XXX WTF: object can't be null - !object.flagop in condition descr = (object == null ? "()" : '(' + classType.description + ')') + descr; String name = classType.implementation .getAccessor(field, descr, false); ctx.methodInsn(INVOKESTATIC, className, name, descr); } else { ctx.fieldInsn(object == null ? GETSTATIC : GETFIELD, className, field.name, descr); } convertValue(ctx, field.type); } public void gen2(Ctx ctx, Code setValue, int __) { JavaType classType = field.classType.javaType; String className = classType.className(); if (object != null) { object.gen(ctx); ctx.typeInsn(CHECKCAST, className); classType = object.type.deref().javaType; } genValue(ctx, setValue, field.type, line); String descr = JavaType.descriptionOf(field.type); if (descr.length() > 1) ctx.typeInsn(CHECKCAST, field.type.type == YetiType.JAVA ? field.type.javaType.className() : descr); if ((field.access & ACC_PROTECTED) != 0 && classType.implementation != null && !object.flagop(DIRECT_THIS)) { // XXX WTF: object can't be null - !object.flagop in condition descr = (object != null ? "(".concat(classType.description) : "(") + descr + ")V"; String name = classType.implementation .getAccessor(field, descr, true); ctx.methodInsn(INVOKESTATIC, className, name, descr); } else { ctx.fieldInsn(object == null ? PUTSTATIC : PUTFIELD, className, field.name, descr); } ctx.insn(ACONST_NULL); } Code assign(final Code setValue) { if ((field.access & ACC_FINAL) != 0) return null; return new SimpleCode(this, setValue, null, 0); } boolean flagop(int fl) { return (fl & STD_CONST) != 0 && field.constValue != null; } } final class Cast extends JavaExpr { boolean convert; Cast(Code code, YType type, boolean convert, int line) { super(code, null, null, line); this.type = type; this.line = line; this.convert = convert; } void gen(Ctx ctx) { if (convert) { convertedArg(ctx, object, type.deref(), line); return; } object.gen(ctx); if (type.deref().type == YetiType.UNIT) { ctx.insn(POP); ctx.insn(ACONST_NULL); } } boolean prepareConst(Ctx ctx) { return object.prepareConst(ctx); } boolean flagop(int fl) { return ((fl & CONST) != 0 ? !convert : (fl & PURE) != 0) && object.flagop(fl); } } final class LoadVar extends Code { int var; void gen(Ctx ctx) { ctx.varInsn(ALOAD, var); } } final class VariantConstructor extends Code implements CodeGen { String name; VariantConstructor(YType type, String name) { this.type = type; this.name = name; } public void gen2(Ctx ctx, Code param, int line) { ctx.typeInsn(NEW, "yeti/lang/TagCon"); ctx.insn(DUP); ctx.ldcInsn(name); ctx.visitInit("yeti/lang/TagCon", "(Ljava/lang/String;)V"); } void gen(Ctx ctx) { ctx.constant("TAG:".concat(name), new SimpleCode(this, null, type, 0)); } Code apply(final Code arg, YType res, int line) { class Tag extends Code implements CodeGen { Object key; public void gen2(Ctx ctx, Code param, int line_) { ctx.typeInsn(NEW, "yeti/lang/Tag"); ctx.insn(DUP); arg.gen(ctx); ctx.ldcInsn(name); ctx.visitInit("yeti/lang/Tag", "(Ljava/lang/Object;Ljava/lang/String;)V"); } void gen(Ctx ctx) { if (key != null) ctx.constant(key, new SimpleCode(this, null, type, 0)); else gen2(ctx, null, 0); } boolean flagop(int fl) { return (fl & STD_CONST) != 0 && key != null; } Object valueKey() { return key == null ? this : key; } }; Tag tag = new Tag(); tag.type = res; tag.polymorph = arg.polymorph; if (arg.flagop(CONST)) { Object[] key = {"TAG", name, arg.valueKey()}; tag.key = Arrays.asList(key); } return tag; } } abstract class SelectMember extends BindRef implements CodeGen { private boolean assigned = false; Code st; String name; int line; SelectMember(YType type, Code st, String name, int line, boolean polymorph) { this.type = type; this.polymorph = polymorph; this.st = st; this.name = name; this.line = line; } void gen(Ctx ctx) { st.gen(ctx); ctx.visitLine(line); if (ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "yeti/lang/Struct"); ctx.ldcInsn(name); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/Struct", "get", "(Ljava/lang/String;)Ljava/lang/Object;"); } public void gen2(Ctx ctx, Code setValue, int __) { st.gen(ctx); ctx.visitLine(line); if (ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "yeti/lang/Struct"); ctx.ldcInsn(name); setValue.gen(ctx); ctx.visitLine(line); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/Struct", "set", "(Ljava/lang/String;Ljava/lang/Object;)V"); ctx.insn(ACONST_NULL); } Code assign(final Code setValue) { if (!assigned && !mayAssign()) return null; assigned = true; return new SimpleCode(this, setValue, null, 0); } abstract boolean mayAssign(); } final class SelectMemberFun extends Code implements CodeGen { String[] names; SelectMemberFun(YType type, String[] names) { this.type = type; this.names = names; this.polymorph = true; } public void gen2(Ctx ctx, Code param, int line) { for (int i = 1; i < names.length; ++i) { ctx.typeInsn(NEW, "yeti/lang/Compose"); ctx.insn(DUP); } for (int i = names.length; --i >= 0;) { ctx.typeInsn(NEW, "yeti/lang/Selector"); ctx.insn(DUP); ctx.ldcInsn(names[i]); ctx.visitInit("yeti/lang/Selector", "(Ljava/lang/String;)V"); if (i + 1 != names.length) ctx.visitInit("yeti/lang/Compose", "(Ljava/lang/Object;Ljava/lang/Object;)V"); } } void gen(Ctx ctx) { StringBuffer buf = new StringBuffer("SELECTMEMBER"); for (int i = 0; i < names.length; ++i) { buf.append(':'); buf.append(names[i]); } ctx.constant(buf.toString(), new SimpleCode(this, null, type, 0)); } } final class KeyRefExpr extends Code implements CodeGen { Code val; Code key; int line; KeyRefExpr(YType type, Code val, Code key, int line) { this.type = type; this.val = val; this.key = key; this.line = line; } void gen(Ctx ctx) { val.gen(ctx); if (val.type.deref().param[2] == YetiType.LIST_TYPE) { ctx.visitLine(line); ctx.typeInsn(CHECKCAST, "yeti/lang/MList"); key.genInt(ctx, line, false); ctx.visitLine(line); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/MList", "get", "(I)Ljava/lang/Object;"); return; } if (ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "yeti/lang/ByKey"); key.gen(ctx); ctx.visitLine(line); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/ByKey", "vget", "(Ljava/lang/Object;)Ljava/lang/Object;"); } public void gen2(Ctx ctx, Code setValue, int __) { val.gen(ctx); if (ctx.compilation.isGCJ) ctx.typeInsn(CHECKCAST, "yeti/lang/ByKey"); key.gen(ctx); setValue.gen(ctx); ctx.visitLine(line); ctx.methodInsn(INVOKEINTERFACE, "yeti/lang/ByKey", "put", "(Ljava/lang/Object;Ljava/lang/Object;)" + "Ljava/lang/Object;"); } Code assign(final Code setValue) { return new SimpleCode(this, setValue, null, 0); } } final class ConditionalExpr extends Code { Code[][] choices; ConditionalExpr(YType type, Code[][] choices, boolean poly) { this.type = type; this.choices = choices; this.polymorph = poly; } void gen(Ctx ctx) { Label end = new Label(); for (int i = 0, last = choices.length - 1; i <= last; ++i) { Label jmpNext = i < last ? new Label() : end; if (choices[i].length == 2) { choices[i][1].genIf(ctx, jmpNext, false); // condition choices[i][0].gen(ctx); // body ctx.jumpInsn(GOTO, end); } else { choices[i][0].gen(ctx); } ctx.visitLabel(jmpNext); } ctx.insn(-1); // reset type } void genIf(Ctx ctx, Label to, boolean ifTrue) { Label end = new Label(); for (int i = 0, last = choices.length - 1; i <= last; ++i) { Label jmpNext = i < last ? new Label() : end; if (choices[i].length == 2) { choices[i][1].genIf(ctx, jmpNext, false); // condition choices[i][0].genIf(ctx, to, ifTrue); // body ctx.jumpInsn(GOTO, end); } else { choices[i][0].genIf(ctx, to, ifTrue); } ctx.visitLabel(jmpNext); } } void markTail() { for (int i = choices.length; --i >= 0;) { choices[i][0].markTail(); } } } class SeqExpr extends Code { Code st; Code result; SeqExpr(Code statement) { st = statement; } void gen(Ctx ctx) { st.gen(ctx); ctx.insn(POP); // ignore the result of st expr result.gen(ctx); } void genIf(Ctx ctx, Label to, boolean ifTrue) { st.gen(ctx); ctx.insn(POP); // ignore the result of st expr result.genIf(ctx, to, ifTrue); } void markTail() { result.markTail(); } } interface Binder { BindRef getRef(int line); } final class BindExpr extends SeqExpr implements Binder, CaptureWrapper { private int id; private int mvar = -1; private final boolean var; private String javaType; private String javaDescr; private Closure closure; boolean assigned; boolean captured; Ref refs; int evalId = -1; private boolean directBind; private String directField; private String myClass; private int bindingUsed; class Ref extends BindRef { int arity; Ref next; void gen(Ctx ctx) { if (directBind) { st.gen(ctx); } else { --bindingUsed; genPreGet(ctx); genGet(ctx); } } Code assign(final Code value) { if (!var) return null; assigned = true; return new Code() { void gen(Ctx ctx) { genLocalSet(ctx, value); ctx.insn(ACONST_NULL); } }; } boolean flagop(int fl) { if ((fl & ASSIGN) != 0) return var ? assigned = true : false; if ((fl & CONST) != 0) return directBind; if ((fl & DIRECT_BIND) != 0) return directBind || directField != null; if ((fl & MODULE_REQUIRED) != 0) return directField != null; return (fl & PURE) != 0 && !var; } CaptureWrapper capture() { captured = true; if (!var) return null; ++bindingUsed; // reference through wrapper return BindExpr.this; } Code unref(boolean force) { return force || directBind ? st : null; } void forceDirect() { directField = ""; } } BindExpr(Code expr, boolean var) { super(expr); this.var = var; } void setMVarId(Closure closure, int arrayId, int index) { this.closure = closure; mvar = arrayId; id = index; } public BindRef getRef(int line) { //BindRef res = st.bindRef(); //if (res == null) Ref res = new Ref(); res.binder = this; res.type = st.type; res.polymorph = !var && st.polymorph; res.next = refs; if (st instanceof Function) res.origin = res; ++bindingUsed; return refs = res; } public Object captureIdentity() { return mvar == -1 ? (Object) this : closure; } public String captureType() { if (javaDescr == null) throw new IllegalStateException(toString()); return mvar == -1 ? javaDescr : "[Ljava/lang/Object;"; } public void genPreGet(Ctx ctx) { if (mvar == -1) { if (directField == null) { ctx.load(id); int t; // garbage collect infinite lists if (bindingUsed == 0 && ctx.tainted == 0 && ((t = st.type.deref().type) == 0 || t == YetiType.MAP)) { ctx.insn(ACONST_NULL); ctx.varInsn(ASTORE, id); } ctx.forceType(javaType); } else { ctx.fieldInsn(GETSTATIC, myClass, directField, javaDescr); } } else { ctx.load(mvar).forceType("[Ljava/lang/Object;"); } } public void genGet(Ctx ctx) { if (mvar != -1) { ctx.typeInsn(CHECKCAST, "[Ljava/lang/Object;"); ctx.intConst(id); ctx.insn(AALOAD); } } public void genSet(Ctx ctx, Code value) { if (directField == null) { ctx.typeInsn(CHECKCAST, "[Ljava/lang/Object;"); ctx.intConst(id); value.gen(ctx); ctx.insn(AASTORE); } else { value.gen(ctx); ctx.fieldInsn(PUTSTATIC, myClass, directField, javaDescr); } } private void genLocalSet(Ctx ctx, Code value) { if (mvar == -1) { value.gen(ctx); if (!javaType.equals("java/lang/Object")) ctx.typeInsn(CHECKCAST, javaType); if (directField == null) ctx.varInsn(ASTORE, id); else ctx.fieldInsn(PUTSTATIC, myClass, directField, javaDescr); } else { ctx.load(mvar).intConst(id); value.gen(ctx); ctx.insn(AASTORE); } } // called by Function.prepareConst when this bastard mutates into method void setCaptureType(String type) { javaType = type; javaDescr = type.charAt(0) == '[' ? type : 'L' + type + ';'; } void genBind(Ctx ctx) { setCaptureType(javaType(st.type)); if (ctx == null) return; // named lambdas use genBind for initializing the expr if (!var && st.prepareConst(ctx) && evalId == -1) { directBind = true; return; } if (directField == "") { // forceDirect, JavaClass does it myClass = ctx.className; directField = "$".concat(Integer.toString(ctx.constants.ctx.fieldCounter++)); ctx.cw.visitField(ACC_STATIC | ACC_SYNTHETIC | ACC_VOLATILE, directField, javaDescr, null, null).visitEnd(); } else if (mvar == -1) { id = ctx.localVarCount++; } genLocalSet(ctx, st); if (evalId != -1) { ctx.intConst(evalId); genPreGet(ctx); if (mvar != -1) ctx.intConst(id); ctx.methodInsn(INVOKESTATIC, "yeti/lang/compiler/YetiEval", "setBind", mvar == -1 ? "(ILjava/lang/Object;)V" : "(I[Ljava/lang/Object;I)V"); } } void gen(Ctx ctx) { genBind(ctx); result.gen(ctx); } void genIf(Ctx ctx, Label to, boolean ifTrue) { genBind(ctx); result.genIf(ctx, to, ifTrue); } } final class LoadModule extends Code { String moduleName; ModuleType moduleType; boolean checkUsed; boolean typedefUsed; private boolean used; LoadModule(String moduleName, ModuleType type, int depth) { this.type = type.copy(depth, null); this.moduleName = moduleName.toLowerCase(); moduleType = type; polymorph = true; } void gen(Ctx ctx) { if (checkUsed && !used) ctx.insn(ACONST_NULL); else ctx.methodInsn(INVOKESTATIC, moduleName, "eval", "()Ljava/lang/Object;"); } Binder bindField(final String name, final YType type) { return new Binder() { public BindRef getRef(final int line) { final boolean mutable = type.field == YetiType.FIELD_MUTABLE; if (!mutable && moduleType.directFields) { String fname = name.equals("eval") ? "eval$" : mangle(name); StaticRef r = new StaticRef(moduleName, fname, type, this, true, line); r.method = true; return r; } // property or mutable field // XXX in threory reading properties could be done directly used = true; return new SelectMember(type, LoadModule.this, name, line, !mutable) { boolean mayAssign() { return mutable; } boolean flagop(int fl) { return (fl & DIRECT_BIND) != 0 || (fl & ASSIGN) != 0 && mutable; } }; } }; } } final class Range extends Code { final Code from; final Code to; Range(Code from, Code to) { type = YetiType.NUM_TYPE; this.from = from; this.to = to; } void gen(Ctx ctx) { from.gen(ctx); to.gen(ctx); } } final class ListConstructor extends Code implements CodeGen { private Code[] items; private List key; ListConstructor(Code[] items) { int i; this.items = items; for (i = 0; i < items.length; ++i) if (!items[i].flagop(CONST)) return; // good, got constant list Object[] ak = new Object[items.length + 1]; ak[0] = "LIST"; for (i = 0; i < items.length; ++i) ak[i + 1] = items[i].valueKey(); key = Arrays.asList(ak); } public void gen2(Ctx ctx, Code param, int line) { for (int i = 0; i < items.length; ++i) { if (!(items[i] instanceof Range)) { ctx.typeInsn(NEW, "yeti/lang/LList"); ctx.insn(DUP); } items[i].gen(ctx); } ctx.insn(ACONST_NULL); for (int i = items.length; --i >= 0;) if (items[i] instanceof Range) ctx.methodInsn(INVOKESTATIC, "yeti/lang/ListRange", "range", "(Ljava/lang/Object;Ljava/lang/Object;" + "Lyeti/lang/AList;)Lyeti/lang/AList;"); else ctx.visitInit("yeti/lang/LList", "(Ljava/lang/Object;Lyeti/lang/AList;)V"); } void gen(Ctx ctx) { if (items.length == 0) { ctx.insn(ACONST_NULL); return; } if (key == null) gen2(ctx, null, 0); else ctx.constant(key, new SimpleCode(this, null, type, 0)); ctx.forceType("yeti/lang/AList"); } Object valueKey() { return key; } boolean flagop(int fl) { return (fl & STD_CONST) != 0 && (key != null || items.length == 0) || (fl & EMPTY_LIST) != 0 && items.length == 0 || (fl & LIST_RANGE) != 0 && items.length != 0 && items[0] instanceof Range; } } final class MapConstructor extends Code { Code[] keyItems; Code[] items; MapConstructor(Code[] keyItems, Code[] items) { this.keyItems = keyItems; this.items = items; } void gen(Ctx ctx) { ctx.typeInsn(NEW, "yeti/lang/Hash"); ctx.insn(DUP); if (keyItems.length > 16) { ctx.intConst(keyItems.length); ctx.visitInit("yeti/lang/Hash", "(I)V"); } else { ctx.visitInit("yeti/lang/Hash", "()V"); } for (int i = 0; i < keyItems.length; ++i) { ctx.insn(DUP); keyItems[i].gen(ctx); items[i].gen(ctx); ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Hash", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); ctx.insn(POP); } } boolean flagop(int fl) { return (fl & EMPTY_LIST) != 0 && keyItems.length == 0; } } final class EvalBind implements Binder, CaptureWrapper, Opcodes, CodeGen { YetiEval.Binding bind; EvalBind(YetiEval.Binding bind) { this.bind = bind; } public void gen2(Ctx ctx, Code value, int line) { genPreGet(ctx); genSet(ctx, value); ctx.insn(ACONST_NULL); } public BindRef getRef(int line) { return new BindRef() { { type = bind.type; binder = EvalBind.this; polymorph = !bind.mutable && bind.polymorph; } void gen(Ctx ctx) { genPreGet(ctx); genGet(ctx); } Code assign(final Code value) { return bind.mutable ? new SimpleCode(EvalBind.this, value, null, 0) : null; } boolean flagop(int fl) { return (fl & ASSIGN) != 0 && bind.mutable; } CaptureWrapper capture() { return EvalBind.this; } }; } public void genPreGet(Ctx ctx) { ctx.intConst(bind.bindId); ctx.methodInsn(INVOKESTATIC, "yeti/lang/compiler/YetiEval", "getBind", "(I)[Ljava/lang/Object;"); } public void genGet(Ctx ctx) { ctx.intConst(bind.index); ctx.insn(AALOAD); } public void genSet(Ctx ctx, Code value) { ctx.intConst(bind.index); value.gen(ctx); ctx.insn(AASTORE); } public Object captureIdentity() { return this; } public String captureType() { return "[Ljava/lang/Object;"; } }