Package wyil.io

Source Code of wyil.io.WyilFileWriter

// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//    * 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.
//    * Neither the name of the <organization> nor the
//      names of its contributors may be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 DAVID J. PEARCE 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 wyil.io;

import java.io.*;
import java.math.BigInteger;
import java.util.*;

import wycc.lang.NameID;
import wycc.util.Pair;
import wyfs.io.BinaryOutputStream;
import wyfs.lang.Path;
import wyil.lang.*;
import wyautl.util.BigRational;

/**
* <p>
* Responsible for writing a WyilFile to an output stream in binary form. The
* binary format is structured to given maximum flexibility and to avoid
* built-in limitations in terms of e.g. maximum sizes, etc.
* </p>
*
* <p>
* The primitive component of a WyilFile is a <i>block</i>. Each block has a
* kind, a given and then the payload. Blocks which are not recognised can be
* ignored by skipping over their payload. Blocks are always byte aligned, but
* their contents may not be.
* </p>
*
* <p>
* A binary Wyil file begins with a header and a resource pool stratified into
* sections containing the string constants, general constants, types and more.
* Following the header are zero or more module blocks. Additional top-level
* block kinds (e.g. for licenses) may be specified in the future. Each module
* block consists of zero or more declaration blocks, which include method and
* function declarations, type declarations and constant declarations.
* </p>
*
* @author David J. Pearce
*
*/
public final class WyilFileWriter {
  private static final int MAJOR_VERSION = 0;
  private static final int MINOR_VERSION = 1;

  private final BinaryOutputStream out;

  private final ArrayList<String> stringPool  = new ArrayList<String>();
  private final HashMap<String,Integer> stringCache = new HashMap<String,Integer>();
  private final ArrayList<PATH_Item> pathPool = new ArrayList<PATH_Item>();
  private final HashMap<Path.ID,Integer> pathCache = new HashMap<Path.ID,Integer>();
  private final ArrayList<NAME_Item> namePool = new ArrayList<NAME_Item>();
  private final HashMap<NameID,Integer> nameCache = new HashMap<NameID,Integer>();
  private final ArrayList<Constant> constantPool = new ArrayList<Constant>();
  private final HashMap<Constant,Integer> constantCache = new HashMap<Constant,Integer>();
  private final ArrayList<Type> typePool = new ArrayList<Type>();
  private final HashMap<Type,Integer> typeCache = new HashMap<Type,Integer>();

  public WyilFileWriter(OutputStream output) {
    this.out = new BinaryOutputStream(output);
  }

  public void close() throws IOException {
    out.close();
  }

  public void write(WyilFile module) throws IOException {
    // first, write magic number
    out.write_u8(0x57); // W
    out.write_u8(0x59); // Y
    out.write_u8(0x49); // I
    out.write_u8(0x4C); // L
    out.write_u8(0x46); // F
    out.write_u8(0x49); // I
    out.write_u8(0x4C); // L
    out.write_u8(0x45); // E

    // second, build pools
    buildPools(module);

    // third, write head block
    writeBlock(BLOCK_Header,module,out);

    // fourth, write module block(s)
    writeBlock(BLOCK_Module,module,out);

    out.flush();
  }

  private void writeBlock(int kind, Object data, BinaryOutputStream output)
      throws IOException {

    output.pad_u8(); // pad out to next byte boundary

    // determine bytes for block
    byte[] bytes = null;
    switch(kind) {
      case BLOCK_Header:
        bytes = generateHeaderBlock((WyilFile) data);
        break;
      case BLOCK_Module:
        bytes = generateModuleBlock((WyilFile) data);
        break;
      case BLOCK_Type:
        bytes = generateTypeBlock((WyilFile.TypeDeclaration) data);
        break;
      case BLOCK_Constant:
        bytes = generateConstantBlock((WyilFile.ConstantDeclaration) data);
        break;
      case BLOCK_Function:
        bytes = generateFunctionOrMethodBlock((WyilFile.FunctionOrMethodDeclaration) data);
        break;
      case BLOCK_Method:
        bytes = generateFunctionOrMethodBlock((WyilFile.FunctionOrMethodDeclaration) data);
        break;
      case BLOCK_Case:
        bytes = generateFunctionOrMethodCaseBlock((WyilFile.Case) data);
        break;
      case BLOCK_Body:
      case BLOCK_Precondition:
      case BLOCK_Postcondition:
      case BLOCK_Constraint:
        bytes = generateCodeBlock((Code.Block) data);
        break;
    }

    output.write_uv(kind);
    output.write_uv(bytes.length);
    output.pad_u8(); // pad out to next byte boundary
    output.write(bytes);
  }

  /**
   * Write the header information for this WYIL file, including the stratified
   * resource pool.
   *
   * @param module
   *
   * @throws IOException
   */
  private byte[] generateHeaderBlock(WyilFile module)
      throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    // second, write the file version number
    output.write_uv(MAJOR_VERSION);
    output.write_uv(MINOR_VERSION);

    // third, write the various pool sizes
    output.write_uv(stringPool.size());
    output.write_uv(pathPool.size());
    output.write_uv(namePool.size());
    output.write_uv(typePool.size());
    output.write_uv(constantPool.size());

    // finally, write the number of remaining blocks
    output.write_uv(module.blocks().size());

    writeStringPool(output);
    writePathPool(output);
    writeNamePool(output);
    writeTypePool(output);
    writeConstantPool(output);

    output.close();

    return bytes.toByteArray();
  }

  /**
   * Write the list of strings making up the string pool in UTF8.
   *
   * @throws IOException
   */
  private void writeStringPool(BinaryOutputStream output) throws IOException {
    //System.out.println("Writing " + stringPool.size() + " string item(s).");
    for (String s : stringPool) {
      try {
        byte[] bytes = s.getBytes("UTF-8");
        output.write_uv(bytes.length);
        output.write(bytes, 0, bytes.length);
      } catch (UnsupportedEncodingException e) {
        // hmmm, this aint pretty ;)
      }
    }
  }

  private void writePathPool(BinaryOutputStream output) throws IOException {
    for(int i=1;i<pathPool.size();++i) {
      PATH_Item p = pathPool.get(i);
      output.write_uv(p.parentIndex);
      output.write_uv(p.stringIndex);
    }
  }

  private void writeNamePool(BinaryOutputStream output) throws IOException {
    //System.out.println("Writing " + stringPool.size() + " name item(s).");
    for (NAME_Item p : namePool) {
      //output.write_uv(p.kind.kind());
      output.write_uv(p.pathIndex);
      output.write_uv(p.nameIndex);
    }
  }

  private void writeConstantPool(BinaryOutputStream output) throws IOException {
    //System.out.println("Writing " + stringPool.size() + " constant item(s).");

    for (Constant val : constantPool) {
      if(val instanceof Constant.Null) {
        output.write_uv(CONSTANT_Null);

      } else if(val instanceof Constant.Bool) {
        Constant.Bool b = (Constant.Bool) val;
        output.write_uv(b.value ? CONSTANT_True : CONSTANT_False);

      } else if(val instanceof Constant.Byte) {
        Constant.Byte b = (Constant.Byte) val;
        output.write_uv(CONSTANT_Byte);
        output.write_u8(b.value);

      } else if(val instanceof Constant.Char) {
        Constant.Char c = (Constant.Char) val;
        output.write_uv(CONSTANT_Char);
        output.write_uv(c.value);

      } else if(val instanceof Constant.Integer) {
        Constant.Integer i = (Constant.Integer) val;
        BigInteger num = i.value;
        byte[] numbytes = num.toByteArray();
        output.write_uv(CONSTANT_Int);
        output.write_uv(numbytes.length);
        output.write(numbytes);

      } else if(val instanceof Constant.Strung) {
        Constant.Strung s = (Constant.Strung) val;
        output.write_uv(CONSTANT_String);
        String value = s.value;
        output.write_uv(stringCache.get(value));

      } else if(val instanceof Constant.Decimal) {
        Constant.Decimal r = (Constant.Decimal) val;
        output.write_uv(CONSTANT_Real);
        BigInteger mantissa = r.value.unscaledValue();
        int exponent = r.value.scale();
        byte[] bytes = mantissa.toByteArray();
        output.write_uv(bytes.length);
        output.write(bytes);
        output.write_uv(exponent);
      } else if(val instanceof Constant.Set) {
        Constant.Set s = (Constant.Set) val;
        output.write_uv(CONSTANT_Set);
        output.write_uv(s.values.size());
        for(Constant v : s.values) {
          int index = constantCache.get(v);
          output.write_uv(index);
        }

      } else if(val instanceof Constant.List) {
        Constant.List s = (Constant.List) val;
        output.write_uv(CONSTANT_List);
        output.write_uv(s.values.size());
        for(Constant v : s.values) {
          int index = constantCache.get(v);
          output.write_uv(index);
        }

      } else if(val instanceof Constant.Map) {
        Constant.Map m = (Constant.Map) val;
        output.write_uv(CONSTANT_Map);
        output.write_uv(m.values.size());
        for(java.util.Map.Entry<Constant,Constant> e : m.values.entrySet()) {
          int keyIndex = constantCache.get(e.getKey());
          output.write_uv(keyIndex);
          int valIndex = constantCache.get(e.getValue());
          output.write_uv(valIndex);
        }

      } else if(val instanceof Constant.Record) {
        Constant.Record r = (Constant.Record) val;
        output.write_uv(CONSTANT_Record);
        output.write_uv(r.values.size());
        for(java.util.Map.Entry<String,Constant> v : r.values.entrySet()) {
          output.write_uv(stringCache.get(v.getKey()));
          int index = constantCache.get(v.getValue());
          output.write_uv(index);
        }

      } else if(val instanceof Constant.Tuple) {
        Constant.Tuple t = (Constant.Tuple) val;
        output.write_uv(CONSTANT_Tuple);
        output.write_uv(t.values.size());
        for(Constant v : t.values) {
          int index = constantCache.get(v);
          output.write_uv(index);
        }
      } else if(val instanceof Constant.Lambda) {
        Constant.Lambda fm = (Constant.Lambda) val;
        Type.FunctionOrMethod t = fm.type();
        output.write_uv(t instanceof Type.Function ? CONSTANT_Function
            : CONSTANT_Method);
        output.write_uv(typeCache.get(t));
        output.write_uv(nameCache.get(fm.name));
      } else {
        throw new RuntimeException("Unknown value encountered - " + val);
      }
    }
  }

  private void writeTypePool(BinaryOutputStream output) throws IOException {
    //System.out.println("Writing " + stringPool.size() + " type item(s).");
    Type.BinaryWriter bout = new Type.BinaryWriter(output);
    for (Type t : typePool) {
      bout.write(t);
    }
  }

  private byte[] generateModuleBlock(WyilFile module) throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    output.write_uv(pathCache.get(module.id())); // FIXME: BROKEN!
    output.write_uv(MODIFIER_Public); // for now
    output.write_uv(module.blocks().size());

    for(WyilFile.Block d : module.blocks()) {
      writeModuleBlock(d,output);
    }

        output.close();

    return bytes.toByteArray();
  }

  private void writeModuleBlock(WyilFile.Block d,
      BinaryOutputStream output) throws IOException {
    if(d instanceof WyilFile.ConstantDeclaration) {
      writeBlock(BLOCK_Constant, d ,output);
    } else if(d instanceof WyilFile.TypeDeclaration) {
      writeBlock(BLOCK_Type, d, output);
    } else if(d instanceof WyilFile.FunctionOrMethodDeclaration) {
      WyilFile.FunctionOrMethodDeclaration md = (WyilFile.FunctionOrMethodDeclaration) d;
      if(md.type() instanceof Type.Function) {
        writeBlock(BLOCK_Function, d, output);
      } else {
        writeBlock(BLOCK_Method, d, output);
      }
    }
  }

  private byte[] generateConstantBlock(WyilFile.ConstantDeclaration cd) throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    output.write_uv(stringCache.get(cd.name()));
    output.write_uv(generateModifiers(cd.modifiers()));
    output.write_uv(constantCache.get(cd.constant()));
    output.write_uv(0); // no sub-blocks
    // TODO: write annotations

    output.close();
    return bytes.toByteArray();
  }

  private byte[] generateTypeBlock(WyilFile.TypeDeclaration td) throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    output.write_uv(stringCache.get(td.name()));
    output.write_uv(generateModifiers(td.modifiers()));
    output.write_uv(typeCache.get(td.type()));
    Code.Block invariant = td.invariant();

    if(invariant != null) {
      output.write_uv(1);
      writeBlock(BLOCK_Constraint,td.invariant(),output);
    } else {
      output.write_uv(0);
    }

    output.close();
    return bytes.toByteArray();
  }

  private byte[] generateFunctionOrMethodBlock(WyilFile.FunctionOrMethodDeclaration md) throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    output.write_uv(stringCache.get(md.name()));
    output.write_uv(generateModifiers(md.modifiers()));
    output.write_uv(typeCache.get(md.type()));
    output.write_uv(md.cases().size());

    for(WyilFile.Case c : md.cases()) {
      writeBlock(BLOCK_Case,c,output);
    }

    // TODO: write annotations
    output.close();
    return bytes.toByteArray();
  }

  private byte[] generateFunctionOrMethodCaseBlock(WyilFile.Case c) throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    int bodyCount = c.body() == null ? 0 : 1;

    output.write_uv(c.precondition().size() + c.postcondition().size() + bodyCount);
    for(Code.Block requires : c.precondition()) {
      writeBlock(BLOCK_Precondition,requires,output);
    }
    for(Code.Block ensures : c.postcondition()) {
      writeBlock(BLOCK_Postcondition,ensures,output);
    }
    if(c.body() != null) {
      writeBlock(BLOCK_Body,c.body(),output);
    }
    // TODO: write annotations

    output.close();
    return bytes.toByteArray();
  }

  private byte[] generateCodeBlock(Code.Block block) throws IOException {
    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    BinaryOutputStream output = new BinaryOutputStream(bytes);

    HashMap<String,Integer> labels = new HashMap<String,Integer>();

    int nlabels = 0;
    int offset = 0;
    for(Code.Block.Entry e : block) {
      Code code = e.code;
      if(code instanceof Codes.Label) {
        Codes.Label l = (Codes.Label) code;
        labels.put(l.label, offset);
        nlabels++;
      } else {
        offset++;
      }
    }

    output.write_uv(block.size()-nlabels); // instruction count (not same as block size!)
    offset = 0;
    for(Code.Block.Entry e : block) {
      if(e.code instanceof Codes.Label) {

      } else {
        writeCode(e.code, offset++, labels, output);
      }
    }

    output.close();
    return bytes.toByteArray();
  }

  private void writeCode(Code code, int offset,
      HashMap<String, Integer> labels, BinaryOutputStream output)
      throws IOException {

    // first determine whether we need some kind of wide instruction.
    int width = calculateWidth(code, offset, labels);

    switch (width) {
      case Code.OPCODE_wide :
        output.write_u8(width);
        writeBase(true, code, output);
        writeRest(false, code, offset, labels, output);
        break;
      case Code.OPCODE_widerest :
        output.write_u8(width);
        writeBase(false, code, output);
        writeRest(true, code, offset, labels, output);
        break;
      case Code.OPCODE_widewide :
        output.write_u8(width);
        writeBase(true, code, output);
        writeRest(true, code, offset, labels, output);
        break;
      default :
        writeBase(false, code, output);
        writeRest(false, code, offset, labels, output);
    }
  }

  /**
   * Write the "base" part of a bytecode instruction. This includes only the
   * opcode itself and the operands (if any).
   *
   * @param wide
   *            --- indicates whether we should be writing the base in "wide"
   *            format. That is, using the unlimited representation of
   *            integers. In the alternative "short" representation, all
   *            operands are written using exactly 4 unsigned bits. This means
   *            we can encode registers 0-15, which covers the majority of
   *            cases. The wide format is then used for cases when we have to
   *            write a register operand whose index is > 15.
   * @param code
   *            --- The bytecode to be written.
   * @param offset
   *            --- The current offset of this bytecode in the bytecode array
   *            being generated. This offset is measured in complete
   *            bytecodes, not in e.g. bytes. Therefore, the first bytecode
   *            has offset zero, the second bytecode has offset 1, etc. The
   *            offset is required
   * @param output
   *            --- The binary stream to write this bytecode to.
   * @throws IOException
   */
  private void writeBase(boolean wide, Code code,
      BinaryOutputStream output) throws IOException {

    // second, deal with standard instruction formats
    output.write_u8(code.opcode());

    if(code instanceof Code.AbstractUnaryOp) {
      Code.AbstractUnaryOp<Type> a = (Code.AbstractUnaryOp) code;
      if(a.operand != Codes.NULL_REG) {
        writeBase(wide,a.operand,output);
      } else {
        // possible only for empty return
      }
    } else if(code instanceof Code.AbstractBinaryOp) {
      Code.AbstractBinaryOp<Type> a = (Code.AbstractBinaryOp) code;
      writeBase(wide,a.leftOperand,output);
      writeBase(wide,a.rightOperand,output);
    } else if(code instanceof Code.AbstractUnaryAssignable) {
      Code.AbstractUnaryAssignable<Type> a = (Code.AbstractUnaryAssignable) code;
      writeBase(wide,a.target(),output);
      writeBase(wide,a.operand(0),output);
    } else if(code instanceof Code.AbstractBinaryAssignable) {
      Code.AbstractBinaryAssignable<Type> a = (Code.AbstractBinaryAssignable) code;
      writeBase(wide, a.target(),output);
      writeBase(wide, a.operand(0),output);
      writeBase(wide, a.operand(1),output);
    } else if(code instanceof Codes.Lambda) {
      // Special case for lambda since their operands maybe NULL_REG.
      Code.AbstractNaryAssignable<Type> a = (Code.AbstractNaryAssignable) code;
      if(a.target() != Codes.NULL_REG) {
        writeBase(wide,a.target(),output);
      }
      int[] operands = a.operands();
      writeBase(wide,operands.length,output);
      for(int i=0;i!=operands.length;++i) {
        writeBase(wide,operands[i]+1,output);
      }
    } else if(code instanceof Code.AbstractNaryAssignable) {
      Code.AbstractNaryAssignable<Type> a = (Code.AbstractNaryAssignable) code;
      if(a.target() != Codes.NULL_REG) {
        writeBase(wide,a.target(),output);
      }
      int[] operands = a.operands();
      writeBase(wide,operands.length,output);
      for(int i=0;i!=operands.length;++i) {
        writeBase(wide,operands[i],output);
      }
    } else if(code instanceof Code.AbstractAssignable) {
      Code.AbstractAssignable c = (Code.AbstractAssignable) code;
      writeBase(wide,c.target(),output);
    } else if(code instanceof Codes.ForAll) {
      Codes.ForAll l = (Codes.ForAll) code;
      int[] operands = l.modifiedOperands;
      writeBase(wide,operands.length + 2,output);
      writeBase(wide,l.indexOperand,output);
      writeBase(wide,l.sourceOperand,output);
      for(int i=0;i!=operands.length;++i) {
        writeBase(wide,operands[i],output);
      }
    } else if(code instanceof Codes.Loop) {
      Codes.Loop l = (Codes.Loop) code;
      int[] operands = l.modifiedOperands;
      writeBase(wide,operands.length,output);
      for(int i=0;i!=operands.length;++i) {
        writeBase(wide,operands[i],output);
      }
    } else if(code instanceof Codes.TryCatch) {
      Codes.TryCatch tc = (Codes.TryCatch) code;
      writeBase(wide,tc.operand,output);
    }
  }

  /**
   * Write the "rest" of a bytecode instruction. This includes any additional
   * information, such as the type and/or other pool items required for the
   * bytecode.
   *
   * @param wide
   *            --- indicates whether we should be writing the rest in "wide"
   *            format. That is, using the unlimited representation of
   *            integers. In the alternative "short" representation, all pool
   *            indices are written using exactly 8 unsigned bits. This means
   *            we can encode pool indices 0-255, which covers a large number
   *            of cases. The wide format is then used for cases when we have
   *            to a pool index > 255.
   * @param code
   *            --- The bytecode to be written.
   * @param offset
   *            --- The current offset of this bytecode in the bytecode array
   *            being generated. This offset is measured in complete
   *            bytecodes, not in e.g. bytes. Therefore, the first bytecode
   *            has offset zero, the second bytecode has offset 1, etc. The
   *            offset is required for calculating jump targets for branching
   *            instructions (e.g. goto). Since jump targets (in short form)
   *            are encoded as a relative offset, we need to know our current
   *            offset to compute the relative target.
   * @param labels
   *            --- A map from label to offset. This is required to determine
   *            the (relative) jump offset for a branching instruction.
   * @param output
   *            --- The binary output stream to which this bytecode is being
   *            written.
   * @throws IOException
   */
  private void writeRest(boolean wide, Code code, int offset,
      HashMap<String, Integer> labels, BinaryOutputStream output) throws IOException {

    if(code instanceof Code.AbstractUnaryOp) {
      Code.AbstractUnaryOp<Type> a = (Code.AbstractUnaryOp) code;
      if(a.operand != Codes.NULL_REG) {
        writeRest(wide,typeCache.get(a.type),output);
      } else {
        // possible only for empty return
      }
    } else if(code instanceof Code.AbstractBinaryOp) {
      Code.AbstractBinaryOp<Type> a = (Code.AbstractBinaryOp) code;
      writeRest(wide,typeCache.get(a.type),output);
    } else if(code instanceof Code.AbstractUnaryAssignable) {
      Code.AbstractUnaryAssignable<Type> a = (Code.AbstractUnaryAssignable) code;
      writeRest(wide,typeCache.get(a.type()),output);
    } else if(code instanceof Code.AbstractBinaryAssignable) {
      Code.AbstractBinaryAssignable<Type> a = (Code.AbstractBinaryAssignable) code;
      writeRest(wide,typeCache.get(a.type()),output);
    } else if(code instanceof Code.AbstractNaryAssignable) {
      Code.AbstractNaryAssignable<Type> a = (Code.AbstractNaryAssignable) code;
      writeRest(wide,typeCache.get(a.type()),output);
    }
    // now deal with non-uniform instructions
    // First, deal with special cases
    if(code instanceof Codes.AssertOrAssume) {
      Codes.AssertOrAssume l = (Codes.AssertOrAssume) code;
      int target = labels.get(l.target);
      writeTarget(wide,offset,target,output);
    } else if(code instanceof Codes.Const) {
      Codes.Const c = (Codes.Const) code;
      writeRest(wide,constantCache.get(c.constant),output);
    } else if(code instanceof Codes.Convert) {
      Codes.Convert c = (Codes.Convert) code;
      writeRest(wide,typeCache.get(c.result),output);
    } else if(code instanceof Codes.Fail) {
      Codes.Fail c = (Codes.Fail) code;
      int idx = 0;
      if(c.message != null) {
        idx = constantCache.get(c.message) + 1;
      }
      writeRest(wide,idx,output);
    } else if(code instanceof Codes.FieldLoad) {
      Codes.FieldLoad c = (Codes.FieldLoad) code;
      writeRest(wide,stringCache.get(c.field),output);
    } else if(code instanceof Codes.ForAll) {
      Codes.ForAll f = (Codes.ForAll) code;
      writeRest(wide,typeCache.get(f.type),output);
      int target = labels.get(f.target);
      writeTarget(wide,offset,target,output);
    } else if(code instanceof Codes.IfIs) {
      Codes.IfIs c = (Codes.IfIs) code;
      int target = labels.get(c.target) - offset;
      writeRest(wide,typeCache.get(c.rightOperand),output);
      writeTarget(wide,offset,target,output);
    } else if(code instanceof Codes.If) {
      Codes.If c = (Codes.If) code;
      int target = labels.get(c.target);
      writeTarget(wide,offset,target,output);
    } else if(code instanceof Codes.Goto) {
      Codes.Goto c = (Codes.Goto) code;
      int target = labels.get(c.target);
      writeTarget(wide,offset,target,output);
    } else if(code instanceof Codes.Invoke) {
      Codes.Invoke c = (Codes.Invoke) code;
      writeRest(wide,nameCache.get(c.name),output);
    } else if(code instanceof Codes.Lambda) {
      Codes.Lambda c = (Codes.Lambda) code;
      writeRest(wide,nameCache.get(c.name),output);
    } else if(code instanceof Codes.Loop) {
      Codes.Loop l = (Codes.Loop) code;
      int target = labels.get(l.target);
      writeTarget(wide,offset,target,output);
    } else if(code instanceof Codes.Update) {
      Codes.Update c = (Codes.Update) code;
      List<String> fields = c.fields;
      writeRest(wide,typeCache.get(c.afterType),output);
      writeRest(wide,fields.size(),output);
      for (int i = 0; i != fields.size(); ++i) {
        writeRest(wide, stringCache.get(fields.get(i)), output);
      }
    } else if(code instanceof Codes.Switch) {
      Codes.Switch c = (Codes.Switch) code;
      List<Pair<Constant,String>> branches = c.branches;
      int target = labels.get(c.defaultTarget) - offset;
      writeTarget(wide,offset,target,output);
      writeRest(wide,branches.size(),output);
      for(Pair<Constant,String> b : branches) {
        writeRest(wide,constantCache.get(b.first()),output);
        target = labels.get(b.second());
        writeTarget(wide,offset,target,output);
      }
    } else if(code instanceof Codes.TryCatch) {
      Codes.TryCatch tc = (Codes.TryCatch) code;
      int target = labels.get(tc.target);
      ArrayList<Pair<Type,String>> catches = tc.catches;
      writeTarget(wide,offset,target,output);
      writeRest(wide,catches.size(),output);
      for (int i = 0; i != catches.size(); ++i) {
        Pair<Type, String> handler = catches.get(i);
        writeRest(wide, typeCache.get(handler.first()), output);
        writeTarget(wide, offset, labels.get(handler.second()), output);
      }
    } else if(code instanceof Codes.TupleLoad) {
      Codes.TupleLoad c = (Codes.TupleLoad) code;
      writeRest(wide,c.index,output);
    }
  }

  private void writeBase(boolean wide, int value, BinaryOutputStream output) throws IOException {
    if(wide) {
      output.write_uv(value);
    } else {
      if(value >= 16) {
        throw new IllegalArgumentException("invalid base value");
      }
      output.write_un(value,4);
    }
  }

  private void writeRest(boolean wide, int value, BinaryOutputStream output) throws IOException {
    if(wide) {
      output.write_uv(value);
    } else {
      if(value >= 256) {
        throw new IllegalArgumentException("invalid base value");
      }
      output.write_u8(value);
    }
  }

  private void writeTarget(boolean wide, int offset, int target,
      BinaryOutputStream output) throws IOException {
    if (wide) {
      output.write_uv(target);
    } else {
      target = (target - offset) + 128;
      output.write_u8(target);
    }
  }

  /**
   * Calculate the "width" of a given bytecode. That is, whether or not either
   * of the base or remainder components need to be encoded using the "wide"
   * format. The wide format allows for unlimited precision, but occupies more
   * space. The alternative "short" format uses fixed precision, but cannot
   * encode all possible register operands and/or pool indices.
   *
   * @param code
   *            --- The bytecode whose width is to be determined.
   * @param offset
   *            --- The current offset of this bytecode in the bytecode array
   *            being generated. This offset is measured in complete
   *            bytecodes, not in e.g. bytes. Therefore, the first bytecode
   *            has offset zero, the second bytecode has offset 1, etc. The
   *            offset is required for calculating jump targets for branching
   *            instructions (e.g. goto). Since jump targets (in short form)
   *            are encoded as a relative offset, we need to know our current
   *            offset to compute the relative target.
   * @param labels
   *            --- A map from label to offset. This is required to determine
   *            the (relative) jump offset for a branching instruction.
   * @return
   */
  private int calculateWidth(Code code, int offset, HashMap<String,Integer> labels) {
    int maxBase = 0;
    int maxRest = 0;

    if(code instanceof Code.AbstractUnaryOp) {
      Code.AbstractUnaryOp<Type> a = (Code.AbstractUnaryOp) code;
      maxBase = a.operand;
      maxRest = typeCache.get(a.type);
    } else if(code instanceof Code.AbstractBinaryOp) {
      Code.AbstractBinaryOp<Type> a = (Code.AbstractBinaryOp) code;
      maxBase = Math.max(a.leftOperand,a.rightOperand);
      maxRest = typeCache.get(a.type);
    } else if(code instanceof Code.AbstractUnaryAssignable) {
      Code.AbstractUnaryAssignable<Type> a = (Code.AbstractUnaryAssignable) code;
      maxBase = Math.max(a.target(),a.operand(0));
      maxRest = typeCache.get(a.type());
    } else if(code instanceof Code.AbstractBinaryAssignable) {
      Code.AbstractBinaryAssignable<Type> a = (Code.AbstractBinaryAssignable) code;
      maxBase = Math.max(a.operand(0),a.operand(1));
      maxBase = Math.max(a.target(),maxBase);
      maxRest = typeCache.get(a.type());
    } else if(code instanceof Code.AbstractNaryAssignable) {
      Code.AbstractNaryAssignable<Type> a = (Code.AbstractNaryAssignable) code;
      int[] operands = a.operands();
      maxBase = Math.max(a.target(),operands.length);
      for(int i=0;i!=operands.length;++i) {
        maxBase = Math.max(maxBase,operands[i]);
      }
      maxRest = typeCache.get(a.type());
    } else if(code instanceof Code.AbstractAssignable) {
      Code.AbstractAssignable a = (Code.AbstractAssignable) code;
      maxBase = a.target();
    }

    // now, deal with non-uniform opcodes
    if(code instanceof Codes.AssertOrAssume) {
      Codes.AssertOrAssume aoa = (Codes.AssertOrAssume) code;
      maxRest = targetWidth(aoa.target, offset, labels);
    } else if(code instanceof Codes.Const) {
      Codes.Const c = (Codes.Const) code;
      maxRest = Math.max(maxRest,constantCache.get(c.constant));
    } else if(code instanceof Codes.Convert) {
      Codes.Convert c = (Codes.Convert) code;
      maxRest = Math.max(maxRest,typeCache.get(c.result));
    } else if(code instanceof Codes.FieldLoad) {
      Codes.FieldLoad c = (Codes.FieldLoad) code;
      maxRest = Math.max(maxRest,stringCache.get(c.field));
    } else if(code instanceof Codes.ForAll) {
      Codes.ForAll f = (Codes.ForAll) code;
      int[] operands = f.modifiedOperands;
      maxBase = Math.max(f.sourceOperand, f.indexOperand);
      for(int i=0;i!=operands.length;++i) {
        maxBase = Math.max(maxBase, operands[i]);
      }
      maxRest = Math.max(maxRest,typeCache.get(f.type));
      maxRest = Math.max(maxRest,targetWidth(f.target, offset, labels));
    } else if(code instanceof Codes.IfIs) {
      Codes.IfIs c = (Codes.IfIs) code;
      maxRest = Math.max(maxRest,typeCache.get(c.rightOperand));
      maxRest = Math.max(maxRest,targetWidth(c.target, offset, labels));
    } else if(code instanceof Codes.If) {
      Codes.If c = (Codes.If) code;
      maxRest = Math.max(maxRest,targetWidth(c.target, offset, labels));
    } else if(code instanceof Codes.Goto) {
      Codes.Goto c = (Codes.Goto) code;
      maxRest = Math.max(maxRest,targetWidth(c.target, offset, labels));
    } else if(code instanceof Codes.Invoke) {
      Codes.Invoke c = (Codes.Invoke) code;
      maxRest = Math.max(maxRest,nameCache.get(c.name));
    } else if(code instanceof Codes.Lambda) {
      Codes.Lambda c = (Codes.Lambda) code;
      maxRest = Math.max(maxRest,nameCache.get(c.name));
    } else if(code instanceof Codes.Loop) {
      Codes.Loop l = (Codes.Loop) code;
      int[] operands = l.modifiedOperands;
      maxBase = 0;
      for(int i=0;i!=operands.length;++i) {
        maxBase = Math.max(maxBase, operands[i]);
      }
      maxRest = Math.max(maxRest,targetWidth(l.target, offset, labels));
    } else if(code instanceof Codes.Update) {
      Codes.Update c = (Codes.Update) code;
      maxRest = Math.max(maxRest,typeCache.get(c.afterType));
      ArrayList<String> fields = c.fields;
      for(int i=0;i!=fields.size();++i) {
        String field = fields.get(i);
        maxRest = Math.max(maxRest,stringCache.get(field));
      }
    } else if(code instanceof Codes.Switch) {
      Codes.Switch c = (Codes.Switch) code;
      List<Pair<Constant,String>> branches = c.branches;
      maxRest = Math.max(maxRest,targetWidth(c.defaultTarget, offset, labels));
      maxRest = Math.max(maxRest,branches.size());
      for(Pair<Constant,String> b : branches) {
        maxRest = Math.max(maxRest,constantCache.get(b.first()));
        maxRest = Math.max(maxRest,targetWidth(b.second(), offset, labels));
      }
    } else if(code instanceof Codes.TryCatch) {
      Codes.TryCatch tc = (Codes.TryCatch) code;
      maxBase = tc.operand;
      maxRest = Math.max(maxRest,
          targetWidth(tc.target, offset, labels));
      ArrayList<Pair<Type,String>> catches = tc.catches;
      maxRest = Math.max(maxRest,catches.size());
      for(int i=0;i!=catches.size();++i) {
        Pair<Type,String> handler = catches.get(i);
        maxRest = Math.max(maxRest,typeCache.get(handler.first()));
        maxRest = Math.max(maxRest,
            targetWidth(handler.second(), offset, labels));
      }
    } else if(code instanceof Codes.TupleLoad) {
      Codes.TupleLoad c = (Codes.TupleLoad) code;
      maxRest = Math.max(maxRest,c.index);
    }

    if(maxBase < 16) {
      if(maxRest < 256) {
        return 0; // standard
      } else {
        return Code.OPCODE_widerest;
      }
    } else {
      if(maxRest < 256) {
        return Code.OPCODE_wide;
      } else {
        return Code.OPCODE_widewide;
      }
    }
  }

  private int targetWidth(String label, int offset,
      HashMap<String, Integer> labels) {
    int target = labels.get(label) - offset;
    target = target + 128;
    if(target < 0) {
      // won't fit in a single byte
      return 256;
    } else {
      return target;
    }
  }

  private int generateModifiers(Collection<Modifier> modifiers) {
    int mods = 0;
    for(Modifier m : modifiers) {
      if(m == Modifier.PUBLIC) {
        mods |= MODIFIER_Public;
      } else if(m == Modifier.PROTECTED) {
        mods |= MODIFIER_Protected;
      } else if(m == Modifier.PRIVATE) {
        mods |= MODIFIER_Private;
      } else if(m == Modifier.NATIVE) {
        mods |= MODIFIER_Native;
      } else if(m == Modifier.EXPORT) {
        mods |= MODIFIER_Export;
      }
    }
    return mods;
  }

  private void buildPools(WyilFile module) {
    stringPool.clear();
    stringCache.clear();

    pathPool.clear();
    pathCache.clear();
    // preload the path root
    pathPool.add(null);
    pathCache.put(wyfs.util.Trie.ROOT,0);

    namePool.clear();
    nameCache.clear();

    constantPool.clear();
    constantCache.clear();

    typePool.clear();
    typeCache.clear();

    addPathItem(module.id());
    for(WyilFile.Block d : module.blocks()) {
      buildPools(d);
    }
  }

  private void buildPools(WyilFile.Block declaration) {
    if(declaration instanceof WyilFile.TypeDeclaration) {
      buildPools((WyilFile.TypeDeclaration)declaration);
    } else if(declaration instanceof WyilFile.ConstantDeclaration) {
      buildPools((WyilFile.ConstantDeclaration)declaration);
    } else if(declaration instanceof WyilFile.FunctionOrMethodDeclaration) {
      buildPools((WyilFile.FunctionOrMethodDeclaration)declaration);
    }
  }

  private void buildPools(WyilFile.TypeDeclaration declaration) {
    addStringItem(declaration.name());
    addTypeItem(declaration.type());
    buildPools(declaration.invariant());
  }

  private void buildPools(WyilFile.ConstantDeclaration declaration) {
    addStringItem(declaration.name());
    addConstantItem(declaration.constant());
  }

  private void buildPools(WyilFile.FunctionOrMethodDeclaration declaration) {
    addStringItem(declaration.name());
    addTypeItem(declaration.type());
    for(WyilFile.Case c : declaration.cases()) {
      buildPools(c.precondition());
      buildPools(c.body());
      buildPools(c.postcondition());
    }
  }

  private void buildPools(List<Code.Block> blocks) {
    for(Code.Block block : blocks) {
      buildPools(block);
    }
  }

  private void buildPools(Code.Block block) {
    if(block == null) { return; }
    for(Code.Block.Entry e : block) {
      buildPools(e.code);
    }
  }

  private void buildPools(Code code) {

    // First, deal with special cases
    if(code instanceof Codes.Const) {
      Codes.Const c = (Codes.Const) code;
      addConstantItem(c.constant);
    } else if(code instanceof Codes.Convert) {
      Codes.Convert c = (Codes.Convert) code;
      addTypeItem(c.result);
    } else if(code instanceof Codes.Fail) {
      Codes.Fail c = (Codes.Fail) code;
      addConstantItem(c.message);
    } else if(code instanceof Codes.FieldLoad) {
      Codes.FieldLoad c = (Codes.FieldLoad) code;
      addStringItem(c.field);
    } else if(code instanceof Codes.ForAll) {
      Codes.ForAll s = (Codes.ForAll) code;
      addTypeItem((Type)s.type);
    }else if(code instanceof Codes.IfIs) {
      Codes.IfIs c = (Codes.IfIs) code;
      addTypeItem(c.type);
      addTypeItem(c.rightOperand);
    } else if(code instanceof Codes.Invoke) {
      Codes.Invoke c = (Codes.Invoke) code;
      addNameItem(c.name);
    } else if(code instanceof Codes.Lambda) {
      Codes.Lambda c = (Codes.Lambda) code;
      addNameItem(c.name);
    } else if(code instanceof Codes.Update) {
      Codes.Update c = (Codes.Update) code;
      addTypeItem(c.type());
      addTypeItem(c.afterType);
      for(Codes.LVal l : c) {
        if(l instanceof Codes.RecordLVal) {
          Codes.RecordLVal lv = (Codes.RecordLVal) l;
          addStringItem(lv.field);
        }
      }
    } else if(code instanceof Codes.Switch) {
      Codes.Switch s = (Codes.Switch) code;
      addTypeItem(s.type);
      for(Pair<Constant,String> b : s.branches) {
        addConstantItem(b.first());
      }
    } else if(code instanceof Codes.TryCatch) {
      Codes.TryCatch tc = (Codes.TryCatch) code;
      ArrayList<Pair<Type, String>> catches = tc.catches;
      for (int i = 0; i != catches.size(); ++i) {
        Pair<Type, String> handler = catches.get(i);
        addTypeItem(handler.first());
      }
    }

    // Second, deal with standard cases
    if(code instanceof Code.AbstractUnaryOp) {
      Code.AbstractUnaryOp<Type> a = (Code.AbstractUnaryOp) code;
      addTypeItem(a.type);
    } else if(code instanceof Code.AbstractBinaryOp) {
      Code.AbstractBinaryOp<Type> a = (Code.AbstractBinaryOp) code;
      addTypeItem(a.type);
    } else if(code instanceof Code.AbstractUnaryAssignable) {
      Code.AbstractUnaryAssignable<Type> a = (Code.AbstractUnaryAssignable) code;
      addTypeItem(a.type());
    } else if(code instanceof Code.AbstractBinaryAssignable) {
      Code.AbstractBinaryAssignable<Type> a = (Code.AbstractBinaryAssignable) code;
      addTypeItem(a.type());
    } else if(code instanceof Code.AbstractNaryAssignable) {
      Code.AbstractNaryAssignable<Type> a = (Code.AbstractNaryAssignable) code;
      addTypeItem(a.type());
    }
  }

  private int addNameItem(NameID name) {
    Integer index = nameCache.get(name);
    if(index == null) {
      int i = namePool.size();
      nameCache.put(name, i);
      namePool.add(new NAME_Item(addPathItem(name.module()),
          addStringItem(name.name())));
      return i;
    } else {
      return index;
    }
  }

  private int addStringItem(String string) {
    Integer index = stringCache.get(string);
    if(index == null) {
      int i = stringPool.size();
      stringCache.put(string, i);
      stringPool.add(string);
      return i;
    } else {
      return index;
    }
  }

  private int addPathItem(Path.ID pid) {
    Integer index = pathCache.get(pid);
    if(index == null) {
      int parent = addPathItem(pid.parent());
      int i = pathPool.size();
      pathPool.add(new PATH_Item(parent,addStringItem(pid.last())));
      pathCache.put(pid, i);
      return i;
    } else {
      return index;
    }
  }

  private int addTypeItem(Type t) {

    // TODO: this could be made way more efficient. In particular, we should
    // combine resources into a proper aliased pool rather than write out
    // types individually ... because that's sooooo inefficient!

    Integer index = typeCache.get(t);
    if(index == null) {
      int i = typePool.size();
      typeCache.put(t, i);
      typePool.add(t);
      return i;
    } else {
      return index;
    }
  }

  private int addConstantItem(Constant v) {

    Integer index = constantCache.get(v);
    if(index == null) {
      // All subitems must have lower indices than the containing item.
      // So, we must add subitems first before attempting to allocate a
      // place for this value.
      addConstantSubitems(v);

      // finally allocate space for this constant.
      int i = constantPool.size();
      constantCache.put(v, i);
      constantPool.add(v);
      return i;
    }
    return index;
  }

  private void addConstantSubitems(Constant v) {
    if(v instanceof Constant.Strung) {
      Constant.Strung s = (Constant.Strung) v;
      addStringItem(s.value);
    } else if(v instanceof Constant.List) {
      Constant.List l = (Constant.List) v;
      for (Constant e : l.values) {
        addConstantItem(e);
      }
    } else if(v instanceof Constant.Set) {
      Constant.Set s = (Constant.Set) v;
      for (Constant e : s.values) {
        addConstantItem(e);
      }
    } else if(v instanceof Constant.Map) {
      Constant.Map m = (Constant.Map) v;
      for (Map.Entry<Constant,Constant> e : m.values.entrySet()) {
        addConstantItem(e.getKey());
        addConstantItem(e.getValue());
      }
    } else if(v instanceof Constant.Tuple) {
      Constant.Tuple t = (Constant.Tuple) v;
      for (Constant e : t.values) {
        addConstantItem(e);
      }
    } else if(v instanceof Constant.Record) {
      Constant.Record r = (Constant.Record) v;
      for (Map.Entry<String,Constant> e : r.values.entrySet()) {
        addStringItem(e.getKey());
        addConstantItem(e.getValue());
      }
    } else if(v instanceof Constant.Lambda){
      Constant.Lambda fm = (Constant.Lambda) v;
      addTypeItem(fm.type());
      addNameItem(fm.name);
    }
  }

  /**
   * An PATH_Item represents a path item.
   *
   * @author David J. Pearce
   *
   */
  private class PATH_Item {
    /**
     * The index in the path pool of the parent for this item, or -1 if it
     * has not parent.
     */
    public final int parentIndex;

    /**
     * The index of this path component in the string pool
     */
    public final int stringIndex;

    public PATH_Item(int parentIndex, int stringIndex) {
      this.parentIndex = parentIndex;
      this.stringIndex = stringIndex;
    }
  }

  private enum NAME_Kind {
    PACKAGE(0),
    MODULE(1),
    CONSTANT(2),
    TYPE(3),
    FUNCTION(4),
    METHOD(5);

    private final int kind;

    private NAME_Kind(int kind) {
      this.kind = kind;
    }

    public int kind() {
      return kind;
    }
  }

  /**
   * A NAME_Item represents a named path item, such as a package, module or
   * something within a module (e.g. a function or method declaration).
   *
   * @author David J. Pearce
   *
   */
  private class NAME_Item {
    /**
     * The kind of name item this represents.
     */
    // public final NAME_Kind kind;

    /**
     * Index of path for this item in path pool
     */
    public final int pathIndex;

    /**
     * Index of string for this named item
     */
    public final int nameIndex;

    public NAME_Item(/*NAME_Kind kind, */int pathIndex, int nameIndex) {
      //this.kind = kind;
      this.pathIndex = pathIndex;
      this.nameIndex = nameIndex;
    }
  }

  // =========================================================================
  // BLOCK identifiers
  // =========================================================================

  public final static int BLOCK_Header = 0;
  public final static int BLOCK_Module = 1;
  public final static int BLOCK_Documentation = 2;
  public final static int BLOCK_License = 3;
  // ... (anticipating some others here)
  public final static int BLOCK_Type = 10;
  public final static int BLOCK_Constant = 11;
  public final static int BLOCK_Function = 12;
  public final static int BLOCK_Method = 13;
  public final static int BLOCK_Case = 14;
  // ... (anticipating some others here)
  public final static int BLOCK_Body = 20;
  public final static int BLOCK_Precondition = 21;
  public final static int BLOCK_Postcondition = 22;
  public final static int BLOCK_Constraint = 23;
  // ... (anticipating some others here)

  // =========================================================================
  // CONSTANT identifiers
  // =========================================================================

  public final static int CONSTANT_Null = 0;
  public final static int CONSTANT_True = 1;
  public final static int CONSTANT_False = 2;
  public final static int CONSTANT_Byte = 3;
  public final static int CONSTANT_Char = 4;
  public final static int CONSTANT_Int = 5;
  public final static int CONSTANT_Real = 6;
  public final static int CONSTANT_Set = 7;
  public final static int CONSTANT_String = 8;
  public final static int CONSTANT_List = 9;
  public final static int CONSTANT_Record = 10;
  public final static int CONSTANT_Tuple = 11;
  public final static int CONSTANT_Map = 12;
  public final static int CONSTANT_Function = 13;
  public final static int CONSTANT_Method = 14;

  // =========================================================================
  // MODIFIER identifiers
  // =========================================================================

  public final static int MODIFIER_PROTECTION_MASK = 3;
  public final static int MODIFIER_Private = 0;
  public final static int MODIFIER_Protected = 1;
  public final static int MODIFIER_Public = 2;

  // public final static int MODIFIER_Package = 3;
  // public final static int MODIFIER_Module = 4;

  public final static int MODIFIER_MANGLE_MASK = 3 << 4;
  public final static int MODIFIER_Native = 1 << 4;
  public final static int MODIFIER_Export = 2 << 4;
  //public final static int MODIFIER_Total = 3 << 4;
}
TOP

Related Classes of wyil.io.WyilFileWriter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.