/* * Copyright (C) 2011 René Jeschke <rene_jeschke@yahoo.de> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.rjeschke.weel; import java.util.ArrayList; import java.util.HashMap; import com.github.rjeschke.weel.Variable.Type; /** * Weel intermediate language code container. * * @author René Jeschke <rene_jeschke@yahoo.de> */ class WeelCode { /** The Weel. */ private final Weel weel; /** List of used locals. */ ArrayList<Boolean> locals = new ArrayList<Boolean>(); /** Closure variables indices. */ ArrayList<Integer> cvarIndex = new ArrayList<Integer>(); /** Variable index to closure index mapping. */ private HashMap<Integer, Integer> cvarMap = new HashMap<Integer, Integer>(); /** Closure variable name to index mapping. */ final HashMap<String, Integer> cvars = new HashMap<String, Integer>(); /** Instructions. */ ArrayList<Instr> instrs = new ArrayList<Instr>(); /** Enclosing WeelFunction (if not STATIC). */ WeelFunction function; /** Flag indicating that we're an anonymous function. */ boolean isAnonymousFunction; /** Flag indicating that we're an anonymous function using alternate syntax. */ boolean isAlternateSyntax; /** Flag indicating that our function has at least one return statement. */ boolean hasReturn; /** * Flag indicating that our function has at least one exit statement. * Relevant only for automatically typed anonymous functions. */ boolean hasExit; /** Maximum stack depth. */ private int maxStack; /** Label counter */ private int labels = 0; /** Label lines. */ private int[] lines; /** Starting position in source code. */ String source = null; /** * Constructor. * * @param weel * The Weel. */ public WeelCode(final Weel weel) { this.weel = weel; } /** * Adds an instruction to this code block. * * @param i * The instruction. */ void add(final Instr i) { this.instrs.add(i); } /** * Registers a local variable. * * @return The local variable index. */ int registerLocal() { for (int i = 0; i < this.locals.size(); i++) { if (!this.locals.get(i).booleanValue()) { this.locals.set(i, true); return i; } } final int ret = this.locals.size(); this.locals.add(true); return ret; } /** * Unregisters a local variable. * * @param index * The variable index. */ void unregisterLocal(final int index) { this.locals.set(index, false); } /** * Registers or gets a closure variable. * * @param var * The variable. * @return The index or <code>-1</code> */ int getCvar(final Variable var) { if (var.type == Type.NONE || var.type == Type.GLOBAL) return -1; final int vi = var.type == Type.LOCAL ? var.index : -var.index - 1; final Integer idx = this.cvarMap.get(vi); if (idx != null) return idx; final int num = this.cvarIndex.size(); this.cvarIndex.add(vi); this.cvarMap.put(vi, num); return num; } /** * Registers a label. * * @return The label index. */ int registerLabel() { return this.labels++; } /** * Closes this block. * * @param debugMode * Flag indicating that we're in debug mode. * @param dumpCode * Flag indicating that we should dump the generated code to * stdout. */ public void closeBlock(final boolean debugMode, final boolean dumpCode) { // Remove asserts if !debugMode if (!debugMode) { int i = 0; while (i < this.instrs.size()) { if (this.instrs.get(i).getType() == Op.BEGASSERT) { while (this.instrs.get(i).getType() != Op.ENDASSERT) { this.instrs.remove(i); } this.instrs.remove(i); } else { i++; } } } // Resolve labels this.resolveLabels(); // Refactor this.refactor(); // Resolve labels again this.resolveLabels(); // Calculate maximum stack depth, check for return value this.maxStack = 0; final boolean allReturn = this.recurse(0, 0) == 1; if (this.function != null && this.function.returnsValue && !allReturn) { throw new WeelException("Not all code paths of '" + this.function + "' return a value" + this.source); } this.refactorTailCalls(); // Create frame this.insertInstr(0, this.function != null ? new InstrOframe( this.function.getNumArguments(), this.locals.size() - this.function.getNumArguments()) : new InstrOframe(0, this.locals.size())); this.instrs.add(new InstrCframe(this.maxStack, this.function != null && this.function.returnsValue)); // Finally resolve labels again this.resolveLabels(); if (dumpCode) { this.dump(); } } /** * Dumps the contents of this block to stdout. */ void dump() { System.out.println(); if (this.function != null) System.out.println(this.function); else System.out.println("STATIC"); for (Instr i : this.instrs) { if (i.getType() == Op.KEY) continue; if (i.getType() != Op.LABEL) { if (i.getType() == Op.BEGASSERT || i.getType() == Op.ENDASSERT) System.out.print(" "); else System.out.print(" "); } System.out.println(i); } } /** * Refactors ALU2 instructions. */ private void refactorAlu() { final WeelRuntime rt = this.weel.getTempRuntime(); boolean redo = true; while (redo) { redo = false; for (int i = 0; i < this.instrs.size() - 1; i++) { final Instr a = this.instrs.get(i); if (a.getType() == Op.ALU2) { final InstrAlu2 alu = (InstrAlu2) a; if (i > 1) { switch (alu.type) { case strcat: { final Instr l0 = this.instrs.get(i - 2); final Instr l1 = this.instrs.get(i - 1); if (l0.getType() == Op.LOAD && l1.getType() == Op.LOAD) { rt.load(((InstrLoad) l0).value); rt.load(((InstrLoad) l1).value); rt.strcat(); this.instrs.set(i - 2, new InstrLoad(rt .popString())); this.instrs.remove(i - 1); this.instrs.remove(i - 1); i -= 2; redo = true; } break; } case mapcat: case mapcat2: break; default: { final Instr l1 = this.instrs.get(i - 1); if (l1.getType() == Op.LOAD && ((InstrLoad) l1).value.isNumber()) { final Instr l0 = this.instrs.get(i - 2); if (l0.getType() != Op.LOAD && alu.value == null && InstrAlu2.OVERLOADED .contains(alu.type)) { alu.value = ((InstrLoad) l1).value; this.instrs.remove(i - 1); i--; redo = true; } else if (l0.getType() == Op.LOAD && ((InstrLoad) l0).value.isNumber()) { rt.load(((InstrLoad) l0).value.getNumber()); rt.load(((InstrLoad) l1).value.getNumber()); switch (alu.type) { case strcat: case mapcat: case mapcat2: break; case add: rt.add(); break; case sub: rt.sub(); break; case mul: rt.mul(); break; case div: rt.mul(); break; case mod: rt.mod(); break; case pow: rt.pow(); break; case and: rt.and(); break; case or: rt.or(); break; case xor: rt.xor(); break; case shl: rt.shl(); break; case shr: rt.shr(); break; case ushr: rt.ushr(); break; case cmpEq: rt.cmpEq(); break; case cmpNe: rt.cmpNe(); break; case cmpGt: rt.cmpGt(); break; case cmpGe: rt.cmpGe(); break; case cmpLt: rt.cmpLt(); break; case cmpLe: rt.cmpLe(); break; } this.instrs.set(i - 2, new InstrLoad(rt .popNumber())); this.instrs.remove(i - 1); this.instrs.remove(i - 1); i -= 2; redo = true; } break; } } } } } } } } /** * Refactors map accesses and jumps. */ private void refactorMapsAndJumps() { boolean redo = true; while (redo) { redo = false; for (int i = 0; i < this.instrs.size() - 1; i++) { final Instr y = i > 1 ? this.instrs.get(i - 2) : null; final Instr z = i > 0 ? this.instrs.get(i - 1) : null; final Instr a = this.instrs.get(i); final Instr b = this.instrs.get(i + 1); switch (a.getType()) { case SETMAP: { final InstrSetMap sm = (InstrSetMap) a; if (sm.key == null) { int p = i; while (p >= 0 && this.instrs.get(p).getType() != Op.KEY) { p--; } if (p > 0 && this.instrs.get(p - 1).getType() == Op.LOAD) { final Value v = ((InstrLoad) this.instrs.get(p - 1)).value .clone(); if (v.type == ValueType.NUMBER || v.type == ValueType.STRING) { sm.key = v; this.instrs.remove(p - 1); i--; redo = true; } } } break; } case GETMAP: if (z != null && z.getType() == Op.KEY && y != null && y.getType() == Op.LOAD) { final Value v = ((InstrLoad) y).value.clone(); final InstrGetMap gm = (InstrGetMap) a; if (gm.key == null && (v.type == ValueType.STRING || v.type == ValueType.NUMBER)) { gm.key = v; this.instrs.remove(i - 2); i--; redo = true; } } break; case GETMAPOOP: if (z != null && z.getType() == Op.KEY && y != null && y.getType() == Op.LOAD) { final Value v = ((InstrLoad) y).value.clone(); final InstrGetMapOop gm = (InstrGetMapOop) a; if (gm.key == null && v.type == ValueType.STRING) { gm.key = v; this.instrs.remove(i - 2); i--; redo = true; } } break; case GOTO: if (b.getType() == Op.GOTO) { // Remove duplicate GOTOs this.instrs.remove(i + 1); redo = true; } else if (b.getType() == Op.LABEL) { if (((InstrGoto) a).index == ((InstrLabel) b).index) { // Remove GOTO to next line this.instrs.remove(i); i--; redo = true; } } break; case IFEQ: if (b.getType() == Op.GOTO) { // Replace IFEQ followed by GOTO with IFNE this.instrs .set(i, new InstrIfNe(((InstrGoto) b).index)); this.instrs.remove(i + 1); redo = true; } break; case IFNE: if (b.getType() == Op.GOTO) { // Replace IFNE followed by GOTO with IFEQ this.instrs .set(i, new InstrIfEq(((InstrGoto) b).index)); this.instrs.remove(i + 1); redo = true; } break; default: break; } } } } /** * Refactors CMP. */ private void refactorCmp() { boolean redo = true; while (redo) { redo = false; for (int i = 0; i < this.instrs.size() - 1; i++) { final Instr a = this.instrs.get(i); final Instr b = this.instrs.get(i + 1); switch (a.getType()) { case ALU2: { final InstrAlu2 alu = (InstrAlu2) a; switch (alu.type) { case cmpEq: case cmpGe: case cmpGt: case cmpLe: case cmpLt: case cmpNe: if (b.getType() == Op.POPBOOL) { final InstrCmpPop icp = new InstrCmpPop( alu.type); icp.value = alu.value; this.instrs.set(i, icp); this.instrs.remove(i + 1); redo = true; } break; default: break; } break; } default: break; } } } } /** * Inserts an instruction. * * @param index The index. * @param ins The instruction. */ private void insertInstr(final int index, final Instr ins) { if(index == this.instrs.size()) { this.instrs.add(ins); } else { this.instrs.add(null); for(int i = this.instrs.size() - 2; i >= index; i--) { this.instrs.set(i + 1, this.instrs.get(i)); } this.instrs.set(index, ins); } } /** * Refactors tail calls. */ private void refactorTailCalls() { if(this.instrs.size() < 1 || this.function == null) { return; } final int fidx = this.function.index; boolean changed = false; final int l = this.registerLabel(); if(this.instrs.get(this.instrs.size() - 1).getType() == Op.LABEL) { int n = this.instrs.size() - 1; final ArrayList<Integer> labels = new ArrayList<Integer>(); while(n > 0 && this.instrs.get(n).getType() == Op.LABEL) { labels.add(((InstrLabel)this.instrs.get(n--)).index); } n++; for(int i = 1; i < this.instrs.size() - 1; i++) { final Instr a = this.instrs.get(i); final Instr b = this.instrs.get(i + 1); if(a.getType() == Op.CALL && (b.getType() == Op.GOTO || (i + 1) == n)) { final InstrCall call = (InstrCall)a; if((call.func.index != fidx) || (b.getType() == Op.GOTO && !labels.contains(((InstrGoto)b).index))) { continue; } // Remove return/exit GOTO if(b.getType() == Op.GOTO) { this.instrs.remove(i + 1); } // Place new GOTO this.instrs.set(i, new InstrGoto(l)); for(int p = 0; p < this.function.arguments; p++) { this.insertInstr(i, new InstrVarStore(VarInstrType.LOCAL, p)); } i += this.function.arguments; changed = true; } } } else if(this.instrs.get(this.instrs.size() - 1).getType() == Op.CALL) { final InstrCall call = (InstrCall)this.instrs.get(this.instrs.size() - 1); if(call.func.index == fidx) { final int i = this.instrs.size() - 1; this.instrs.set(i, new InstrGoto(l)); for(int p = 0; p < this.function.arguments; p++) { this.insertInstr(i, new InstrVarStore(VarInstrType.LOCAL, p)); } changed = true; } } if(changed) { this.insertInstr(0, new InstrLabel(l)); } } /** * Refactors this block's code, by replacing/reordering/removing common * compilation 'artifacts'. */ // TODO check if my redesign works ;) private void refactor() { this.refactorAlu(); // I think I could join these two this.refactorMapsAndJumps(); this.refactorCmp(); } /** * Scans all code paths in this block, checking for return values and * maximum stack depth. * * @param line * Line number to start. * @param stack * Stack value to start with. * @return true/false for return value, the stack depth */ private int recurse(final int line, final int stack) { int cur = stack; boolean returns = false; for (int i = line; i < this.instrs.size(); i++) { final Instr in = this.instrs.get(i); switch (in.getType()) { case ALU2: if (((InstrAlu2) in).value == null) cur--; break; case POP: cur -= ((InstrPop) in).pops; break; case STACKCALL: { final InstrStackCall call = (InstrStackCall) in; cur -= call.paramc + 1; if (call.needsReturn) cur++; break; } case SPECIALCALL: { final InstrSpecialCall call = (InstrSpecialCall) in; cur -= call.paramc + 1; if (call.needsReturn) cur++; break; } case CALL: { final InstrCall call = (InstrCall) in; cur -= call.func.arguments; if (call.func.returnsValue) cur++; break; } case IFEQ: { int to = this.lines[((InstrIfEq) in).index]; if (to > i) { if (i > 0 && (this.instrs.get(i - 1).getType() == Op.DOFOREACH)) returns |= this.recurse(to, cur - 2) == 0; else returns |= this.recurse(to, cur) == 0; } break; } case IFNE: { int to = this.lines[((InstrIfNe) in).index]; if (to > i) { returns |= this.recurse(to, cur) == 0; if (i > 0 && (this.instrs.get(i - 1).getType() == Op.TESTPOPF || this.instrs .get(i - 1).getType() == Op.TESTPOPT)) cur--; } break; } case GOTO: { int to = this.lines[((InstrGoto) in).index]; if (to > i) { i = to; } break; } case GETMAP: if (((InstrGetMap) in).key == null) { cur--; } break; case SETMAP: if (((InstrSetMap) in).key == null) { cur -= 3; } else { cur -= 2; } break; case GETMAPOOP: if (((InstrGetMapOop) in).key != null) { cur++; } break; case CMPPOP: cur -= ((InstrCmpPop) in).value == null ? 2 : 1; break; default: cur += in.getType().getDelta(); break; } this.maxStack = Math.max(cur, this.maxStack); } if (line == 0 && stack == 0) { if (cur < 0 || cur > 1) { throw new WeelException("What a terrible failure: recurse reached " + cur + ", contact the author."); } returns |= cur == 0; return returns ? 0 : 1; } if (cur < 0 || cur > 1) { throw new WeelException("What a terrible failure: recurse reached " + cur + ", contact the author."); } return cur; } /** * Maps label indices to line numbers. */ private void resolveLabels() { this.lines = new int[this.labels]; for (int i = 0; i < this.instrs.size(); i++) { final Instr in = this.instrs.get(i); if (in.getType() == Op.LABEL) { this.lines[((InstrLabel) in).index] = i; ((InstrLabel) in).line = i; } } } }