// ex: se sts=4 sw=4 expandtab:
/*
* Yeti language compiler java bytecode generator.
*
* Copyright (c) 2007-2013 Madis Janson
* Copyright (c) 2011 Gergő Érdi
* 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.Label;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.IdentityHashMap;
import java.util.HashMap;
interface Closure {
// Closures "wrap" references to the outside world.
BindRef refProxy(BindRef code);
void addVar(BindExpr binder);
}
interface CaptureWrapper {
void genPreGet(Ctx ctx);
void genGet(Ctx ctx);
void genSet(Ctx ctx, Code value);
Object captureIdentity();
String captureType();
}
class Apply extends Code {
final Code fun, arg;
final int line;
int arity = 1;
BindExpr.Ref ref;
Apply(YType res, Code fun, Code arg, int line) {
type = res;
this.fun = fun;
this.arg = arg;
this.line = line;
}
void gen(Ctx ctx) {
Function f;
int argc = 0;
// Function sets its methodImpl field, if it has determined that
// it optimises itself into simple method.
if (ref != null &&
(f = (Function) ((BindExpr) ref.binder).st).methodImpl != null
&& arity == (argc = f.methodImpl.argVar)) {
//System.err.println("A" + arity + " F" + argc);
// first argument is function value (captures array really)
StringBuffer sig = new StringBuffer(f.capture1 ? "(" : "([");
sig.append("Ljava/lang/Object;");
Apply a = this; // "this" is the last argument applied, so reverse
Code[] args = new Code[argc];
for (int i = argc; --i > 0; a = (Apply) a.fun)
args[i] = a.arg;
args[0] = a.arg; // out-of-cycle as we need "a" for fun
a.fun.gen(ctx);
if (!f.capture1)
ctx.typeInsn(CHECKCAST, "[Ljava/lang/Object;");
for (int i = 0; i < argc; ++i) {
args[i].gen(ctx);
sig.append("Ljava/lang/Object;");
}
sig.append(")Ljava/lang/Object;");
ctx.visitLine(line);
ctx.methodInsn(INVOKESTATIC, f.name, f.bindName, sig.toString());
return;
}
if (fun instanceof Function) {
f = (Function) fun;
LoadVar arg_ = new LoadVar();
// inline direct calls
// TODO: constants don't need a temp variable
if (f.uncapture(arg_)) {
arg.gen(ctx);
arg_.var = ctx.localVarCount++;
ctx.varInsn(ASTORE, arg_.var);
f.genClosureInit(ctx);
f.body.gen(ctx);
return;
}
}
Apply to = (arity & 1) == 0 && arity - argc > 1 ? (Apply) fun : this;
to.fun.gen(ctx);
ctx.visitLine(to.line);
ctx.typeInsn(CHECKCAST, "yeti/lang/Fun");
if (to == this) {
ctx.visitApply(arg, line);
} else {
to.arg.gen(ctx);
arg.gen(ctx);
ctx.visitLine(line);
ctx.methodInsn(INVOKEVIRTUAL, "yeti/lang/Fun", "apply",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
}
}
Code apply(Code arg, final YType res, int line) {
Apply a = new Apply(res, this, arg, line);
a.arity = arity + 1;
if (ref != null) {
ref.arity = a.arity;
a.ref = ref;
}
return a;
}
}
/*
* Since the stupid JVM discards local stack when catching exceptions,
* try-catch blocks have to be converted into fucking closures
* (at least for the generic case).
*/
final class TryCatch extends CapturingClosure {
private List catches = new ArrayList();
private int exVar;
Code block;
Code cleanup;
final class Catch extends BindRef implements Binder {
Code handler;
public BindRef getRef(int line) {
return this;
}
void gen(Ctx ctx) {
ctx.load(exVar);
}
}
void setBlock(Code block) {
this.type = block.type;
this.block = block;
}
Catch addCatch(YType ex) {
Catch c = new Catch();
c.type = ex;
catches.add(c);
return c;
}
void captureInit(Ctx ctx, Capture c, int n) {
c.localVar = n;
c.captureGen(ctx);
}
void gen(Ctx ctx) {
int argc = mergeCaptures(ctx, true);
StringBuffer sigb = new StringBuffer("(");
for (Capture c = captures; c != null; c = c.next) {
sigb.append(c.captureType());
}
sigb.append(")Ljava/lang/Object;");
String sig = sigb.toString();
String name = ctx.methodName(null);
ctx.methodInsn(INVOKESTATIC, ctx.className, name, sig);
Ctx mc = ctx.newMethod(ACC_PRIVATE | ACC_STATIC, name, sig);
mc.localVarCount = argc;
Label codeStart = new Label(), codeEnd = new Label();
Label cleanupStart = cleanup == null ? null : new Label();
Label cleanupEntry = cleanup == null ? null : new Label();
genClosureInit(mc);
int retVar = -1;
if (cleanupStart != null) {
retVar = mc.localVarCount++;
mc.insn(ACONST_NULL);
mc.varInsn(ASTORE, retVar); // silence the JVM verifier...
}
mc.visitLabel(codeStart);
block.gen(mc);
mc.visitLabel(codeEnd);
exVar = mc.localVarCount++;
if (cleanupStart != null) {
Label goThrow = new Label();
mc.visitLabel(cleanupEntry);
mc.varInsn(ASTORE, retVar);
mc.insn(ACONST_NULL);
mc.visitLabel(cleanupStart);
mc.varInsn(ASTORE, exVar);
cleanup.gen(mc);
mc.insn(POP); // cleanup's null
mc.load(exVar).jumpInsn(IFNONNULL, goThrow);
mc.load(retVar).insn(ARETURN);
mc.visitLabel(goThrow);
mc.load(exVar).insn(ATHROW);
} else {
mc.insn(ARETURN);
}
for (int i = 0, cnt = catches.size(); i < cnt; ++i) {
Catch c = (Catch) catches.get(i);
Label catchStart = new Label();
mc.tryCatchBlock(codeStart, codeEnd, catchStart,
c.type.javaType.className());
Label catchEnd = null;
if (cleanupStart != null) {
catchEnd = new Label();
mc.tryCatchBlock(catchStart, catchEnd, cleanupStart, null);
}
mc.visitLabel(catchStart);
mc.varInsn(ASTORE, exVar);
c.handler.gen(mc);
if (catchEnd != null) {
mc.visitLabel(catchEnd);
mc.jumpInsn(GOTO, cleanupEntry);
} else {
mc.insn(ARETURN);
}
}
if (cleanupStart != null)
mc.tryCatchBlock(codeStart, codeEnd, cleanupStart, null);
mc.closeMethod();
}
}
/*
* Bind reference that is actually some wrapper created by closure (capturer).
* This class is mostly useful as a place where tail call optimization happens.
*/
abstract class CaptureRef extends BindRef {
Function capturer;
BindRef ref;
private Binder[] args;
private Capture[] argCaptures;
private boolean hasArgCaptures;
final class SelfApply extends Apply {
boolean tail;
int depth;
SelfApply(YType type, Code f, Code arg, int line, int depth) {
super(type, f, arg, line);
this.depth = depth;
if (origin != null) {
this.arity = origin.arity = args.length - depth + 1;
this.ref = origin;
}
if (depth == 0 && capturer.argCaptures == null) {
if (hasArgCaptures)
throw new CompileException(line, 0,
"Internal error - already has argCaptures");
hasArgCaptures = true;
// we have to resolve the captures lazily later,
// as here all might not yet be referenced
capturer.argCaptures = CaptureRef.this;
}
}
// evaluates call arguments and pushes values into stack
void genArg(Ctx ctx, int i) {
if (i > 0)
((SelfApply) fun).genArg(ctx, i - 1);
arg.gen(ctx);
}
void gen(Ctx ctx) {
if (!tail || depth != 0 || capturer.argCaptures != CaptureRef.this
|| capturer.restart == null) {
// regular apply, if tail call optimisation can't be done
super.gen(ctx);
return;
}
// push all argument values into stack - they must be evaluated
// BEFORE modifying any of the arguments for tail-"call"-jump.
genArg(ctx, argCaptures() == null ? 0 : argCaptures.length);
ctx.varInsn(ASTORE, capturer.argVar);
// Now assign the call argument values into argument registers.
if (argCaptures != null) {
int i = argCaptures.length;
// XXX: The merged-argument fix is needed only when
// argCaptures[i - 1] == null - the argument is
// wrongly considered unused by the tailcall optimizer.
if (capturer.outer != null && capturer.outer.merged &&
i > 0 && argCaptures[i - 1] == null) {
--i;
ctx.varInsn(ASTORE, 1); // HACK - fixes merged argument
}
while (--i >= 0)
if (argCaptures[i] != null)
ctx.varInsn(ASTORE, argCaptures[i].localVar);
else
ctx.insn(POP);
}
// And just jump into the start of the function...
ctx.jumpInsn(GOTO, capturer.restart);
}
void markTail() {
tail = true;
}
Code apply(Code arg, YType res, int line) {
if (depth < 0)
return super.apply(arg, res, line);
return new SelfApply(res, this, arg, line, depth - 1);
}
}
Capture[] argCaptures() {
if (hasArgCaptures && argCaptures == null) {
/*
* All arguments have been applied, now we have to search
* their captures in the inner function (by looking for
* captures matching the function arguments).
* Resulting list will be also given to the inner function,
* so it could copy those captures into local registers
* to allow tail call.
*
* NB. To understand this, remember that this is self-apply,
* so current scope is also the scope of applied function.
*/
argCaptures = new Capture[args.length];
for (Capture c = capturer.captures; c != null; c = c.next)
for (int i = args.length; --i >= 0;)
if (c.binder == args[i]) {
argCaptures[i] = c;
break;
}
}
return argCaptures;
}
Code apply(Code arg, YType res, int line) {
if (args != null) {
return new SelfApply(res, this, arg, line, args.length);
}
/*
* We have application with arg x like ((f x) y) z.
* Now we take the inner function of our scope and travel
* through its outer functions until there is one.
*
* If function that recognizes f as itself is met,
* we know that this is self-application and how many
* arguments are needed to do tail-call optimisation.
* SelfApply with arguments count is given in that case.
*
* SelfApply.apply reduces the argument count until final
* call is reached, in which case tail-call can be done,
* if the application happens to be in tail position.
*/
int n = 0;
for (Function f = capturer; f != null; ++n, f = f.outer)
if (f.selfBind == ref.binder) {
if (ref.flagop(ASSIGN))
break; // no tail recursion for vars
args = new Binder[n];
f = capturer.outer;
for (int i = n; --i >= 0; f = f.outer)
args[i] = f;
return new SelfApply(res, this, arg, line, n);
}
return super.apply(arg, res, line);
}
}
final class Capture extends CaptureRef implements CaptureWrapper, CodeGen {
String id;
Capture next;
CaptureWrapper wrapper;
Object identity;
int localVar = -1; // -1 - use this (TryCatch captures use 0 localVar)
boolean uncaptured;
boolean ignoreGet;
private String refType;
void gen(Ctx ctx) {
if (uncaptured) {
ref.gen(ctx);
return;
}
genPreGet(ctx);
genGet(ctx);
}
String getId(Ctx ctx) {
if (id == null)
id = "_".concat(Integer.toString(ctx.fieldCounter++));
return id;
}
boolean flagop(int fl) {
/*
* DIRECT_BIND is allowed, because with code like
* x = 1; try x finally yrt
* the 1 won't get directly brought into try closure
* unless the mergeCaptures uncaptures the DIRECT_BIND ones
* (the variable doesn't (always) know that it will be
* a direct binding when it's captured, as this determined
* later using prepareConst())
* XXX An automatic uncapture is done on DIRECT_BIND, as it allows
* cascading uncaptures done by mergeCaptures into parent captures,
* avoiding attempts to generate parent capture wrappings in that case.
* This fixes 'try-catch class closure' test.
*/
if (fl == DIRECT_BIND && !uncaptured)
return uncaptured = ref.flagop(fl);
return (fl & (PURE | ASSIGN | DIRECT_BIND)) != 0 && ref.flagop(fl);
}
public void gen2(Ctx ctx, Code value, int __) {
if (uncaptured) {
ref.assign(value).gen(ctx);
} else {
genPreGet(ctx);
wrapper.genSet(ctx, value);
ctx.insn(ACONST_NULL);
}
}
Code assign(final Code value) {
if (!ref.flagop(ASSIGN))
return null;
return new SimpleCode(this, value, null, 0);
}
public void genPreGet(Ctx ctx) {
if (uncaptured) {
wrapper.genPreGet(ctx);
} else if (localVar < 0) {
ctx.load(0);
if (localVar < -1) {
ctx.intConst(-2 - localVar);
ctx.insn(AALOAD);
if (wrapper != null) {
String cty = wrapper.captureType();
if (cty != null)
ctx.captureCast(cty);
}
} else {
ctx.fieldInsn(GETFIELD, ctx.className, id, captureType());
}
} else {
ctx.load(localVar);
// hacky way to forceType on try-catch, but not on method argument
if (!ignoreGet) {
ctx.forceType(captureType().charAt(0) == '['
? refType : refType.substring(1, refType.length() - 1));
}
}
}
public void genGet(Ctx ctx) {
if (wrapper != null && !ignoreGet) {
/*
* The object got from capture might not be the final value.
* for example captured mutable variables are wrapped into array
* by the binding, so the wrapper must get correct array index
* out of the array in that case.
*/
wrapper.genGet(ctx);
}
}
public void genSet(Ctx ctx, Code value) {
wrapper.genSet(ctx, value);
}
public CaptureWrapper capture() {
if (uncaptured) {
return ref.capture();
}
return wrapper == null ? null : this;
}
public Object captureIdentity() {
return wrapper == null ? this : wrapper.captureIdentity();
}
public String captureType() {
if (refType == null) {
if (wrapper != null) {
refType = wrapper.captureType();
if (refType == null)
throw new IllegalStateException("captureType:" + wrapper);
} else if (origin != null) {
refType = ((BindExpr) binder).captureType();
} else {
refType = 'L' + javaType(ref.type) + ';';
}
}
return refType;
}
void captureGen(Ctx ctx) {
if (wrapper == null) {
ref.gen(ctx);
} else {
wrapper.genPreGet(ctx);
}
// stupid AALOAD in genPreGet returns shit,
// so have to captureCast for it...
ctx.captureCast(captureType());
}
BindRef unshare() {
return new BindWrapper(this);
}
}
abstract class AClosure extends Code implements Closure {
private List closureVars = new ArrayList();
public void addVar(BindExpr binder) {
closureVars.add(binder);
}
public final void genClosureInit(Ctx ctx) {
int id = -1, mvarcount = 0;
for (int i = closureVars.size(); --i >= 0;) {
BindExpr bind = (BindExpr) closureVars.get(i);
if (bind.assigned && bind.captured) {
if (id == -1) {
id = ctx.localVarCount++;
}
bind.setMVarId(this, id, mvarcount++);
}
}
if (mvarcount > 0) {
ctx.intConst(mvarcount);
ctx.typeInsn(ANEWARRAY, "java/lang/Object");
ctx.varInsn(ASTORE, id);
}
}
}
abstract class CapturingClosure extends AClosure {
Capture captures;
Capture captureRef(BindRef code) {
for (Capture c = captures; c != null; c = c.next)
if (c.binder == code.binder) {
// evil hack... ref sharing broke fun-method
// optimisation accounting of ref usage
c.origin = code.origin;
return c;
}
Capture c = new Capture();
c.binder = code.binder;
c.type = code.type;
c.polymorph = code.polymorph;
c.ref = code;
c.wrapper = code.capture();
c.origin = code.origin;
c.next = captures;
captures = c;
return c;
}
public BindRef refProxy(BindRef code) {
return code.flagop(DIRECT_BIND) ? code : captureRef(code);
}
// Called by mergeCaptures to initialize a capture.
// It must be ok to copy capture after that.
abstract void captureInit(Ctx fun, Capture c, int n);
// mergeCaptures seems to drop only some uncaptured ones
// (looks like because so is easy to do, currently
// this seems to cause extra check only in Function.finishGen).
int mergeCaptures(Ctx ctx, boolean cleanup) {
int counter = 0;
Capture prev = null, next;
next_capture:
for (Capture c = captures; c != null; c = next) {
next = c.next;
Object identity = c.identity = c.captureIdentity();
if (cleanup && (c.uncaptured || c.ref.flagop(DIRECT_BIND))) {
c.uncaptured = true;
if (prev == null)
captures = next;
else
prev.next = next;
}
if (c.uncaptured)
continue;
// remove shared captures
for (Capture i = captures; i != c; i = i.next) {
if (i.identity == identity) {
c.id = i.id; // copy old one's id
c.localVar = i.localVar;
prev.next = next;
onMerge(c);
continue next_capture;
}
}
captureInit(ctx, c, counter++);
prev = c;
}
return counter;
}
void onMerge(Capture removed) {
}
}
final class Function extends CapturingClosure implements Binder {
private static final Code NEVER = new Code() {
void gen(Ctx ctx) {
throw new UnsupportedOperationException();
}
};
String name; // name of the generated function class
Binder selfBind;
Code body;
String bindName; // function (self)binding name, if there is any
// function body has asked self reference (and the ref is not mutable)
private CaptureRef selfRef;
Label restart; // used by tail-call optimizer
Function outer; // outer function of directly-nested function
// outer arguments to be saved in local registers (used for tail-call)
CaptureRef argCaptures;
// argument value for inlined function
private Code uncaptureArg;
// register used by argument (2 for merged inner function)
int argVar = 1;
// Marks function optimised as method and points to it's inner-most lambda
Function methodImpl;
// Function has been merged with its inner function.
boolean merged;
// How many times the argument has been used.
// This counter is also used by argument nulling to determine
// when it safe to assume that argument value is no more needed.
private int argUsed;
// Function is constant that can be statically shared.
// Stores function instance in static final _ field and allows
// direct-ref no-capture optimisations for function binding.
private boolean shared;
// Module has asked function to be a public (inner) class.
// Useful for making Java code happy, if it wants to call the function.
boolean publish;
// Function uses local bindings from its module. Published function
// should ensure module initialisation in this case, when called.
private boolean moduleInit;
// methodImpl and only one live capture - carry it directly.
boolean capture1;
// not in struct - capture final fields
private boolean notInStruct;
final BindRef arg = new BindRef() {
void gen(Ctx ctx) {
if (uncaptureArg != null) {
uncaptureArg.gen(ctx);
} else {
int t;
ctx.load(argVar);
// inexact nulling...
if (--argUsed == 0 && ctx.tainted == 0 &&
(t = type.deref().type) != YetiType.NUM &&
t != YetiType.BOOL) {
ctx.insn(ACONST_NULL);
ctx.varInsn(ASTORE, argVar);
}
}
}
boolean flagop(int fl) {
return (fl & PURE) != 0;
}
};
Function(YType type) {
this.type = type;
arg.binder = this;
}
public BindRef getRef(int line) {
++argUsed;
return arg;
}
// uncaptures captured variables if possible
// useful for function inlineing, don't work with self-refs
boolean uncapture(Code arg) {
if (selfRef != null || merged)
return false;
for (Capture c = captures; c != null; c = c.next)
c.uncaptured = true;
uncaptureArg = arg;
return true;
}
void setBody(Code body) {
this.body = body;
if (body instanceof Function) {
Function bodyFun = (Function) body;
bodyFun.outer = this;
if (argVar == 1 && !bodyFun.merged &&
bodyFun.selfRef == null && captures == null) {
merged = true;
++bodyFun.argVar;
}
}
}
/*
* When function body refers to bindings outside of it,
* at each closure border on the way out (to the binding),
* a refProxy (of the ending closure) is called, possibly
* transforming the BindRef.
*/
public BindRef refProxy(BindRef code) {
if (code.flagop(DIRECT_BIND)) {
if (code.flagop(MODULE_REQUIRED)) {
moduleInit = true;
}
return code;
}
if (selfBind == code.binder && !code.flagop(ASSIGN)) {
if (selfRef == null) {
selfRef = new CaptureRef() {
void gen(Ctx ctx) {
if (shared) {
Function.this.gen(ctx);
} else {
ctx.load(0).forceType("yeti/lang/Fun");
}
}
boolean flagop(int fl) {
// Don't be a capture when FunClass._ can be used
return (fl & DIRECT_BIND) != 0 && shared;
}
};
selfRef.binder = selfBind;
selfRef.type = code.type;
selfRef.ref = code;
// Right place for this should be outside of if (so it would
// be updated on multiple selfRefs), but allowing method-fun
// in such case slows some code down (b/c array capture).
// Having it here means non-first self-refs arity stays zero
// and so these will be considered to be used as fun-values.
selfRef.origin = code.origin;
selfRef.capturer = this;
}
// selfRef.origin = code.origin;
return selfRef;
}
if (merged) {
return code;
}
Capture c = captureRef(code);
c.capturer = this;
//expecting max 2 merged
if (outer != null && outer.merged &&
(code == outer.selfRef || code == outer.arg)) {
/*
* It's actually simple - because nested functions are merged,
* the parent argument is now real argument that can be
* directly accessed. Therefore capture proxy would only
* fuck things up - and so that proxy is marked uncaptured.
* Same goes for the parent-self-ref - it is now our this.
*
* Only problem is that tail-rec optimisation generates code,
* that wants to store into the "captured" variables copy
* before jumping back into the start of the function.
* The optimiser sets argCaptures which should be copied
* into local vars by function class generator, but this
* coping is skipped as pointless for uncaptured ones.
*
* Therefore the captures localVar is simply set here to 1,
* which happens to be parent args register (and is ignored
* by selfRefs). Probable alternative would be to set it
* when the copy code generation is skipped.
*/
c.localVar = 1; // really evil hack for tail-recursion.
c.uncaptured = true;
}
return c;
}
// called by mergeCaptures
void captureInit(Ctx fun, Capture c, int n) {
if (methodImpl == null) {
// c.getId() initialises the captures id as a side effect
fun.cw.visitField(notInStruct ? ACC_PRIVATE | ACC_FINAL : 0,
c.getId(fun), c.captureType(),
null, null).visitEnd();
} else if (capture1) {
assert (n == 0);
c.localVar = 0;
fun.load(0).captureCast(c.captureType());
fun.varInsn(ASTORE, 0);
} else {
c.localVar = -2 - n;
}
}
private void prepareMethod(Ctx ctx) {
/*
* The make-a-method trick is actually damn easy I think.
* The captures of the innermost joined lambda must be set
* to refer to the method arguments and closure array instead.
* This is done by mapping our arguments and outer capture set
* into good vars. After that the inner captures can be scanned
* and made to point to those values.
*/
// Map captures using binder as identity.
Map captureMapping = null;
/*
* This has to be done before mergeCaptures to have all binders.
* NOP for 1/2-arg functions - they don't have argument captures and
* the outer captures localVar's will be set by mergeCaptures
* (unless the 2 argument function _has_ argument captures,
* which can happen when the inner function is constructed by
* some weird kind of lambda!).
*/
if (methodImpl != this &&
(methodImpl != body || methodImpl.captures != null)) {
captureMapping = new IdentityHashMap();
// Function is binder for it's argument
int argCounter = 0;
for (Function f = this; f != methodImpl; f = (Function) f.body) {
// just to hold localVar
Capture tmp = new Capture();
tmp.localVar = ++argCounter;
f.argVar = argCounter; // merge fucks up the pre-last capture
captureMapping.put(f, tmp);
}
methodImpl.argVar = ++argCounter;
for (Capture c = captures; c != null; c = c.next)
captureMapping.put(c.binder, c);
Capture tmp = new Capture();
tmp.localVar = 0;
captureMapping.put(selfBind, tmp);
}
// Create method
if (bindName != null)
bindName = mangle(bindName);
bindName = ctx.methodName(bindName);
StringBuffer sig = new StringBuffer(capture1 ? "(" : "([");
for (int i = methodImpl.argVar + 2; --i >= 0;) {
if (i == 0)
sig.append(')');
sig.append("Ljava/lang/Object;");
}
Ctx m = ctx.newMethod(ACC_STATIC, bindName, sig.toString());
// Removes duplicate captures and calls captureInit
// (which sets captures localVar for our case).
int captureCount = mergeCaptures(m, false);
// Hijack the inner functions capture mapping...
if (captureMapping != null)
for (Capture c = methodImpl.captures; c != null; c = c.next) {
Object mapped = captureMapping.get(c.binder);
if (mapped != null) {
c.localVar = ((Capture) mapped).localVar;
c.ignoreGet = c.localVar > 0;
} else { // Capture was stealed away by direct bind?
Capture x = c;
while (x.capturer != this && x.ref instanceof Capture)
x = (Capture) x.ref;
if (x.uncaptured) {
c.ref = x.ref;
c.uncaptured = true;
}
}
}
// Generate method body
name = ctx.className;
m.localVarCount = methodImpl.argVar + 1; // capturearray, args
methodImpl.genClosureInit(m);
m.visitLabel(methodImpl.restart = new Label());
methodImpl.body.gen(m);
methodImpl.restart = null;
m.insn(ARETURN);
m.closeMethod();
if (!shared && !capture1) {
ctx.intConst(captureCount);
ctx.typeInsn(ANEWARRAY, "java/lang/Object");
}
}
/*
* For functions, this generates the function class.
* An instance is also given, but capture fields are not initialised
* (the captures are set later in the finishGen).
*/
boolean prepareGen(Ctx ctx, boolean notStruct) {
if (methodImpl != null) {
prepareMethod(ctx);
return false;
}
if (merged) { // 2 nested lambdas have been optimised into 1
Function inner = (Function) body;
inner.bindName = bindName;
boolean res = inner.prepareGen(ctx, notStruct);
name = inner.name;
return res;
}
notInStruct = notStruct;
if (bindName == null)
bindName = "";
name = ctx.compilation.createClassName(ctx,
ctx.className, mangle(bindName));
publish &= shared;
String funClass =
argVar == 2 ? "yeti/lang/Fun2" : "yeti/lang/Fun";
Ctx fun = ctx.newClass(ACC_SUPER | ACC_FINAL, name, funClass, null, 0);
if (publish)
fun.markInnerClass(ctx, ACC_STATIC | ACC_FINAL);
mergeCaptures(fun, false);
if (!notStruct)
fun.createInit(shared ? ACC_PRIVATE : 0, funClass);
Ctx apply = argVar == 2
? fun.newMethod(ACC_PUBLIC + ACC_FINAL, "apply",
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;")
: fun.newMethod(ACC_PUBLIC + ACC_FINAL, "apply",
"(Ljava/lang/Object;)Ljava/lang/Object;");
apply.localVarCount = argVar + 1; // this, arg
if (argCaptures != null) {
// Tail recursion needs all args to be in local registers
// - otherwise it couldn't modify them safely before restarting
Capture[] args = argCaptures.argCaptures();
for (int i = 0; i < args.length; ++i) {
Capture c = args[i];
if (c != null && !c.uncaptured) {
c.gen(apply);
c.localVar = apply.localVarCount;
c.ignoreGet = true;
apply.varInsn(ASTORE, apply.localVarCount++);
}
}
}
if (moduleInit && publish)
apply.methodInsn(INVOKESTATIC, ctx.className, "init", "()V");
genClosureInit(apply);
apply.visitLabel(restart = new Label());
body.gen(apply);
restart = null;
apply.insn(ARETURN);
apply.closeMethod();
Ctx valueCtx =
shared ? fun.newMethod(ACC_STATIC, "<clinit>", "()V") : ctx;
valueCtx.typeInsn(NEW, name);
valueCtx.insn(DUP);
if (notStruct) { // final fields must be initialized in constructor
StringBuffer sigb = new StringBuffer("(");
for (Capture c = captures; c != null; c = c.next)
if (!c.uncaptured)
sigb.append(c.captureType());
String sig = sigb.append(")V").toString();
Ctx init = fun.newMethod(shared ? ACC_PRIVATE : 0, "<init>", sig);
init.load(0).methodInsn(INVOKESPECIAL, funClass, "<init>", "()V");
int counter = 0;
for (Capture c = captures; c != null; c = c.next)
if (!c.uncaptured) {
c.captureGen(valueCtx);
init.load(0).load(++counter)
.fieldInsn(PUTFIELD, name, c.id, c.captureType());
}
init.insn(RETURN);
init.closeMethod();
valueCtx.visitInit(name, sig);
valueCtx.forceType("yeti/lang/Fun");
} else {
valueCtx.visitInit(name, "()V");
}
if (shared) {
fun.cw.visitField(ACC_STATIC | ACC_FINAL,
"_", "Lyeti/lang/Fun;", null, null).visitEnd();
valueCtx.fieldInsn(PUTSTATIC, name, "_", "Lyeti/lang/Fun;");
valueCtx.insn(RETURN);
valueCtx.closeMethod();
}
return notStruct;
}
void finishGen(Ctx ctx) {
if (merged) {
((Function) body).finishGen(ctx);
return;
}
boolean meth = methodImpl != null;
int counter = -1;
// Capture a closure
for (Capture c = captures; c != null; c = c.next) {
if (c.uncaptured)
continue;
if (capture1) {
c.captureGen(ctx);
return;
}
ctx.insn(DUP);
if (meth) {
ctx.intConst(++counter);
c.captureGen(ctx);
ctx.insn(AASTORE);
} else {
c.captureGen(ctx);
ctx.fieldInsn(PUTFIELD, name, c.id, c.captureType());
}
}
ctx.forceType(meth ? "[Ljava/lang/Object;" : "yeti/lang/Fun");
}
boolean flagop(int fl) {
return merged ? ((Function) body).flagop(fl) :
(fl & (PURE | CONST)) != 0 && (shared || captures == null);
}
// Check whether all captures are actually static constants.
// If so, the function value should also be optimised into shared constant.
boolean prepareConst(Ctx ctx) {
if (shared) // already optimised into static constant value
return true;
BindExpr bindExpr = null;
// First try determine if we can reduce into method.
if (selfBind instanceof BindExpr &&
(bindExpr = (BindExpr) selfBind).evalId == -1 &&
bindExpr.result != null) {
int arityLimit = 99999999;
for (BindExpr.Ref i = bindExpr.refs; i != null; i = i.next) {
if (arityLimit > i.arity)
arityLimit = i.arity;
}
int arity = 0;
Function impl = this;
while (++arity < arityLimit && impl.body instanceof Function)
impl = (Function) impl.body;
/*
* Merged ones are a bit tricky - their capture set is
* merged into their inner one, where is also their own
* argument. Also their inner ones arg is messed up.
* Easier to not touch them, although it would be good for speed.
*/
if (arity > 0 && arityLimit > 0 && (arity > 1 || !merged)) {
//System.err.println("FF " + arity + " " + arityLimit +
// " " + bindName);
if (merged) { // steal captures and unmerge :)
captures = ((Function) body).captures;
merged = false;
}
methodImpl = impl.merged ? impl.outer : impl;
bindExpr.setCaptureType("[Ljava/lang/Object;");
}
}
if (merged) {
// merged functions are hollow, their juice is in the inner function
Function inner = (Function) body;
inner.bindName = bindName;
inner.publish = publish;
if (inner.prepareConst(ctx)) {
name = inner.name; // used by gen
return true;
}
return false;
}
// this can be optimised into "const x", so don't touch.
if (argUsed == 0 && argVar == 1 &&
methodImpl == null && body.flagop(PURE))
return false; //captures == null;
// Uncapture the direct bindings.
Capture prev = null;
int liveCaptures = 0;
for (Capture c = captures; c != null; c = c.next)
if (c.ref.flagop(DIRECT_BIND)) {
c.uncaptured = true;
if (prev == null)
captures = c.next;
else
prev.next = c.next;
} else {
// Why in the hell are existing uncaptured ones preserved?
// Does some checks them (selfref, args??) after prepareConst?
if (!c.uncaptured)
++liveCaptures;
prev = c;
}
if (methodImpl != null && liveCaptures == 1) {
capture1 = true;
bindExpr.setCaptureType("java/lang/Object");
}
// If all captures were uncaptured, then the function can
// (and will) be optimised into shared static constant.
if (liveCaptures == 0) {
shared = true;
prepareGen(ctx, false);
}
return liveCaptures == 0;
}
void gen(Ctx ctx) {
if (shared) {
if (methodImpl == null)
ctx.fieldInsn(GETSTATIC, name, "_", "Lyeti/lang/Fun;");
else
ctx.insn(ACONST_NULL);
} else if (!merged && argUsed == 0 && body.flagop(PURE) &&
uncapture(NEVER)) {
// This lambda can be optimised into "const x", so do it.
genClosureInit(ctx);
ctx.typeInsn(NEW, "yeti/lang/Const");
ctx.insn(DUP);
body.gen(ctx);
ctx.visitInit("yeti/lang/Const", "(Ljava/lang/Object;)V");
ctx.forceType("yeti/lang/Fun");
} else if (prepareConst(ctx)) {
if (methodImpl == null)
ctx.fieldInsn(GETSTATIC, name, "_", "Lyeti/lang/Fun;");
else
ctx.insn(ACONST_NULL);
} else if (!prepareGen(ctx, true))
finishGen(ctx);
}
}
class LoopExpr extends AClosure {
Code cond, body;
LoopExpr() {
this.type = YetiType.UNIT_TYPE;
}
public BindRef refProxy(BindRef code) {
return code;
}
void gen(Ctx ctx) {
Label start = new Label();
Label end = new Label();
ctx.visitLabel(start);
++ctx.tainted;
genClosureInit(ctx);
cond.genIf(ctx, end, false);
body.gen(ctx);
--ctx.tainted;
ctx.insn(POP);
ctx.jumpInsn(GOTO, start);
ctx.visitLabel(end);
ctx.insn(ACONST_NULL);
}
}
final class RootClosure extends LoopExpr {
LoadModule[] preload;
boolean isModule;
ModuleType moduleType;
int line;
void gen(Ctx ctx) {
genClosureInit(ctx);
for (int i = 0; i < preload.length; ++i) {
if (preload[i] != null) {
preload[i].gen(ctx);
ctx.insn(POP);
}
}
body.gen(ctx);
}
}