// ex: se sts=4 sw=4 expandtab: /* * Yeti language compiler java bytecode generator. * * Copyright (c) 2008-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 java.util.Map; import java.util.HashMap; import java.util.List; import java.util.ArrayList; import java.util.Iterator; final class JavaClass extends CapturingClosure implements Runnable { private String className; private String[] implement; private YetiType.ClassBinding parentClass; private List fields = new ArrayList(); private List methods = new ArrayList(); private Field serialVersion; private JavaExpr superInit; private final boolean isPublic; private boolean hasStatic; private int captureCount; private Map accessors; private Ctx classCtx; private Capture merged; private final int cline; // for duplicate class error YType classType; final Meth constr = new Meth(); final Binder self; Binder superRef; static class Arg extends BindRef implements Binder { int argn; final YType javaType; private boolean isSuper; Arg(YType type, boolean isSuper) { this.javaType = type; this.type = JavaType.convertValueType(type); this.isSuper = isSuper; binder = this; } public BindRef getRef(int line) { if (isSuper && line >= 0) throw new CompileException(line, 0, "super cannot be used as a value"); return this; } void gen(Ctx ctx) { ctx.load(argn); if (javaType.type == YetiType.JAVA_ARRAY) { ctx.forceType(JavaType.descriptionOf(javaType)); } else if (javaType.javaType.description.charAt(0) == 'L') { ctx.forceType(javaType.javaType.className()); } } boolean flagop(int flag) { return (flag & DIRECT_THIS) != 0 && argn == 0; } } static class Meth extends JavaType.Method implements Closure { private List args = new ArrayList(); private AClosure closure = new LoopExpr(); // just for closure init private int line; Capture captures; Code code; Binder addArg(YType type) { Arg arg = new Arg(type, false); args.add(arg); arg.argn = (access & ACC_STATIC) == 0 ? args.size() : args.size() - 1; return arg; } public BindRef refProxy(BindRef code) { return code; // method don't capture - this is outer classes job } public void addVar(BindExpr binder) { closure.addVar(binder); } void init() { arguments = new YType[args.size()]; for (int i = 0; i < arguments.length; ++i) { Arg arg = (Arg) args.get(i); arguments[i] = arg.javaType; } sig = name.concat(super.descr(null)); descr = null; } String descr(String extra) { if (descr != null) return descr; StringBuffer additionalArgs = new StringBuffer(); for (Capture c = captures; c != null; c = c.next) additionalArgs.append(c.captureType()); return super.descr(additionalArgs.toString()); } void convertArgs(Ctx ctx) { int n = (access & ACC_STATIC) == 0 ? 1 : 0; int at = n; for (int i = 0; i < arguments.length; ++i) { ++at; if (arguments[i].type != YetiType.JAVA) continue; String descr = arguments[i].javaType.description; if (descr != "Ljava/lang/String;" && descr.charAt(0) == 'L') continue; --at; at += loadArg(ctx, arguments[i], at); JavaExpr.convertValue(ctx, arguments[i]); ctx.varInsn(ASTORE, i + n); } ctx.localVarCount = at; } void gen(Ctx ctx) { ctx = ctx.newMethod(access, name, descr(null)); if ((access & ACC_ABSTRACT) != 0) { ctx.closeMethod(); return; } convertArgs(ctx); closure.genClosureInit(ctx); JavaExpr.convertedArg(ctx, code, returnType, line); if (returnType.type == YetiType.UNIT) { ctx.insn(POP); ctx.insn(RETURN); } else { genRet(ctx, returnType); } ctx.closeMethod(); } } final class Field extends Code implements Binder, CaptureWrapper, CodeGen { private String name; // mangled name private String javaType; private String descr; Code value; private final boolean var; private int access = ACC_PRIVATE; private boolean directConst; Field(String name, Code value, boolean var) { this.name = name; this.value = value; this.var = var; } public void genPreGet(Ctx ctx) { if (!directConst) ctx.load(0); } public void genGet(Ctx ctx) { if (directConst) value.gen(ctx); else ctx.fieldInsn(GETFIELD, className, name, descr); } public void genSet(Ctx ctx, Code value) { value.gen(ctx); ctx.typeInsn(CHECKCAST, javaType); ctx.fieldInsn(PUTFIELD, className, name, descr); } public Object captureIdentity() { return JavaClass.this; } public String captureType() { return classType.javaType.description; } public void gen2(Ctx ctx, Code value, int __) { genPreGet(ctx); genSet(ctx, value); ctx.insn(ACONST_NULL); } public BindRef getRef(int line) { if (javaType == null) { if (name == "_") throw new IllegalStateException("NO _ REF"); javaType = Code.javaType(value.type); descr = 'L' + javaType + ';'; } BindRef ref = new BindRef() { void gen(Ctx ctx) { genPreGet(ctx); genGet(ctx); } Code assign(final Code value) { return var ? new SimpleCode(Field.this, value, null, 0) : null; } boolean flagop(int fl) { return (fl & ASSIGN) != 0 && var || (fl & (CONST | DIRECT_BIND)) != 0 && directConst || (fl & PURE) != 0 && !var; } CaptureWrapper capture() { if (!var) return null; access = ACC_SYNTHETIC; // clear private return Field.this; } }; ref.type = value.type; ref.binder = this; return ref; } void gen(Ctx ctx) { if (this == serialVersion) { // hack to allow defining serialVersionUID Long v = new Long((((NumericConstant) value).num).longValue()); ctx.cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, name, "J", null, v); directConst = true; } else if (javaType == null) { // _ = or just unused binding value.gen(ctx); ctx.insn(POP); } else if (!var && value.prepareConst(ctx)) { directConst = true; } else { ctx.cw.visitField(var ? access : access | ACC_FINAL, name, descr, null, null).visitEnd(); genPreGet(ctx); genSet(ctx, value); } } } JavaClass(String className, boolean isPublic, int line) { type = YetiType.UNIT_TYPE; this.className = className; classType = new YType(YetiType.JAVA, YetiType.NO_PARAM); classType.javaType = JavaType.createNewClass(className, this); self = new Arg(classType, false); constr.name = "<init>"; constr.returnType = YetiType.UNIT_TYPE; constr.className = className; constr.access = isPublic ? ACC_PUBLIC : 0; this.isPublic = isPublic; cline = line; } private static int loadArg(Ctx ctx, YType argType, int n) { int ins = ALOAD; if (argType.type == YetiType.JAVA) { switch (argType.javaType.description.charAt(0)) { case 'D': ins = DLOAD; break; case 'F': ins = FLOAD; break; case 'J': ins = LLOAD; break; case 'L': break; default : ins = ILOAD; } } ctx.varInsn(ins, n); return ins == DLOAD ? 2 : 1; } static void genRet(Ctx ctx, YType returnType) { int ins = ARETURN; if (returnType.type == YetiType.JAVA) { switch (returnType.javaType.description.charAt(0)) { case 'D': ins = DRETURN; break; case 'F': ins = FRETURN; break; case 'J': ins = LRETURN; break; case 'L': break; case 'V': ins = RETURN; break; default : ins = IRETURN; } } ctx.insn(ins); } void init(YetiType.ClassBinding parentClass, String[] interfaces) { implement = interfaces; this.parentClass = parentClass; YType t = new YType(YetiType.JAVA, YetiType.NO_PARAM); t.javaType = parentClass.type.javaType.dup(); t.javaType.implementation = this; t.javaType.publicMask = ACC_PUBLIC | ACC_PROTECTED; superRef = new Arg(t, true); } Meth addMethod(String name, YType returnType, String mod, int line) { Meth m = new Meth(); m.name = name; m.returnType = returnType; m.className = className; m.access = mod == "static-method" ? ACC_PUBLIC + ACC_STATIC : mod == "abstract-method" ? ACC_PUBLIC + ACC_ABSTRACT : ACC_PUBLIC; if ((m.access & ACC_STATIC) != 0) hasStatic = true; m.line = line; methods.add(m); return m; } Binder addField(Code value, boolean var, String name) { Field field; if (name == "serialVersionUID" && !var && serialVersion == null && value instanceof NumericConstant) { serialVersion = field = new Field(name, value, false); } else { field = new Field("$" + fields.size(), value, var); } fields.add(field); return field; } public BindRef refProxy(BindRef code) { if (code.flagop(DIRECT_BIND)) return code; if (!isPublic) return captureRef(code); code.forceDirect(); return code; } void superInit(JavaType.Method init, Code[] args, int line) { superInit = new JavaExpr(null, init, args, line); } void close() throws JavaClassNotFoundException { constr.init(); JavaTypeReader t = new JavaTypeReader(); t.constructors.add(constr); for (int i = 0, cnt = methods.size(); i < cnt; ++i) { Meth m = (Meth) methods.get(i); m.init(); ((m.access & ACC_STATIC) != 0 ? t.staticMethods : t.methods).add(m); } t.parent = parentClass.type.javaType; t.className = className; t.interfaces = implement; t.access = isPublic ? ACC_PUBLIC : 0; classType.javaType.publicMask = ACC_PUBLIC | ACC_PROTECTED; classType.javaType.resolve(t); } // must be called after close BindRef[] getCaptures() { captureCount = mergeCaptures(null, true); BindRef[] r = new BindRef[captureCount]; int n = 0; for (Capture c = captures; c != null; c = c.next) { r[n++] = c.ref; } return r; } // called by mergeCaptures void captureInit(Ctx fun, Capture c, int n) { c.id = "_" + n; // for super arguments c.localVar = n + constr.args.size() + 1; } void onMerge(Capture removed) { removed.next = merged; merged = removed; } String getAccessor(JavaType.Method method, String descr, boolean invokeSuper) { if (accessors == null) accessors = new HashMap(); String sig = method.sig; if (invokeSuper) sig = "*".concat(method.sig); Object[] accessor = (Object[]) accessors.get(sig); if (accessor == null) { accessor = new Object[] { "access$" + accessors.size(), method, descr, invokeSuper ? "" : null }; accessors.put(method.sig, accessor); } return (String) accessor[0]; } String getAccessor(JavaType.Field field, String descr, boolean write) { String key = (write ? "{" : "}").concat(field.name); if (accessors == null) accessors = new HashMap(); Object[] accessor = (Object[]) accessors.get(key); if (accessor == null) { accessor = new Object[] { "access$" + accessors.size(), field, descr, write ? "" : null, null }; accessors.put(key, accessor); } return (String) accessor[0]; } void gen(Ctx ctx) { int i, cnt; Capture c; constr.captures = captures; ctx.insn(ACONST_NULL); Ctx clc = ctx.newClass(classType.javaType.access | ACC_SUPER, className, parentClass.type.javaType.className(), implement, cline); clc.fieldCounter = captureCount; // block using our method names ;) for (i = 0, cnt = methods.size(); i < cnt; ++i) clc.usedMethodNames.put(((Meth) methods.get(i)).name, null); if (!isPublic) clc.markInnerClass(ctx.constants.ctx, ACC_STATIC); Ctx init = clc.newMethod(constr.access, "<init>", constr.descr(null)); if (isPublic && !hasStatic) init.methodInsn(INVOKESTATIC, ctx.className, "init", "()V"); constr.convertArgs(init); genClosureInit(init); superInit.genCall(init.load(0), parentClass.getCaptures(), INVOKESPECIAL); // extra arguments are used for smuggling in captured bindings int n = constr.arguments.length; for (c = captures; c != null; c = c.next) { c.localVar = -1; // reset to using this clc.cw.visitField(ACC_FINAL | ACC_PRIVATE, c.id, c.captureType(), null, null).visitEnd(); init.load(0).load(++n) .fieldInsn(PUTFIELD, className, c.id, c.captureType()); } for (c = merged; c != null; c = c.next) c.localVar = -1; // reset all merged captures also for (i = 0, cnt = fields.size(); i < cnt; ++i) ((Code) fields.get(i)).gen(init); init.insn(RETURN); init.closeMethod(); for (i = 0, cnt = methods.size(); i < cnt; ++i) ((Meth) methods.get(i)).gen(clc); if (isPublic && hasStatic) { Ctx clinit = clc.newMethod(ACC_STATIC, "<clinit>", "()V"); clinit.methodInsn(INVOKESTATIC, ctx.className, "init", "()V"); clinit.insn(RETURN); clinit.closeMethod(); } classCtx = clc; ctx.compilation.postGen.add(this); } // postGen hook. accessors can be added later than the class gen is called public void run() { if (accessors == null) return; for (Iterator i = accessors.values().iterator(); i.hasNext(); ) { Object[] accessor = (Object[]) i.next(); int acc = ACC_STATIC; JavaType.Method m = null; if (accessor.length == 4) { // method m = (JavaType.Method) accessor[1]; acc = m.access & ACC_STATIC; } Ctx mc = classCtx.newMethod(acc | ACC_SYNTHETIC, (String) accessor[0], (String) accessor[2]); if (m != null) { // method int start = 0; int insn = INVOKESTATIC; if ((acc & ACC_STATIC) == 0) { insn = accessor[3] == null ? INVOKEVIRTUAL : INVOKESPECIAL; start = 1; mc.load(0); } for (int j = 0; j < m.arguments.length; ) j += loadArg(mc, m.arguments[j], j + start); mc.methodInsn(insn, accessor[3] == null ? className : parentClass.type.javaType.className(), m.name, m.descr(null)); genRet(mc, m.returnType); } else { // field JavaType.Field f = (JavaType.Field) accessor[1]; int insn = GETSTATIC, reg = 0; if ((f.access & ACC_STATIC) == 0) { mc.load(reg++); insn = GETFIELD; } if (accessor[3] != null) { mc.load(reg); insn = insn == GETFIELD ? PUTFIELD : PUTSTATIC; } mc.fieldInsn(insn, className, f.name, JavaType.descriptionOf(f.type)); if (accessor[3] != null) { mc.insn(RETURN); } else { genRet(mc, f.type); } } mc.closeMethod(); } } }