Simplified, extended jit implementation

This commit is contained in:
James Roseborough
2008-05-18 15:12:03 +00:00
parent c5c322fee1
commit 039d79fe5f
13 changed files with 314 additions and 174 deletions

View File

@@ -29,23 +29,12 @@ public class LClosure extends LFunction {
public LTable env;
/**
* @deprecated construct with environment instead
* @param state
* @param p
*/
public LClosure(LuaState state, LPrototype p) {
this.env = state._G;
this.p = p;
upVals = new UpVal[p.nups];
}
/**
* Construct using a prototype and initial environment.
* @param p
* @param env
*/
public LClosure(LPrototype p, LTable env) {
protected LClosure(LPrototype p, LTable env) {
this.p = p;
this.env = env;
upVals = new UpVal[p.nups];

View File

@@ -450,7 +450,7 @@ public class LuaState extends Lua {
public int load( InputStream is, String chunkname ) {
try {
LPrototype p = LoadState.undump(this, is, chunkname );
pushlvalue( new LClosure( p, _G ) );
pushlvalue( p.newClosure( _G ) );
return 0;
} catch ( Throwable t ) {
pushstring( t.getMessage() );
@@ -840,7 +840,7 @@ public class LuaState extends Lua {
case LuaState.OP_CLOSURE: {
b = LuaState.GETARG_Bx(i);
proto = cl.p.p[b];
newClosure = new LClosure(proto, cl.env);
newClosure = proto.newClosure(cl.env);
for (int j = 0; j < newClosure.upVals.length; j++, ci.pc++) {
i = code[ci.pc];
o = LuaState.GET_OPCODE(i);

View File

@@ -193,7 +193,7 @@ public class StandardLuaJVM {
LPrototype p = LoadState.undump(state, is, getScript());
// create closure and execute
final LClosure c = new LClosure(p, state._G);
final LClosure c = p.newClosure(state._G);
String[] args = getScriptArgs();
int numOfScriptArgs = (args != null ? args.length : 0);
LValue[] vargs = new LValue[numOfScriptArgs];

View File

@@ -69,7 +69,7 @@ public class LuaRunner {
LPrototype p = LoadState.undump(state, is, script);
// create closure and execute
LClosure c = new LClosure( p, state._G );
LClosure c = p.newClosure( state._G );
// do the call
state.doCall( c, new LValue[0] );

View File

@@ -58,7 +58,7 @@ public class LuajavaRunner {
LPrototype p = LoadState.undump(state, is, script);
// create closure and execute
LClosure c = new LClosure( p, state._G );
LClosure c = p.newClosure( state._G );
state.doCall(c, new LValue[0]);
}

View File

@@ -14,6 +14,10 @@ public class JitPrototype extends LPrototype {
super();
}
protected void unimplemented() {
throw new RuntimeException("unimplemented");
}
protected void setLuaPrototype(LPrototype lp) {
this.p = lp;
}
@@ -22,17 +26,17 @@ public class JitPrototype extends LPrototype {
return new JitClosure(this, env);
}
private static final class JitClosure extends LClosure {
protected static final class JitClosure extends LClosure {
private final JitPrototype jp;
public JitClosure(JitPrototype jitPrototype, LTable env) {
super( jitPrototype.p, env );
this.jp = jitPrototype;
}
public boolean luaStackCall(LuaState vm) {
jp.jitCall(vm,env);
jp.jitCall(vm,env,this);
return false;
}
}
public abstract void jitCall( LuaState vm, LTable env );
public abstract void jitCall( LuaState vm, LTable env, JitClosure jcl );
}

View File

@@ -19,6 +19,7 @@ import javax.tools.JavaCompiler.CompilationTask;
import org.luaj.compiler.LuaC;
import org.luaj.debug.Print;
import org.luaj.jit.JitPrototype.JitClosure;
import org.luaj.platform.J2sePlatform;
import org.luaj.vm.LClosure;
import org.luaj.vm.LPrototype;
@@ -100,24 +101,87 @@ public class LuaJit extends Lua {
}
private static String RKBC_jit(int bc) {
return LuaState.ISK(bc) ?
"k"+LuaState.INDEXK(bc):
"s"+bc;
}
private static String GETARG_RKB_jit(int i) {
return RKBC_jit(GETARG_B(i));
}
private static String GETARG_RKC_jit(int i) {
return RKBC_jit(GETARG_C(i));
}
private static void writeSource( PrintStream ps, String name, LPrototype p ) {
int i, a, b, c, o, n, cb;
LValue rkb, rkc, nvarargs, key, val;
LValue i0, step, idx, limit, init, table;
boolean back, body;
LValue i0, table;
boolean body;
String bs, cs;
int[] code = p.code;
LValue[] k = p.k;
// class header
ps.print(
"import org.luaj.vm.*;\n"+
"import org.luaj.jit.*;\n"+
"\n"+
"public class "+name+" extends JitPrototype {\n"+
" public void jitCall(LuaState vm, LTable env) {\n"+
" int base = vm.base;\n"+
"" );
"public class "+name+" extends JitPrototype {\n" );
int[] code = p.code;
LValue[] k = p.k;
// static constants
int nk = k.length;
if ( nk > 0 ) {
ps.print( "\tprivate LValue k0" ) ;
for (int ik=1; ik<nk; ik++ )
ps.print( ",k"+ik ) ;
ps.println( ";" ) ;
ps.println(" protected void setLuaPrototype(LPrototype lp) {\n" );
ps.println(" super.setLuaPrototype(lp);\n" );
ps.println( " final LValue[] k = p.k;" ) ;
for (int ik=0; ik<nk; ik++ )
ps.println( " k"+ik+" = k["+ik+"];" ) ;
ps.println(" }" ) ;
}
// jit call
ps.println( "\tpublic void jitCall(LuaState vm, LTable env, JitClosure jcl) {" );
ps.println( "\t\tint base = vm.base;" );
// parameters
int ns = p.maxstacksize;
int is = 0;
if ( ! p.is_vararg ) {
ps.println( "\t\tvm.settop("+p.numparams+");");
for (; is<p.numparams; is++ )
ps.println( "\t\tLValue s"+is+" = vm.stack[base+"+is+"];" );
}
for (; is<ns; is++ )
ps.println( "\t\tLValue s"+is+" = LNil.NIL;" );
ps.println();
// find local variables, jump points
int forlevel=0,maxforlevels=0;
int[] jumpdeltas = new int[code.length];
for ( int pc=0; pc<code.length; pc++ ) {
i = code[pc];
o = (i >> POS_OP) & MAX_OP;
switch (o) {
case OP_FORPREP:
maxforlevels = Math.max(maxforlevels, ++forlevel);
break;
case OP_FORLOOP:
forlevel--;
break;
}
}
for ( int j=0; j<maxforlevels; j++ )
ps.println("\t\tboolean back"+j+";");
// loop until a return instruction is processed,
// or the vm yields
@@ -136,16 +200,19 @@ public class LuaJit extends Lua {
a = (i >> POS_A) & MAXARG_A;
switch (o) {
default:
ps.println( "\t\tunimplemented();");
break;
case LuaState.OP_MOVE: {
b = LuaState.GETARG_B(i);
// this.stack[base + a] = this.stack[base + b];
ps.println( "\t\tvm.stack[base+"+a+"] = vm.stack[base+"+b+"];" );
ps.println( "\t\ts"+a+" = s"+b+";" );
continue;
}
case LuaState.OP_LOADK: {
b = LuaState.GETARG_Bx(i);
// this.stack[base + a] = k[b];
ps.println( "\t\tvm.stack[base+"+a+"] = p.k["+b+"];" );
ps.println( "\t\ts"+a+" = k"+b+";" );
continue;
}
/*
@@ -157,75 +224,104 @@ public class LuaJit extends Lua {
ci.pc++; // skip next instruction (if C)
continue;
}
*/
case LuaState.OP_LOADNIL: {
b = LuaState.GETARG_B(i);
do {
this.stack[base + b] = LNil.NIL;
} while ((--b) >= a);
continue;
b = LuaState.GETARG_B(i);
ps.print("\t\t");
for ( int j=a; j<=b; j++ )
ps.print("s"+j+"=");
ps.println("LNil.NIL;");
break;
}
case LuaState.OP_GETUPVAL: {
b = LuaState.GETARG_B(i);
this.stack[base + a] = cl.upVals[b].getValue();
continue;
//b = LuaState.GETARG_B(i);
//this.stack[base + a] = cl.upVals[b].getValue();
//continue;
b = LuaState.GETARG_B(i);
ps.println("\t\t\ts"+a+" = jcl.upVals["+b+"].getValue();");
break;
}
*/
case LuaState.OP_GETGLOBAL: {
b = LuaState.GETARG_Bx(i);
// b = LuaState.GETARG_Bx(i);
// key = k[b];
// table = cl.env;
// top = base + a;
// table.luaGetTable(this, table, key);
// pw.println("\t\tvm.top = base+"+a+";");
ps.println("\t\tvm.top = "+a+";");
ps.println("\t\tenv.luaGetTable(vm, env, p.k["+b+"]);");
continue;
// continue
b = LuaState.GETARG_Bx(i);
ps.println("\t\tenv.luaGetTable(vm, env, k"+b+");");
ps.println("\t\ts"+a+" = vm.stack[--vm.top];");
break;
}
/*
case LuaState.OP_GETTABLE: {
b = LuaState.GETARG_B(i);
key = GETARG_RKC(k, i);
table = this.stack[base + b];
top = base + a;
table.luaGetTable(this, table, key);
continue;
//b = GETARG_B(i);
//key = GETARG_RKC(k, i);
//table = this.stack[base + b];
//top = base + a;
//table.luaGetTable(this, table, key);
//continue;
b = GETARG_B(i);
cs = GETARG_RKC_jit(i);
ps.println("\t\ts"+b+".luaGetTable(vm, s"+b+", "+cs+");");
ps.println("\t\ts"+a+" = vm.stack[--vm.top];");
break;
}
case LuaState.OP_SETGLOBAL: {
//b = LuaState.GETARG_Bx(i);
//key = k[b];
//val = this.stack[base + a];
//table = cl.env;
//table.luaSetTable(this, table, key, val);
//continue;
b = LuaState.GETARG_Bx(i);
key = k[b];
val = this.stack[base + a];
table = cl.env;
table.luaSetTable(this, table, key, val);
continue;
ps.println("\t\tenv.luaSetTable(vm, env, k"+b+", s"+a+");");
break;
}
case LuaState.OP_SETUPVAL: {
b = LuaState.GETARG_B(i);
cl.upVals[b].setValue( this.stack[base + a] );
continue;
//b = LuaState.GETARG_B(i);
//cl.upVals[b].setValue( this.stack[base + a] );
//continue;
b = LuaState.GETARG_B(i);
ps.println("\t\t\tjcl.upVals["+b+"].setValue(s"+a+");");
break;
}
case LuaState.OP_SETTABLE: {
key = GETARG_RKB(k, i);
val = GETARG_RKC(k, i);
table = this.stack[base + a];
table.luaSetTable(this, table, key, val);
continue;
//key = GETARG_RKB(k, i);
//val = GETARG_RKC(k, i);
//table = this.stack[base + a];
//table.luaSetTable(this, table, key, val);
//continue;
bs = GETARG_RKB_jit(i);
cs = GETARG_RKC_jit(i);
ps.println("\t\ts"+a+".luaSetTable(vm, s"+a+", "+bs+", "+cs+");");
break;
}
case LuaState.OP_NEWTABLE: {
b = LuaState.GETARG_B(i);
c = LuaState.GETARG_C(i);
this.stack[base + a] = new LTable(b, c);
continue;
//b = LuaState.GETARG_B(i);
//c = LuaState.GETARG_C(i);
//this.stack[base + a] = new LTable(b, c);
//continue;
b = GETARG_B(i);
c = GETARG_C(i);
ps.println("\t\ts"+a+" = new LTable("+b+","+c+");");
break;
}
case LuaState.OP_SELF: {
rkb = GETARG_RKB(k, i);
rkc = GETARG_RKC(k, i);
top = base + a;
rkb.luaGetTable(this, rkb, rkc);
this.stack[base + a + 1] = rkb;
// StkId rb = RB(i);
// setobjs2s(L, ra+1, rb);
// Protect(luaV_gettable(L, rb, RKC(i), ra));
continue;
//rkb = GETARG_RKB(k, i);
//rkc = GETARG_RKC(k, i);
//top = base + a;
//rkb.luaGetTable(this, rkb, rkc);
//this.stack[base + a + 1] = rkb;
//// StkId rb = RB(i);
//// setobjs2s(L, ra+1, rb);
//// Protect(luaV_gettable(L, rb, RKC(i), ra));
//continue;
bs = GETARG_RKB_jit(i);
cs = GETARG_RKC_jit(i);
ps.println("\t\t"+bs+".luaGetTable(vm, "+bs+", "+cs+");");
ps.println("\t\ts"+(a+1)+" = "+bs+";");
break;
}
case LuaState.OP_ADD:
case LuaState.OP_SUB:
@@ -233,27 +329,39 @@ public class LuaJit extends Lua {
case LuaState.OP_DIV:
case LuaState.OP_MOD:
case LuaState.OP_POW: {
rkb = GETARG_RKB(k, i);
rkc = GETARG_RKC(k, i);
this.stack[base + a] = rkc.luaBinOpUnknown(o, rkb);
continue;
//rkb = GETARG_RKB(k, i);
//rkc = GETARG_RKC(k, i);
//this.stack[base + a] = rkc.luaBinOpUnknown(o, rkb);
//continue;
bs = GETARG_RKB_jit(i);
cs = GETARG_RKC_jit(i);
ps.println("\t\ts"+a+" = "+cs+".luaBinOpUnknown("+o+","+bs+");");
break;
}
case LuaState.OP_UNM: {
rkb = GETARG_RKB(k, i);
this.stack[base + a] = rkb.luaUnaryMinus();
continue;
//rkb = GETARG_RKB(k, i);
//this.stack[base + a] = rkb.luaUnaryMinus();
//continue;
bs = GETARG_RKB_jit(i);
ps.println("\t\ts"+a+" = "+bs+".luaUnaryMinus();");
}
case LuaState.OP_NOT: {
rkb = GETARG_RKB(k, i);
this.stack[base + a] = (!rkb.toJavaBoolean() ? LBoolean.TRUE
: LBoolean.FALSE);
continue;
//rkb = GETARG_RKB(k, i);
//this.stack[base + a] = (!rkb.toJavaBoolean() ? LBoolean.TRUE
// : LBoolean.FALSE);
//continue;
bs = GETARG_RKB_jit(i);
ps.println("\t\ts"+a+" = ("+bs+".toJavaBoolean()? LBoolean.TRUE: LBoolean.FALSE);");
break;
}
case LuaState.OP_LEN: {
rkb = GETARG_RKB(k, i);
this.stack[base + a] = LInteger.valueOf( rkb.luaLength() );
continue;
//rkb = GETARG_RKB(k, i);
//this.stack[base + a] = LInteger.valueOf( rkb.luaLength() );
//continue;
bs = GETARG_RKB_jit(i);
ps.println("\t\ts"+a+" = LInteger.valueOf("+bs+".luaLength());");
}
/*
case LuaState.OP_CONCAT: {
b = LuaState.GETARG_B(i);
c = LuaState.GETARG_C(i);
@@ -296,50 +404,57 @@ public class LuaJit extends Lua {
}
*/
case LuaState.OP_CALL: {
//
//// ra is base of new call frame
//this.base += a;
//
//// number of args
//b = LuaState.GETARG_B(i);
//if (b != 0) // else use previous instruction set top
// top = base + b;
//
//// number of return values we need
//c = LuaState.GETARG_C(i);
//
//// make or set up the call
//this.nresults = c - 1;
//if (this.stack[base].luaStackCall(this))
// return;
//
//// adjustTop only for case when call was completed
//// and number of args > 0. If call completed but
//// c == 0, leave top to point to end of results
//if (c > 0)
// adjustTop(base + c - 1);
//
//// restore base
//base = ci.base;
//
//continue;
// ra is base of new call frame
// this.base += a;
// ps.println("\t\tvm.base = base+"+a+";");
// copy call to vm stack
ps.println( "\t\tvm.stack[base+"+a+"] = s"+a+";" );
// number of args
b = LuaState.GETARG_B(i);
// number of args
b = LuaState.GETARG_B(i);
if (b > 0) { // else use previous instruction set top
for ( int j=1; j<b; j++ )
ps.println( "\t\tvm.stack[base+"+(a+j)+"] = s"+(a+j)+";" );
ps.println( "\t\tvm.top = base+"+(a+b)+";" );
}
// adjust top before the call
if (b != 0) { // else use previous instruction set top
// top = base + b;
ps.println("\t\tvm.top = "+(a+b)+";");
}
// number of return values we need
c = LuaState.GETARG_C(i);
// number of return values we need
c = LuaState.GETARG_C(i);
// make the call
ps.println("\t\tvm.call("+(b-1)+","+(c-1)+");");
// make the call
ps.println("\t\tvm.call("+(b-1)+","+(c-1)+");");
//
//
// // make or set up the call
// // this.nresults = c - 1;
// int nresults = c - 1;
// ps.println("\t\tvm.nresults = "+nresults+";");
// // if (this.stack[base].luaStackCall(this))
// // return;
// ps.println("\t\tif ( vm.stack[base].luaStackCall(vm) ) {");
// ps.println("\t\t\tvm.execute();");
// ps.println("\t\t}");
//
// // adjustTop only for case when call was completed
// // and number of args > 0. If call completed but
// // c == 0, leave top to point to end of results
// if (c > 0) {
// // adjustTop(base + c - 1);
// ps.println( "\t\tvm.settop("+nresults+");");
// }
//
// // restore base
// // base = ci.base;
//
// // continue;
break;
// copy results to local vars
if ( c > 0 )
for ( int j=0; j<c-1; j++ )
ps.println( "\t\ts"+(a+j)+" = vm.stack[base+"+(a+j)+"];" );
break;
}
/*
case LuaState.OP_TAILCALL: {
@@ -381,31 +496,40 @@ public class LuaJit extends Lua {
// force restore of base, etc.
return;
}
case LuaState.OP_RETURN: {
// number of return vals to return
b = LuaState.GETARG_B(i) - 1;
if (b == -1)
b = top - (base + a);
// close open upvals
closeUpVals( base );
// number to copy down
System.arraycopy(stack, base + a, stack, ci.resultbase, b);
top = ci.resultbase + b;
// adjust results to what caller expected
if (ci.nresults >= 0)
adjustTop(ci.resultbase + ci.nresults);
// pop the call stack
--cc;
// force a reload of the calling context
return;
}
*/
case LuaState.OP_RETURN: {
//// number of return vals to return
//b = LuaState.GETARG_B(i) - 1;
//if (b == -1)
// b = top - (base + a);
//
//// close open upvals
//closeUpVals( base );
//
//// number to copy down
//System.arraycopy(stack, base + a, stack, ci.resultbase, b);
//top = ci.resultbase + b;
//
//// adjust results to what caller expected
//if (ci.nresults >= 0)
// adjustTop(ci.resultbase + ci.nresults);
//
//// pop the call stack
//--cc;
//
//// force a reload of the calling context
//return;
// number of return vals to return
b = LuaState.GETARG_B(i);
if (b > 0) {
for ( int j=1; j<b; j++ )
ps.println( "\t\tvm.stack[base+"+(a+j-1)+"] = s"+(a+j-1)+";" );
ps.println( "\t\tvm.top = base+"+(a+b)+";" );
}
ps.println( "\t\treturn;" );
break;
}
case LuaState.OP_FORPREP: {
//init = this.stack[base + a];
//step = this.stack[base + a + 2];
@@ -418,15 +542,20 @@ public class LuaJit extends Lua {
b = LuaState.GETARG_sBx(i);
// set up the loop variables
ps.println( "\t\tdouble index = vm.stack[base+"+a+"].toJavaDouble();");
ps.println( "\t\tdouble limit = vm.stack[base+"+(a+1)+"].toJavaDouble();");
ps.println( "\t\tdouble step = vm.stack[base+"+(a+2)+"].toJavaDouble();");
ps.println( "\t\tboolean back = step < 0;");
ps.println( "\t\tfor ( ; back? (index>=limit): (index<=limit); index+=step ) {");
ps.println( "\t\tvm.stack[base+"+(a+3)+"] = LDouble.valueOf(index);");
String init = "s"+(a);
String limit = "s"+(a+1);
String step = "s"+(a+2);
String idx = "s"+(a+3);
String back = "back"+(forlevel++);
ps.println( "\t\t"+back+"="+step+".luaBinCmpInteger(Lua.OP_LT,0);");
ps.println( "\t\tfor ( "+idx+"="+init+";\n" +
"\t\t\t"+back+"? "+idx+".luaBinCmpUnknown(Lua.OP_LE, "+limit+"): "+limit+".luaBinCmpUnknown(Lua.OP_LE, "+idx+");\n" +
"\t\t\t"+idx+"="+idx+".luaBinOpUnknown(Lua.OP_ADD,"+step+") ) {");
forlevel++;
break;
}
case LuaState.OP_FORLOOP: {
forlevel--;
ps.println( "\t\t}");
//i0 = this.stack[base + a];
//step = this.stack[base + a + 2];
@@ -444,8 +573,8 @@ public class LuaJit extends Lua {
//continue;
break;
}
/*
case LuaState.OP_TFORLOOP: {
ps.println( "\t\t}");
//cb = base + a + 3; // call base
//base = cb;
//adjustTop( cb + 3 );
@@ -467,7 +596,6 @@ public class LuaJit extends Lua {
//}
//continue;
}
/*
case LuaState.OP_SETLIST: {
b = LuaState.GETARG_B(i);
c = LuaState.GETARG_C(i);
@@ -493,7 +621,7 @@ public class LuaJit extends Lua {
case LuaState.OP_CLOSURE: {
b = LuaState.GETARG_Bx(i);
proto = cl.p.p[b];
newClosure = new LClosure(proto, cl.env);
newClosure = proto.newClosure(cl.env);
for (int j = 0; j < newClosure.upVals.length; j++, ci.pc++) {
i = code[ci.pc];
o = LuaState.GET_OPCODE(i);

View File

@@ -0,0 +1,19 @@
package org.luaj.jit;
import java.io.IOException;
import org.luaj.vm.LPrototype;
import org.luaj.vm.LuaJTest;
import org.luaj.vm.LuaState;
/**
* Suite of standard tests, but using the LuaJit compiler
* for all loaded prototypes.
*/
public class LuaJitTest extends LuaJTest {
protected LPrototype loadScriptResource( LuaState state, String name ) throws IOException {
LPrototype p = super.loadScriptResource(state, name);
return LuaJit.jitCompile(p);
}
}

View File

@@ -111,7 +111,7 @@ public class LuaScriptEngine extends LFunction implements ScriptEngine {
LineNumberInputStream is = new LineNumberInputStream( bais );
try {
LPrototype p = LoadState.undump(luaState, is, "script");
LClosure c = new LClosure( p, luaState._G );
LClosure c = p.newClosure( luaState._G );
luaState.doCall( c, new LValue[0] );
return luaState.topointer(1);
} catch ( LuaErrorException lee ) {

View File

@@ -31,7 +31,7 @@ public class SimpleTests extends TestCase {
// try running the code!
LuaState state = Platform.newLuaState();
BaseLib.install( state._G );
LClosure c = new LClosure( state, p );
LClosure c = p.newClosure( state._G );
state.doCall( c, new LValue[0] );
} catch ( Exception e ) {
fail("i/o exception: "+e );

View File

@@ -55,7 +55,7 @@ public class DebugStackStateTest extends TestCase {
LPrototype p = LoadState.undump(state, is, script);
// create closure and execute
final LClosure c = new LClosure( state, p );
final LClosure c = p.newClosure( state._G );
// suspend the vm right away
state.suspend();

View File

@@ -194,7 +194,7 @@ public class LuaJTest extends TestCase {
BaseLib.redirectOutput( outputStream );
try {
// create closure and execute
LClosure c = new LClosure( p, state._G );
LClosure c = p.newClosure( state._G );
state.doCall(c, new LValue[0]);
final String actualOutput = new String( outputStream.toByteArray() );
@@ -207,7 +207,7 @@ public class LuaJTest extends TestCase {
}
}
private LPrototype loadScriptResource( LuaState state, String name ) throws IOException {
protected LPrototype loadScriptResource( LuaState state, String name ) throws IOException {
InputStream script = getClass().getResourceAsStream( "/"+name+".luac" );
if ( script == null ) {
script = getClass().getResourceAsStream( "/"+name+".lua" );

View File

@@ -74,7 +74,7 @@ public class StandardTest extends TestCase {
// the garbage collector. Until we implement that, remove the
// built-in collectgarbage function.
state._G.put( "collectgarbage", LNil.NIL );
LClosure c = new LClosure( code, state._G );
LClosure c = code.newClosure( state._G );
ByteArrayOutputStream output = new ByteArrayOutputStream();
BaseLib.redirectOutput( output );