// Panopticon - A libre program analysis library for machine code
// Copyright (C) 2014-2018  The Panopticon Developers
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

use std::io::{Write,Cursor,Read};
use std::ops::{Range};
use leb128;
use uuid::Uuid;
use {
    Statement,
    Endianess,
    Operation,
    MemoryOperation,
    FlowOperation,
    Constant,
    Variable,
    Value,
    StrRef,
    Result,
    RewriteControl,
    NameRef,
    Names,
    Strings,
    Segment,
    SegmentRef,
    Segments,
    Constraint
};

/// Memory conserving representation of IL code.
#[derive(Clone,Debug)]
pub struct Bitcode {
    data: Vec<u8>,
}
// const: <len, pow2><leb128 value>
// var: <name, leb128 str idx>, <subscript, leb128 + 1>, <len, value>

//  1\2  c   v   u
//   c|000 001 010
//   v|011 100 101
//   u|110 111 xxx
//
// add  00000--- <a> <b> <res>
// sub  00001--- <a> <b> <res>
// mul  00010--- <a> <b> <res>
// divu 00011--- <a> <b> <res>
// divs 00100--- <a> <b> <res>
// shl  00101--- <a> <b> <res>
// shru 00110--- <a> <b> <res>
// shrs 00111--- <a> <b> <res>
// mod  01000--- <a> <b> <res>
// and  01001--- <a> <b> <res>
// or   01010--- <a> <b> <res>
// xor  01011--- <a> <b> <res>
// eq   01100--- <a> <b> <res>
// leu  01101--- <a> <b> <res>
// les  01110--- <a> <b> <res>
// ltu  01111--- <a> <b> <res>
// lts  10000--- <a> <b> <res>
//
//  \  e   u   s   f
// v | 000 001 010 011
// u | 100 101 110 111
//
// c: 0, v: 1
// little: 0, big: 1
// zext   1000100- <size, leb128> <a>
// sext   1000101- <size, leb128> <a>
// mov    1000110- <a>
// movu   10001110
// init   10001111 <name, leb128> <size, leb128>
// sel    100100-- <offset, leb128> <sz, leb128> <a>
// load   100101e- <region, leb128> <size, leb128> <a>
// phi2   10011000 <a, var> <b, var> 3 0x0
// phi3   10011001 <a, var> <b, var> <c, var>
// call   10011010 <stub, leb128>
// call   10011011 <uuid, leb128>
// icall  1001110- <a>
// ucall  10011110
// phi0   10011111 3*3 0x0
// store  1010e--- <region, leb128> <size, leb128> <addr> <val>
// ret    10110000
//        10110001
// loadu  1011001e <region, leb128> <size, leb128>
// phi1   10110100 <a> 2*3 0x0
// mphi0  10110101 3*3 0x0
// mphi1  10110110 <a, seg> 2*3 0x0
// mphi2  10110111 <a, seg> <b, seg> 3 0x0
// mphi3  10111000 <a, seg> <b, seg> <c, seg>
// alloc  10111001 <name, leb128>
// assume 11000--- <constr> <a>

macro_rules! encoding_rule {
    ( $val:tt [ c , c ] => $a:expr, $b:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let a = $a;
        let b = $b;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_constant(a,data)?;
        Self::encode_constant(b,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ c , v ] => $a:expr, $b:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let a = $a;
        let b = $b;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_constant(a,data)?;
        Self::encode_variable(b,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ c , u ] => $a:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let a = $a;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_constant(a,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ v , c ] => $a:expr, $b:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let a = $a;
        let b = $b;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_variable(a,data)?;
        Self::encode_constant(b,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ v , v ] => $a:expr, $b:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let a = $a;
        let b = $b;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_variable(a,data)?;
        Self::encode_variable(b,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ v , u ] => $a:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let a = $a;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_variable(a,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ u , c ] => $b:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let b = $b;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_constant(b,data)?;
        Self::encode_variable(res,data)?;
    }};
    ( $val:tt [ u , v ] => $b:expr, $res:expr, $data:expr ) => {{
        let val = $val;
        let b = $b;
        let res = $res;
        let data = $data;

        data.write(&[val])?;
        Self::encode_variable(b,data)?;
        Self::encode_variable(res,data)?;
    }};
}

impl Default for Bitcode {
    fn default() -> Bitcode {
        Bitcode{
            data: Vec::new(),
        }
    }
}

impl Bitcode {
    /// Appends statements  in `i` to the bitcode vector, returing the new byte range.
    pub fn append<I: IntoIterator<Item=Statement> + Sized>(&mut self, i: I) -> Result<Range<usize>> {
        let mut buf = Cursor::new(Vec::new());
        let start = self.data.len();

        for stmt in i {
            Self::encode_statement(stmt,&mut buf)?;
        }

        self.data.extend(buf.into_inner().into_iter());
        Ok(start..self.data.len())
    }

    /// Creates a new Bitcode instance from `v`.
    pub fn new(v: Vec<Statement>) -> Result<Bitcode> {
        let mut buf = Cursor::new(Vec::new());

        for stmt in v {
            Self::encode_statement(stmt,&mut buf)?;
        }

        Ok(Bitcode{ data: buf.into_inner() })
    }

    /// Creates an empty Bitcode vector with initial capacity `cap`.
    pub fn with_capacity(cap: usize) -> Bitcode {
        Bitcode{
            data: Vec::with_capacity(cap),
        }
    }

    /// Maps function `func` over all statements in `range`, and writing them back into the vector.
    pub fn rewrite<F: FnMut(&mut Statement,&mut Names,&mut Strings,&mut Segments) -> Result<RewriteControl> + Sized>(&mut self, range: Range<usize>, names: &mut Names, strings: &mut Strings, segments: &mut Segments, mut func: F) -> Result<Range<usize>> {
        if range.start == range.end { return Ok(range); }

        debug!("rewrite bitcode in {:?}",range);
        let mut read_pos = range.start;
        let mut bytes_left = range.end - range.start;
        let mut write_pos = range.start;
        let mut tmp = Vec::with_capacity(10);

        while bytes_left > 0 {
            let mut stmt = {
                debug!("read from {:?}",read_pos);
                let mut read = Cursor::new(&self.data[read_pos..]);
                let stmt = self.decode_statement(&mut read)?;

                read_pos += read.position() as usize;
                debug!("left: {:?}",bytes_left);
                debug!("read {:?}",read.position());
                debug!("read to {:?}",read_pos);
                bytes_left -= read.position() as usize;
                stmt
            };

            debug!("map {:?}",stmt);
            match func(&mut stmt,names,strings,segments)? {
                RewriteControl::Continue => { /* fall-thru */ }
                RewriteControl::Break => { return Ok(range.start..(read_pos + bytes_left)); }
            }
            debug!("  to {:?}",stmt);

            {
                let mut write = Cursor::new(tmp);

                Self::encode_statement(stmt, &mut write)?;
                tmp = write.into_inner();

                if read_pos - write_pos < tmp.len() {
                    let diff = tmp.len() - (read_pos - write_pos);
                    debug!("make space for {} more bytes",diff);

                    self.data.reserve(diff);
                    // XXX
                    for _ in 0..diff { self.data.insert(write_pos,42); }
                    read_pos += diff;
                }
            }

            //debug!("data: {:?}",self.data);
            debug!("write {:?} at {}",tmp,write_pos);

            {
                let mut write = Cursor::new(&mut self.data[write_pos..]);
                write_pos += write.write(&tmp)?;
                tmp.clear();
            }

            if read_pos > write_pos {
                debug!("remove {:?}",write_pos..read_pos);
                self.data.drain(write_pos..read_pos);
                read_pos = write_pos;
            }

            assert_eq!(read_pos, write_pos);
            //debug!("data: {:?}",self.data);
        }

        Ok(range.start..write_pos)
    }

    /// Removes the serialized statements inside `range`.
    pub fn remove(&mut self, range: Range<usize>) {
        self.data.drain(range);
    }

    /// Serializes the statements in `stmts` and writes the bytes into position `pos`. Returns the
    /// inserted byte range.
    pub fn insert(&mut self, pos: usize, stmts: Vec<Statement>) -> Result<Range<usize>> {
        let data = Vec::with_capacity(stmts.len() * 10);
        let mut cur = Cursor::new(data);

        for stmt in stmts {
            Self::encode_statement(stmt, &mut cur)?;
        }

        let buf = cur.into_inner();
        let len = buf.len();

        for b in buf.into_iter().rev() {
            self.data.insert(pos,b);
        }
            //        self.data.splice(pos..pos,buf.into_iter());
        Ok(pos..(pos + len))
    }

    fn encode_statement<W: Write>(stmt: Statement, data: &mut W) -> Result<()> {
        use il::Operation::*;
        use value::Value::*;

        match stmt {
            // Add: 0b00000---
            Statement::Expression{ op: Add(Constant(a),Constant(b)), result } => encoding_rule!( 0b00000_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: Add(Constant(a),Variable(b)), result } => encoding_rule!( 0b00000_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: Add(Constant(a),Undefined), result } => encoding_rule!( 0b00000_010 [c,u] => a, result, data ),
            Statement::Expression{ op: Add(Variable(a),Constant(b)), result } => encoding_rule!( 0b00000_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: Add(Variable(a),Variable(b)), result } => encoding_rule!( 0b00000_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: Add(Variable(a),Undefined), result } => encoding_rule!( 0b00000_101 [v,u] => a, result, data ),
            Statement::Expression{ op: Add(Undefined,Constant(b)), result } => encoding_rule!( 0b00000_110 [u,c] => b, result, data ),
            Statement::Expression{ op: Add(Undefined,Variable(b)), result } => encoding_rule!( 0b00000_111 [u,v] => b, result, data ),
            Statement::Expression{ op: Add(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // Subtract: 0b00001---
            Statement::Expression{ op: Subtract(Constant(a),Constant(b)), result } => encoding_rule!( 0b00001_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: Subtract(Constant(a),Variable(b)), result } => encoding_rule!( 0b00001_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: Subtract(Constant(a),Undefined), result } => encoding_rule!( 0b00001_010 [c,u] => a, result, data ),
            Statement::Expression{ op: Subtract(Variable(a),Constant(b)), result } => encoding_rule!( 0b00001_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: Subtract(Variable(a),Variable(b)), result } => encoding_rule!( 0b00001_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: Subtract(Variable(a),Undefined), result } => encoding_rule!( 0b00001_101 [v,u] => a, result, data ),
            Statement::Expression{ op: Subtract(Undefined,Constant(b)), result } => encoding_rule!( 0b00001_110 [u,c] => b, result, data ),
            Statement::Expression{ op: Subtract(Undefined,Variable(b)), result } => encoding_rule!( 0b00001_111 [u,v] => b, result, data ),
            Statement::Expression{ op: Subtract(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // Multiply: 0b00010---
            Statement::Expression{ op: Multiply(Constant(a),Constant(b)), result } => encoding_rule!( 0b00010_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: Multiply(Constant(a),Variable(b)), result } => encoding_rule!( 0b00010_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: Multiply(Constant(a),Undefined), result } => encoding_rule!( 0b00010_010 [c,u] => a, result, data ),
            Statement::Expression{ op: Multiply(Variable(a),Constant(b)), result } => encoding_rule!( 0b00010_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: Multiply(Variable(a),Variable(b)), result } => encoding_rule!( 0b00010_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: Multiply(Variable(a),Undefined), result } => encoding_rule!( 0b00010_101 [v,u] => a, result, data ),
            Statement::Expression{ op: Multiply(Undefined,Constant(b)), result } => encoding_rule!( 0b00010_110 [u,c] => b, result, data ),
            Statement::Expression{ op: Multiply(Undefined,Variable(b)), result } => encoding_rule!( 0b00010_111 [u,v] => b, result, data ),
            Statement::Expression{ op: Multiply(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // DivideUnsigned: 0b00011---
            Statement::Expression{ op: DivideUnsigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b00011_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: DivideUnsigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b00011_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: DivideUnsigned(Constant(a),Undefined), result } => encoding_rule!( 0b00011_010 [c,u] => a, result, data ),
            Statement::Expression{ op: DivideUnsigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b00011_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: DivideUnsigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b00011_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: DivideUnsigned(Variable(a),Undefined), result } => encoding_rule!( 0b00011_101 [v,u] => a, result, data ),
            Statement::Expression{ op: DivideUnsigned(Undefined,Constant(b)), result } => encoding_rule!( 0b00011_110 [u,c] => b, result, data ),
            Statement::Expression{ op: DivideUnsigned(Undefined,Variable(b)), result } => encoding_rule!( 0b00011_111 [u,v] => b, result, data ),
            Statement::Expression{ op: DivideUnsigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // DivideSigned: 0b00100---
            Statement::Expression{ op: DivideSigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b00100_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: DivideSigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b00100_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: DivideSigned(Constant(a),Undefined), result } => encoding_rule!( 0b00100_010 [c,u] => a, result, data ),
            Statement::Expression{ op: DivideSigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b00100_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: DivideSigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b00100_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: DivideSigned(Variable(a),Undefined), result } => encoding_rule!( 0b00100_101 [v,u] => a, result, data ),
            Statement::Expression{ op: DivideSigned(Undefined,Constant(b)), result } => encoding_rule!( 0b00100_110 [u,c] => b, result, data ),
            Statement::Expression{ op: DivideSigned(Undefined,Variable(b)), result } => encoding_rule!( 0b00100_111 [u,v] => b, result, data ),
            Statement::Expression{ op: DivideSigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // ShiftLeft: 0b00101---
            Statement::Expression{ op: ShiftLeft(Constant(a),Constant(b)), result } => encoding_rule!( 0b00101_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: ShiftLeft(Constant(a),Variable(b)), result } => encoding_rule!( 0b00101_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: ShiftLeft(Constant(a),Undefined), result } => encoding_rule!( 0b00101_010 [c,u] => a, result, data ),
            Statement::Expression{ op: ShiftLeft(Variable(a),Constant(b)), result } => encoding_rule!( 0b00101_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: ShiftLeft(Variable(a),Variable(b)), result } => encoding_rule!( 0b00101_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: ShiftLeft(Variable(a),Undefined), result } => encoding_rule!( 0b00101_101 [v,u] => a, result, data ),
            Statement::Expression{ op: ShiftLeft(Undefined,Constant(b)), result } => encoding_rule!( 0b00101_110 [u,c] => b, result, data ),
            Statement::Expression{ op: ShiftLeft(Undefined,Variable(b)), result } => encoding_rule!( 0b00101_111 [u,v] => b, result, data ),
            Statement::Expression{ op: ShiftLeft(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // ShiftRightUnsigned: 0b00110---
            Statement::Expression{ op: ShiftRightUnsigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b00110_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b00110_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Constant(a),Undefined), result } => encoding_rule!( 0b00110_010 [c,u] => a, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b00110_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b00110_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Variable(a),Undefined), result } => encoding_rule!( 0b00110_101 [v,u] => a, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Undefined,Constant(b)), result } => encoding_rule!( 0b00110_110 [u,c] => b, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Undefined,Variable(b)), result } => encoding_rule!( 0b00110_111 [u,v] => b, result, data ),
            Statement::Expression{ op: ShiftRightUnsigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // ShiftRightSigned: 0b00111---
            Statement::Expression{ op: ShiftRightSigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b00111_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b00111_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Constant(a),Undefined), result } => encoding_rule!( 0b00111_010 [c,u] => a, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b00111_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b00111_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Variable(a),Undefined), result } => encoding_rule!( 0b00111_101 [v,u] => a, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Undefined,Constant(b)), result } => encoding_rule!( 0b00111_110 [u,c] => b, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Undefined,Variable(b)), result } => encoding_rule!( 0b00111_111 [u,v] => b, result, data ),
            Statement::Expression{ op: ShiftRightSigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // Modulo: 0b01000---
            Statement::Expression{ op: Modulo(Constant(a),Constant(b)), result } => encoding_rule!( 0b01000_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: Modulo(Constant(a),Variable(b)), result } => encoding_rule!( 0b01000_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: Modulo(Constant(a),Undefined), result } => encoding_rule!( 0b01000_010 [c,u] => a, result, data ),
            Statement::Expression{ op: Modulo(Variable(a),Constant(b)), result } => encoding_rule!( 0b01000_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: Modulo(Variable(a),Variable(b)), result } => encoding_rule!( 0b01000_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: Modulo(Variable(a),Undefined), result } => encoding_rule!( 0b01000_101 [v,u] => a, result, data ),
            Statement::Expression{ op: Modulo(Undefined,Constant(b)), result } => encoding_rule!( 0b01000_110 [u,c] => b, result, data ),
            Statement::Expression{ op: Modulo(Undefined,Variable(b)), result } => encoding_rule!( 0b01000_111 [u,v] => b, result, data ),
            Statement::Expression{ op: Modulo(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // And: 0b01001---
            Statement::Expression{ op: And(Constant(a),Constant(b)), result } => encoding_rule!( 0b01001_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: And(Constant(a),Variable(b)), result } => encoding_rule!( 0b01001_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: And(Constant(a),Undefined), result } => encoding_rule!( 0b01001_010 [c,u] => a, result, data ),
            Statement::Expression{ op: And(Variable(a),Constant(b)), result } => encoding_rule!( 0b01001_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: And(Variable(a),Variable(b)), result } => encoding_rule!( 0b01001_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: And(Variable(a),Undefined), result } => encoding_rule!( 0b01001_101 [v,u] => a, result, data ),
            Statement::Expression{ op: And(Undefined,Constant(b)), result } => encoding_rule!( 0b01001_110 [u,c] => b, result, data ),
            Statement::Expression{ op: And(Undefined,Variable(b)), result } => encoding_rule!( 0b01001_111 [u,v] => b, result, data ),
            Statement::Expression{ op: And(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // InclusiveOr: 0b01010---
            Statement::Expression{ op: InclusiveOr(Constant(a),Constant(b)), result } => encoding_rule!( 0b01010_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: InclusiveOr(Constant(a),Variable(b)), result } => encoding_rule!( 0b01010_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: InclusiveOr(Constant(a),Undefined), result } => encoding_rule!( 0b01010_010 [c,u] => a, result, data ),
            Statement::Expression{ op: InclusiveOr(Variable(a),Constant(b)), result } => encoding_rule!( 0b01010_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: InclusiveOr(Variable(a),Variable(b)), result } => encoding_rule!( 0b01010_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: InclusiveOr(Variable(a),Undefined), result } => encoding_rule!( 0b01010_101 [v,u] => a, result, data ),
            Statement::Expression{ op: InclusiveOr(Undefined,Constant(b)), result } => encoding_rule!( 0b01010_110 [u,c] => b, result, data ),
            Statement::Expression{ op: InclusiveOr(Undefined,Variable(b)), result } => encoding_rule!( 0b01010_111 [u,v] => b, result, data ),
            Statement::Expression{ op: InclusiveOr(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // ExclusiveOr: 0b01011---
            Statement::Expression{ op: ExclusiveOr(Constant(a),Constant(b)), result } => encoding_rule!( 0b01011_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: ExclusiveOr(Constant(a),Variable(b)), result } => encoding_rule!( 0b01011_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: ExclusiveOr(Constant(a),Undefined), result } => encoding_rule!( 0b01011_010 [c,u] => a, result, data ),
            Statement::Expression{ op: ExclusiveOr(Variable(a),Constant(b)), result } => encoding_rule!( 0b01011_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: ExclusiveOr(Variable(a),Variable(b)), result } => encoding_rule!( 0b01011_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: ExclusiveOr(Variable(a),Undefined), result } => encoding_rule!( 0b01011_101 [v,u] => a, result, data ),
            Statement::Expression{ op: ExclusiveOr(Undefined,Constant(b)), result } => encoding_rule!( 0b01011_110 [u,c] => b, result, data ),
            Statement::Expression{ op: ExclusiveOr(Undefined,Variable(b)), result } => encoding_rule!( 0b01011_111 [u,v] => b, result, data ),
            Statement::Expression{ op: ExclusiveOr(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // Equal: 0b01100---
            Statement::Expression{ op: Equal(Constant(a),Constant(b)), result } => encoding_rule!( 0b01100_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: Equal(Constant(a),Variable(b)), result } => encoding_rule!( 0b01100_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: Equal(Constant(a),Undefined), result } => encoding_rule!( 0b01100_010 [c,u] => a, result, data ),
            Statement::Expression{ op: Equal(Variable(a),Constant(b)), result } => encoding_rule!( 0b01100_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: Equal(Variable(a),Variable(b)), result } => encoding_rule!( 0b01100_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: Equal(Variable(a),Undefined), result } => encoding_rule!( 0b01100_101 [v,u] => a, result, data ),
            Statement::Expression{ op: Equal(Undefined,Constant(b)), result } => encoding_rule!( 0b01100_110 [u,c] => b, result, data ),
            Statement::Expression{ op: Equal(Undefined,Variable(b)), result } => encoding_rule!( 0b01100_111 [u,v] => b, result, data ),
            Statement::Expression{ op: Equal(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // LessOrEqualUnsigned: 0b01101---
            Statement::Expression{ op: LessOrEqualUnsigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b01101_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b01101_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Constant(a),Undefined), result } => encoding_rule!( 0b01101_010 [c,u] => a, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b01101_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b01101_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Variable(a),Undefined), result } => encoding_rule!( 0b01101_101 [v,u] => a, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Undefined,Constant(b)), result } => encoding_rule!( 0b01101_110 [u,c] => b, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Undefined,Variable(b)), result } => encoding_rule!( 0b01101_111 [u,v] => b, result, data ),
            Statement::Expression{ op: LessOrEqualUnsigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // LessOrEqualSigned: 0b01110---
            Statement::Expression{ op: LessOrEqualSigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b01110_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b01110_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Constant(a),Undefined), result } => encoding_rule!( 0b01110_010 [c,u] => a, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b01110_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b01110_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Variable(a),Undefined), result } => encoding_rule!( 0b01110_101 [v,u] => a, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Undefined,Constant(b)), result } => encoding_rule!( 0b01110_110 [u,c] => b, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Undefined,Variable(b)), result } => encoding_rule!( 0b01110_111 [u,v] => b, result, data ),
            Statement::Expression{ op: LessOrEqualSigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // LessUnsigned: 0b01111---
            Statement::Expression{ op: LessUnsigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b01111_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: LessUnsigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b01111_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: LessUnsigned(Constant(a),Undefined), result } => encoding_rule!( 0b01111_010 [c,u] => a, result, data ),
            Statement::Expression{ op: LessUnsigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b01111_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: LessUnsigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b01111_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: LessUnsigned(Variable(a),Undefined), result } => encoding_rule!( 0b01111_101 [v,u] => a, result, data ),
            Statement::Expression{ op: LessUnsigned(Undefined,Constant(b)), result } => encoding_rule!( 0b01111_110 [u,c] => b, result, data ),
            Statement::Expression{ op: LessUnsigned(Undefined,Variable(b)), result } => encoding_rule!( 0b01111_111 [u,v] => b, result, data ),
            Statement::Expression{ op: LessUnsigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // LessSigned: 0b10000---
            Statement::Expression{ op: LessSigned(Constant(a),Constant(b)), result } => encoding_rule!( 0b10000_000 [c,c] => a, b, result, data ),
            Statement::Expression{ op: LessSigned(Constant(a),Variable(b)), result } => encoding_rule!( 0b10000_001 [c,v] => a, b, result, data ),
            Statement::Expression{ op: LessSigned(Constant(a),Undefined), result } => encoding_rule!( 0b10000_010 [c,u] => a, result, data ),
            Statement::Expression{ op: LessSigned(Variable(a),Constant(b)), result } => encoding_rule!( 0b10000_011 [v,c] => a, b, result, data ),
            Statement::Expression{ op: LessSigned(Variable(a),Variable(b)), result } => encoding_rule!( 0b10000_100 [v,v] => a, b, result, data ),
            Statement::Expression{ op: LessSigned(Variable(a),Undefined), result } => encoding_rule!( 0b10000_101 [v,u] => a, result, data ),
            Statement::Expression{ op: LessSigned(Undefined,Constant(b)), result } => encoding_rule!( 0b10000_110 [u,c] => b, result, data ),
            Statement::Expression{ op: LessSigned(Undefined,Variable(b)), result } => encoding_rule!( 0b10000_111 [u,v] => b, result, data ),
            Statement::Expression{ op: LessSigned(Undefined,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // ZeroExtend: 0b1000100- <size, leb128> <a>
            Statement::Expression{ op: ZeroExtend(sz,Constant(a)), result } => {
                data.write(&[0b10001000])?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_constant(a,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: ZeroExtend(sz,Variable(a)), result } => {
                data.write(&[0b10001001])?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_variable(a,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: ZeroExtend(_,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // SignExtend: 0b1000101- <size, leb128> <a>
            Statement::Expression{ op: SignExtend(sz,Constant(a)), result } => {
                data.write(&[0b10001010])?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_constant(a,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: SignExtend(sz,Variable(a)), result } => {
                data.write(&[0b10001011])?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_variable(a,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: SignExtend(_,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // Move: 0b1000110- <a>
            Statement::Expression{ op: Move(Constant(a)), result } => {
                data.write(&[0b10001100])?;
                Self::encode_constant(a,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: Move(Variable(a)), result } => {
                data.write(&[0b10001101])?;
                Self::encode_variable(a,data)?;
                Self::encode_variable(result,data)?;
            }

            // Move Undefined: 0b10001110
            Statement::Expression{ op: Move(Undefined), result } => {
                data.write(&[0b10001110])?;
                Self::encode_variable(result,data)?;
            }

            // Initialize: 0b10001111 <name, leb128> <size, leb128>
            Statement::Expression{ op: Initialize(name,sz), result } => {
                data.write(&[0b10001111])?;
                leb128::write::unsigned(data,name.index() as u64)?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_variable(result,data)?;
            }

            // Select: 0b100100-- <off, leb128> <sz, leb128> <a>
            Statement::Expression{ op: Select(off,sz,Constant(src)), result } => {
                data.write(&[0b10010000])?;
                leb128::write::unsigned(data,off as u64)?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_constant(src,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: Select(off,sz,Variable(src)), result } => {
                data.write(&[0b10010001])?;
                leb128::write::unsigned(data,off as u64)?;
                leb128::write::unsigned(data,sz as u64)?;
                Self::encode_variable(src,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: Select(_,_,Undefined), result } => {
                Self::encode_statement(Statement::Expression{ op: Move(Undefined), result: result },data)?;
            }

            // Load: 0b100101e- <region, leb128> <size, leb128> <a>
            Statement::Expression{ op: Load(segment,Endianess::Little,bytes,Constant(addr)), result } => {
                data.write(&[0b10010100])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_constant(addr,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: Load(segment,Endianess::Big,bytes,Constant(addr)), result } => {
                data.write(&[0b10010110])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_constant(addr,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: Load(segment,Endianess::Little,bytes,Variable(addr)), result } => {
                data.write(&[0b10010101])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(addr,data)?;
                Self::encode_variable(result,data)?;
            }
            Statement::Expression{ op: Load(segment,Endianess::Big,bytes,Variable(addr)), result } => {
                data.write(&[0b10010111])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(addr,data)?;
                Self::encode_variable(result,data)?;
            }

            // Phi2: 0b10011000 <a> <b>
            Statement::Expression{ op: Phi(Variable(a),Variable(b),Undefined), result } |
            Statement::Expression{ op: Phi(Variable(a),Undefined,Variable(b)), result } |
            Statement::Expression{ op: Phi(Undefined,Variable(a),Variable(b)), result } => {
                data.write(&[0b10011000])?;
                Self::encode_variable(a,data)?;
                Self::encode_variable(b,data)?;
                Self::encode_variable(result,data)?;
            }

            // Phi3: 0b10011001 <a> <b> <c>
            Statement::Expression{ op: Phi(Variable(a),Variable(b),Variable(c)), result } => {
                data.write(&[0b10011001])?;
                Self::encode_variable(a,data)?;
                Self::encode_variable(b,data)?;
                Self::encode_variable(c,data)?;
                Self::encode_variable(result,data)?;
            }

            // Call: 0b10011010 <stub, leb128>
            Statement::Flow{ op: FlowOperation::ExternalCall{ external: name } } => {
                data.write(&[0b10011010])?;
                leb128::write::unsigned(data,name.index() as u64)?;
            }

            // Call: 0b10011011 <uuid, leb128>
            Statement::Flow{ op: FlowOperation::Call{ function: uuid } } => {
                data.write(&[0b10011011])?;
                data.write(&uuid.as_bytes())?;
            }

            // IndirectCall: 0b1001110- <a>
            Statement::Flow{ op: FlowOperation::IndirectCall{ target: tgt } } => {
                data.write(&[0b10011101])?;
                Self::encode_variable(tgt,data)?;
            }

            // Phi0: 0b10011111
            Statement::Expression{ op: Phi(Undefined,Undefined,Undefined), result } => {
                data.write(&[0b10011111])?;
                Self::encode_variable(result,data)?;
            }

            // Store: 0b1010e--- <region, leb128> <size, leb128> <addr> <val>
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Constant(addr), value: Constant(value) }, result } => {
                data.write(&[0b10100000 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_constant(addr,data)?;
                Self::encode_constant(value,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Constant(addr), value: Variable(value) }, result } => {
                data.write(&[0b10100001 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_constant(addr,data)?;
                Self::encode_variable(value,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Constant(addr), value: Undefined }, result } => {
                data.write(&[0b10100010 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_constant(addr,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Variable(addr), value: Constant(value) }, result } => {
                data.write(&[0b10100011 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(addr,data)?;
                Self::encode_constant(value,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Variable(addr), value: Variable(value) }, result } => {
                data.write(&[0b10100100 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(addr,data)?;
                Self::encode_variable(value,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Variable(addr), value: Undefined }, result } => {
                data.write(&[0b10100101 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(addr,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Undefined, value: Constant(value) }, result } => {
                data.write(&[0b10100110 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_constant(value,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ segment, bytes, endianess, address: Undefined, value: Variable(value) }, result } => {
                data.write(&[0b10100111 | if endianess == Endianess::Little { 0 } else { 0b1000 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(value,data)?;
                Self::encode_segment(result,data)?;
            }
            Statement::Memory{ op: MemoryOperation::Store{ address: Undefined, value: Undefined,.. },.. } => { /* NOP */ }

            // Return: 0b10110000
            Statement::Flow{ op: FlowOperation::Return } => {
                data.write(&[0b10110000])?;
            }

            // Load Undefined: 0b1011 001e <region, leb128> <size, leb128>
            Statement::Expression{ op: Load(segment,endianess,bytes,Undefined), result } => {
                data.write(&[0b1011_0010 | if endianess == Endianess::Little { 0 } else { 0b1 }])?;
                Self::encode_segment(segment,data)?;
                leb128::write::unsigned(data,bytes as u64)?;
                Self::encode_variable(result,data)?;
            }

            // Phi1: 0b10110100 <a>
            Statement::Expression{ op: Phi(Variable(a),Undefined,Undefined), result } => {
                data.write(&[0b10110100])?;
                Self::encode_variable(a,data)?;
                Self::encode_variable(result,data)?;
            }

            Statement::Expression{ op: Phi(_,_,_),.. } => {
                return Err(format!("Internal error: invalid Phi expression {:?}",stmt).into());
            }

            // MemoryPhi0: 0b10110101
            Statement::Memory{ op: MemoryOperation::MemoryPhi(None,None,None), result } => {
                data.write(&[0b10110101])?;
                Self::encode_segment(result,data)?;
                data.write(&[0,0,0])?;
            }

            // MemoryPhi1: 0b10110110 <a>
            Statement::Memory{ op: MemoryOperation::MemoryPhi(Some(a),None,None), result } |
            Statement::Memory{ op: MemoryOperation::MemoryPhi(None,Some(a),None), result } |
            Statement::Memory{ op: MemoryOperation::MemoryPhi(None,None,Some(a)), result } => {
                data.write(&[0b10110110])?;
                Self::encode_segment(a,data)?;
                Self::encode_segment(result,data)?;
                data.write(&[0,0])?;
            }

            // MemoryPhi2: 0b10110111 <a> <b>
            Statement::Memory{ op: MemoryOperation::MemoryPhi(Some(a),Some(b),None), result } |
            Statement::Memory{ op: MemoryOperation::MemoryPhi(Some(a),None,Some(b)), result } |
            Statement::Memory{ op: MemoryOperation::MemoryPhi(None,Some(a),Some(b)), result } => {
                data.write(&[0b10110111])?;
                Self::encode_segment(a,data)?;
                Self::encode_segment(b,data)?;
                Self::encode_segment(result,data)?;
                data.write(&[0])?;
            }

            // MemoryPhi3: 0b10111000 <a> <b> <c>
            Statement::Memory{ op: MemoryOperation::MemoryPhi(Some(a),Some(b),Some(c)), result } => {
                data.write(&[0b10111000])?;
                Self::encode_segment(a,data)?;
                Self::encode_segment(b,data)?;
                Self::encode_segment(c,data)?;
                Self::encode_segment(result,data)?;
            }

            // Allocate: 0b10111001 <name, leb128>
            Statement::Memory{ op: MemoryOperation::Allocate{ base }, result } => {
                data.write(&[0b10111001])?;
                leb128::write::unsigned(data,base.index() as u64)?;
                Self::encode_segment(result,data)?;
            }

            // Assume 11000--- <constr> <a>
            Statement::Expression{ op: Assume(constr,a), result } => {
                let code = match (&constr,&a) {
                    (&Constraint::Empty{ .. },&Value::Variable(..)) => 0b000,
                    (&Constraint::Unsigned{ .. },&Value::Variable(..)) => 0b001,
                    (&Constraint::Signed{ .. },&Value::Variable(..)) => 0b010,
                    (&Constraint::Full{ .. },&Value::Variable(..)) => 0b011,
                    (&Constraint::Empty{ .. },_) => 0b100,
                    (&Constraint::Unsigned{ .. },_) => 0b101,
                    (&Constraint::Signed{ .. },_) => 0b110,
                    (&Constraint::Full{ .. },_) => 0b111,
                };

                data.write(&[0b1100_0000 | code])?;
                Self::encode_constraint(constr,data)?;
                if let Value::Variable(var) = a {
                    Self::encode_variable(var,data)?;
                }
                Self::encode_variable(result,data)?;
            }
        }

        Ok(())
    }

    // const: <len, pow2><leb128 value>
    fn encode_constant<W: Write>(c: Constant, data: &mut W) -> Result<()> {
        let Constant{ value, bits } = c;
        leb128::write::unsigned(data,bits as u64)?;
        leb128::write::unsigned(data,value)?;
        Ok(())
    }

    // var: <name, leb128 str idx>, <subscript, leb128 + 1>, <len, pow2>
    fn encode_variable<W: Write>(c: Variable, data: &mut W) -> Result<()> {
        let Variable{ name, bits } = c;
        leb128::write::unsigned(data,name.index() as u64)?;
        leb128::write::unsigned(data,bits as u64)?;
        Ok(())
    }

    // seg: <name, leb128 str idx>
    fn encode_segment<W: Write>(c: Segment, data: &mut W) -> Result<()> {
        let Segment{ name } = c;
        leb128::write::unsigned(data,name.index() as u64)?;
        Ok(())
    }

    fn encode_constraint<W: Write>(c: Constraint, data: &mut W) -> Result<()> {
        match c {
            Constraint::Empty{ bits } => {
                leb128::write::unsigned(data,bits as u64)?;
            }
            Constraint::Unsigned{ from, to, bits } => {
                leb128::write::unsigned(data,bits as u64)?;
                leb128::write::unsigned(data,from)?;
                leb128::write::unsigned(data,to)?;
            }
            Constraint::Signed{ from, to, bits } => {
                leb128::write::unsigned(data,bits as u64)?;
                leb128::write::signed(data,from)?;
                leb128::write::signed(data,to)?;
            }
            Constraint::Full{ bits } => {
                leb128::write::unsigned(data,bits as u64)?;
            }
        }

        Ok(())
    }

    fn decode_statement<R: Read>(&self, data: &mut R) -> Result<Statement> {
        let mut opcode = [0u8; 1];
        data.read_exact(&mut opcode)?;

        match opcode[0] {
            0b00000_000...0b00001_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Add(a,b), result: res };

                Ok(stmt)
            }
            0b00001_000...0b00010_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Subtract(a,b), result: res };

                Ok(stmt)
            }
            0b00010_000...0b00011_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Multiply(a,b), result: res };

                Ok(stmt)
            }
            0b00011_000...0b00100_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::DivideUnsigned(a,b), result: res };

                Ok(stmt)
            }
            0b00100_000...0b00101_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::DivideSigned(a,b), result: res };

                Ok(stmt)
            }
            0b00101_000...0b00110_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::ShiftLeft(a,b), result: res };

                Ok(stmt)
            }
            0b00110_000...0b00111_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::ShiftRightUnsigned(a,b), result: res };

                Ok(stmt)
            }
            0b00111_000...0b01000_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::ShiftRightSigned(a,b), result: res };

                Ok(stmt)
            }
            0b01000_000...0b01001_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Modulo(a,b), result: res };

                Ok(stmt)
            }
            0b01001_000...0b01010_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::And(a,b), result: res };

                Ok(stmt)
            }
            0b01010_000...0b01011_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::InclusiveOr(a,b), result: res };

                Ok(stmt)
            }
            0b01011_000...0b01100_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::ExclusiveOr(a,b), result: res };

                Ok(stmt)
            }
            0b01100_000...0b01101_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Equal(a,b), result: res };

                Ok(stmt)
            }
            0b01101_000...0b01110_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::LessOrEqualUnsigned(a,b), result: res };

                Ok(stmt)
            }
            0b01110_000...0b01111_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::LessOrEqualSigned(a,b), result: res };

                Ok(stmt)
            }
            0b01111_000...0b10000_000 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::LessUnsigned(a,b), result: res };

                Ok(stmt)
            }
            0b10000_000...0b10000_111 => {
                let (a,b) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::LessSigned(a,b), result: res };

                Ok(stmt)
            }

            // zext  1000 100- <size, leb128> <a>
            0b1000_1000 | 0b1000_1001 => {
                let sz = leb128::read::unsigned(data)? as usize;
                let a = if opcode[0] & 1 == 0 {
                    Value::Constant(self.decode_constant(data)?)
                } else {
                    Value::Variable(self.decode_variable(data)?)
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::ZeroExtend(sz,a), result: res };

                Ok(stmt)
            }

            // sext  1000 101- <size, leb128> <a>
            0b1000_1010 | 0b1000_1011 => {
                let sz = leb128::read::unsigned(data)? as usize;
                let a = if opcode[0] & 1 == 0 {
                    Value::Constant(self.decode_constant(data)?)
                } else {
                    Value::Variable(self.decode_variable(data)?)
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::SignExtend(sz,a), result: res };

                Ok(stmt)
            }

            // mov   1000 110- <a>
            0b1000_1100 | 0b1000_1101 => {
                let a = if opcode[0] & 1 == 0 {
                    Value::Constant(self.decode_constant(data)?)
                } else {
                    Value::Variable(self.decode_variable(data)?)
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Move(a), result: res };

                Ok(stmt)
            }

            // movu  1000 1110
            0b1000_1110 => {
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Move(Value::Undefined), result: res };

                Ok(stmt)
            }

            // init  1000 1111 <name, leb128> <size, leb128>
            0b1000_1111 => {
                let name = leb128::read::unsigned(data)? as usize;
                let sz = leb128::read::unsigned(data)? as usize;
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Initialize(StrRef::new(name),sz),
                    result: res
                };

                Ok(stmt)
            }

            // sel   1001 00-- <off, leb128> <sz, leb128> <a>
            0b1001_0000...0b1001_0011 => {
                let off = leb128::read::unsigned(data)? as usize;
                let sz = leb128::read::unsigned(data)? as usize;
                let val = if opcode[0] & 0b1 == 0 {
                    Value::Constant(self.decode_constant(data)?)
                } else {
                    Value::Variable(self.decode_variable(data)?)
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Select(off,sz,val),
                    result: res
                };

                Ok(stmt)
            }

            // load  1001 01e- <region, leb128> <size, leb128> <a>
            0b1001_0100...0b1001_0111 => {
                let seg = self.decode_segment(data)?;
                let sz = leb128::read::unsigned(data)? as usize;
                let val = if opcode[0] & 0b1 == 0 {
                    Value::Constant(self.decode_constant(data)?)
                } else {
                    Value::Variable(self.decode_variable(data)?)
                };
                let endianess = if opcode[0] & 0b10 == 0 {
                    Endianess::Little
                } else {
                    Endianess::Big
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Load(seg,endianess,sz,val),
                    result: res
                };

                Ok(stmt)
            }

            // phi2  1001 1000 <a, var> <b, var> 0x000000
            0b1001_1000 => {
                let a = Value::Variable(self.decode_variable(data)?);
                let b = Value::Variable(self.decode_variable(data)?);
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Phi(a,b,Value::Undefined),
                    result: res
                };

                Ok(stmt)
            }

            // phi3  1001 1001 <a, var> <b, var> <c, var>
            0b1001_1001 => {
                let a = Value::Variable(self.decode_variable(data)?);
                let b = Value::Variable(self.decode_variable(data)?);
                let c = Value::Variable(self.decode_variable(data)?);
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Phi(a,b,c),
                    result: res
                };

                Ok(stmt)
            }

            // excall  1001 1010 <stub, leb128>
            0b1001_1010 => {
                let s = leb128::read::unsigned(data)? as usize;
                let stmt = Statement::Flow{
                    op: FlowOperation::ExternalCall{
                        external: StrRef::new(s)
                    }
                };

                Ok(stmt)
            }

            // call  1001 1011 <uuid, leb128>
            0b1001_1011 => {
                let mut buf = [0u8; 16];

                data.read_exact(&mut buf)?;
                let uu = Uuid::from_bytes(&buf)?;
                let stmt = Statement::Flow{
                    op: FlowOperation::Call{
                        function: uu
                    }
                };

                Ok(stmt)
            }

            // icall 1001 1101 <a>
            0b1001_1101 => {
                let stmt = Statement::Flow{ op:
                    FlowOperation::IndirectCall{
                        target: self.decode_variable(data)?
                    },
                };

                Ok(stmt)
            }

            // phi0  1001 1111
            0b1001_1111 => {
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Phi(
                        Value::undef(),
                        Value::undef(),
                        Value::undef()),
                    result: res
                };

                Ok(stmt)
            }

            // store 1010 e--- <region, leb128> <size, leb128> <addr> <val>
            0b1010_0000...0b1010_1111 => {
                let seg = self.decode_segment(data)?;
                let sz = leb128::read::unsigned(data)? as usize;
                let (addr,val) = self.decode_arguments(opcode[0] & 0b111,data)?;
                let res = self.decode_segment(data)?;
                let endianess = if opcode[0] & 0b1000 == 0 {
                    Endianess::Little
                } else {
                    Endianess::Big
                };
                let stmt = Statement::Memory{
                    op: MemoryOperation::Store{
                        segment: seg,
                        bytes: sz,
                        endianess: endianess,
                        address: addr,
                        value: val,
                    },
                    result: res,
                };

                Ok(stmt)
            }

            // ret   1011 0000
            0b1011_0000 => {
                let stmt = Statement::Flow{ op: FlowOperation::Return };

                Ok(stmt)
            }

            // loadu: 0b1011001e <region, leb128> <size, leb128>
            0b1011_0010 | 0b1011_0011 => {
                let seg = self.decode_segment(data)?;
                let sz = leb128::read::unsigned(data)? as usize;
                let endianess = if opcode[0] & 1 == 0 {
                    Endianess::Little
                } else {
                    Endianess::Big
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Load(seg,endianess,sz,Value::Undefined),
                    result: res
                };

                Ok(stmt)
            }

            // phi1: 0b10110100 <a>
            0b10110100 => {
                let a = Value::Variable(self.decode_variable(data)?);
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{
                    op: Operation::Phi(a,Value::undef(),Value::undef()),
                    result: res
                };

                Ok(stmt)
            }

            // mphi0  10110101 3 0x0
            0b10110101 => {
                let res = self.decode_segment(data)?;
                let stmt = Statement::Memory{
                    op: MemoryOperation::MemoryPhi(None,None,None),
                    result: res
                };

                data.read(&mut [0;3])?;
                Ok(stmt)
            }

            // mphi1  10110110 <a, seg> 2 0x0
            0b10110110 => {
                let a = self.decode_segment(data)?;
                let res = self.decode_segment(data)?;
                let stmt = Statement::Memory{
                    op: MemoryOperation::MemoryPhi(Some(a),None,None),
                    result: res
                };

                data.read(&mut [0;2])?;
                Ok(stmt)
            }

            // mphi2  10110111 <a, seg> <b, seg> 0x0
            0b10110111 => {
                let a = self.decode_segment(data)?;
                let b = self.decode_segment(data)?;
                let res = self.decode_segment(data)?;
                let stmt = Statement::Memory{
                    op: MemoryOperation::MemoryPhi(Some(a),Some(b),None),
                    result: res
                };

                data.read(&mut [0;1])?;
                Ok(stmt)
            }

            // mphi3  10111000 <a, seg> <b, seg> <c, seg>
            0b10111000 => {
                let a = self.decode_segment(data)?;
                let b = self.decode_segment(data)?;
                let c = self.decode_segment(data)?;
                let res = self.decode_segment(data)?;
                let stmt = Statement::Memory{
                    op: MemoryOperation::MemoryPhi(Some(a),Some(b),Some(c)),
                    result: res
                };

                Ok(stmt)
            }

            // alloc  10111001 <name, leb128>
            0b1011_1001 => {
                let name = leb128::read::unsigned(data)? as usize;
                let res = self.decode_segment(data)?;
                let stmt = Statement::Memory{
                    op: MemoryOperation::Allocate{ base: StrRef::new(name) },
                    result: res
                };

                Ok(stmt)
            }

            // assume 11000--- <constr> <a>
            0b1100_0000...0b1100_0111 => {
                let constr = self.decode_constraint(opcode[0] & 0b11,data)?;
                let a = if opcode[0] & 0b100 == 0 {
                    Value::Variable(self.decode_variable(data)?)
                } else {
                    Value::Undefined
                };
                let res = self.decode_variable(data)?;
                let stmt = Statement::Expression{ op: Operation::Assume(constr,a), result: res };

                Ok(stmt)
            }

            _ => Err(format!("Internal error: invalid bitcode {:b}",opcode[0]).into()),
        }
    }

    //  1\2  c   v   u
    //   c|000 001 010
    //   v|011 100 101
    //   u|110 111 xxx
    fn decode_arguments<R: Read>(&self, code: u8, data: &mut R) -> Result<(Value,Value)> {
        match code {
            0b000 => {
                let a = self.decode_constant(data)?;
                let b = self.decode_constant(data)?;
                Ok((Value::Constant(a),Value::Constant(b)))
            }
            0b001 => {
                let a = self.decode_constant(data)?;
                let b = self.decode_variable(data)?;
                Ok((Value::Constant(a),Value::Variable(b)))
            }
            0b010 => {
                let a = self.decode_constant(data)?;
                Ok((Value::Constant(a),Value::Undefined))
            }
            0b011 => {
                let a = self.decode_variable(data)?;
                let b = self.decode_constant(data)?;
                Ok((Value::Variable(a),Value::Constant(b)))
            }
            0b100 => {
                let a = self.decode_variable(data)?;
                let b = self.decode_variable(data)?;
                Ok((Value::Variable(a),Value::Variable(b)))
            }
            0b101 => {
                let a = self.decode_variable(data)?;
                Ok((Value::Variable(a),Value::Undefined))
            }
            0b110 => {
                let a = self.decode_constant(data)?;
                Ok((Value::Undefined,Value::Constant(a)))
            }
            0b111 => {
                let a = self.decode_variable(data)?;
                Ok((Value::Undefined,Value::Variable(a)))
            }
            _ => Err(format!("internal error: impossible argument code {:b}",code).into())
        }
    }

    // const: <len, pow2><leb128 value>
    fn decode_constant<R: Read>(&self, data: &mut R) -> Result<Constant> {
        let bits = leb128::read::unsigned(data)?;
        let value = leb128::read::unsigned(data)?;

        Ok(Constant{ bits: bits as usize, value: value })
    }

    // var: <name, leb128 str idx>
    fn decode_segment<R: Read>(&self, data: &mut R) -> Result<Segment> {
        let name = leb128::read::unsigned(data)?;
        let seg = Segment{
            name: SegmentRef::new(name as usize),
        };

        Ok(seg)
    }

    // seg: <name, leb128 str idx>, <subscript, leb128 + 1>, <len, pow2>
    fn decode_variable<R: Read>(&self, data: &mut R) -> Result<Variable> {
        let name = leb128::read::unsigned(data)?;
        let bits = leb128::read::unsigned(data)?;
        let var = Variable{
            name: NameRef::new(name as usize),
            bits: bits as usize,
        };


        Ok(var)
    }

    // |  e  u  s  f
    // | 00 01 10 11
    fn decode_constraint<R: Read>(&self, code: u8, data: &mut R) -> Result<Constraint> {
        let bits = leb128::read::unsigned(data)? as usize;

        match code {
            0b00 => {
                Ok(Constraint::Empty{ bits: bits })
            }
            0b01 => {
                let from = leb128::read::unsigned(data)?;
                let to = leb128::read::unsigned(data)?;
                let ret = Constraint::Unsigned{
                    bits: bits,
                    from: from,
                    to: to,
                };
                Ok(ret)
            }
            0b10 => {
                let from = leb128::read::signed(data)?;
                let to = leb128::read::signed(data)?;
                let ret = Constraint::Signed{
                    bits: bits,
                    from: from,
                    to: to,
                };
                Ok(ret)
            }
            0b11 => {
                Ok(Constraint::Full{ bits: bits })
            }
            _ => Err(format!("internal error: impossible constraint code {:b}",code).into())
        }
    }

    /// Iterator over all statements.
    pub fn iter<'a>(&'a self) -> BitcodeIter<'a> {
        BitcodeIter{
            cursor: Cursor::new(&self.data),
            bitcode: self,
        }
    }

    /// Iterator over all statements inside byte range `rgn`.
    pub fn iter_range<'a>(&'a self, rgn: Range<usize>) -> BitcodeIter<'a> {
        BitcodeIter{
            cursor: Cursor::new(&self.data[rgn]),
            bitcode: self,
        }
    }

    /// Number of bytes of serialized statements.
    pub fn num_bytes(&self) -> usize {
        self.data.len()
    }
}

/// Iterator over serialized statements.
pub struct BitcodeIter<'a> {
    cursor: Cursor<&'a [u8]>,
    bitcode: &'a Bitcode,
}

impl<'a> Iterator for BitcodeIter<'a> {
    type Item = Statement;

    fn next(&mut self) -> Option<Self::Item> {
        self.bitcode.decode_statement(&mut self.cursor).ok()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use simple_logger;

    quickcheck! {
        fn round_trip(v: Vec<Statement>) -> bool {
            let _ = simple_logger::init();

            debug!("in: {:?}",v);
            match Bitcode::new(v.clone()) {
                Ok(bt) => {
                    debug!("{:?}",bt);
                    let w = bt.iter().collect::<Vec<_>>();
                    debug!("decoded: {:?}",w);
                    v == w
                }
                Err(s) => {
                    debug!("err: {}",s);
                    false
                }
            }
        }
    }

    #[test]
    fn rewrite_equal_size() {
        use Operation::Add;
        let s1 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let s2 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let s3 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let mut bitcode = Bitcode::default();

        let _ = bitcode.append(vec![s1]).unwrap();
        let rgn = bitcode.append(vec![s2]).unwrap();
        let _ = bitcode.append(vec![s3]).unwrap();
        let mut names = Names::default();
        let mut strings = Strings::default();
        let mut segments = Segments::default();

        let new_rgn = bitcode.rewrite(rgn.clone(),&mut names,&mut strings,&mut segments, |stmt,_,_,_| {
            match stmt {
                &mut Statement::Expression{ op: Add(Value::Constant(ref mut a),Value::Variable(ref mut b)), ref mut result } => {
                    *a = Constant::new(43,32).unwrap();
                    *b = Variable::new(NameRef::new(1),32).unwrap();
                    *result = Variable::new(NameRef::new(2),32).unwrap();
                }
                _ => { unreachable!() }
            }

            Ok(RewriteControl::Continue)
        }).unwrap();

        assert_eq!(rgn, new_rgn);
        for (idx,stmt) in bitcode.iter().enumerate() {
            match stmt {
                Statement::Expression{ op: Add(Value::Constant(a),Value::Variable(b)), result } => {
                    if idx == 0 || idx == 2 {
                        assert_eq!(a, Constant::new(42,32).unwrap());
                        assert_eq!(b, Variable::new(NameRef::new(0),32).unwrap());
                        assert_eq!(result, Variable::new(NameRef::new(0),32).unwrap());
                    } else if idx == 1 {
                        assert_eq!(a, Constant::new(43,32).unwrap());
                        assert_eq!(b, Variable::new(NameRef::new(1),32).unwrap());
                        assert_eq!(result, Variable::new(NameRef::new(2),32).unwrap());
                    } else {
                        unreachable!()
                    }
                }
                _ => { unreachable!() }
            }
        }
    }

    #[test]
    fn rewrite_smaller_size() {
        use Operation::Add;
        let s1 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let s2 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let s3 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let mut bitcode = Bitcode::default();

        let _ = bitcode.append(vec![s1]).unwrap();
        let rgn = bitcode.append(vec![s2]).unwrap();
        let _ = bitcode.append(vec![s3]).unwrap();
        let mut names = Names::default();
        let mut strings = Strings::default();
        let mut segments = Segments::default();

        let new_rgn = bitcode.rewrite(rgn.clone(),&mut names, &mut strings,&mut segments, |stmt,_,_,_| {
            *stmt = Statement::Flow{ op: FlowOperation::Return };

            Ok(RewriteControl::Continue)
        }).unwrap();

        assert_eq!(rgn.start, new_rgn.start);
        for (idx,stmt) in bitcode.iter().enumerate() {
            match stmt {
                Statement::Expression{ op: Add(Value::Constant(a),Value::Variable(b)), result } => {
                    assert_eq!(a, Constant::new(42,32).unwrap());
                    assert_eq!(b, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(result, Variable::new(NameRef::new(0),32).unwrap());
                    assert!(idx == 0 || idx == 2);
                }
                Statement::Flow{ op: FlowOperation::Return } => {
                    assert_eq!(idx, 1);
                }
                _ => { unreachable!() }
            }
        }
    }

    #[test]
    fn rewrite_larger_size() {
        use Operation::Add;
        let s1 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let s2 = Statement::Flow{ op: FlowOperation::Return };
        let s3 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let mut bitcode = Bitcode::default();

        let _ = bitcode.append(vec![s1]).unwrap();
        let rgn = bitcode.append(vec![s2]).unwrap();
        let _ = bitcode.append(vec![s3]).unwrap();
        let mut names = Names::default();
        let mut strings = Strings::default();
        let mut segments = Segments::default();

        let new_rgn = bitcode.rewrite(rgn.clone(),&mut names,&mut strings,&mut segments, |stmt,_,_,_| {
            *stmt = Statement::Expression{
                op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
                result: Variable::new(NameRef::new(0),32).unwrap()
            };

            Ok(RewriteControl::Continue)
        }).unwrap();

        assert_eq!(rgn.start, new_rgn.start);
        assert!(rgn.end < new_rgn.end);
        for stmt in bitcode.iter() {
            match stmt {
                Statement::Expression{ op: Add(Value::Constant(a),Value::Variable(b)), result } => {
                    assert_eq!(a, Constant::new(42,32).unwrap());
                    assert_eq!(b, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(result, Variable::new(NameRef::new(0),32).unwrap());
                }
                _ => { unreachable!() }
            }
        }
    }

    #[test]
    fn insert_mid() {
        use Operation::Add;
        let s1 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let s2 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let mut bitcode = Bitcode::default();

        let _ = bitcode.append(vec![s1]).unwrap();
        let rgn = bitcode.append(vec![s2]).unwrap();

        let new_rgn = bitcode.insert(rgn.start,vec![Statement::Flow{ op: FlowOperation::Return }]).unwrap();

        assert_eq!(rgn.start, new_rgn.start);
        for (idx,stmt) in bitcode.iter().enumerate() {
            match stmt {
                Statement::Expression{ op: Add(Value::Constant(a),Value::Variable(b)), result } => {
                    assert_eq!(a, Constant::new(42,32).unwrap());
                    assert_eq!(b, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(result, Variable::new(NameRef::new(0),32).unwrap());
                    assert!(idx == 0 || idx == 2);
                }
                Statement::Flow{ op: FlowOperation::Return } => {
                    assert_eq!(idx, 1);
                }
                _ => { unreachable!() }
            }
        }
    }

    #[test]
    fn insert_start() {
        use Operation::Add;
        let s1 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let mut bitcode = Bitcode::default();
        let rgn = bitcode.append(vec![s1]).unwrap();
        let new_rgn = bitcode.insert(rgn.start,vec![Statement::Flow{ op: FlowOperation::Return }]).unwrap();

        assert_eq!(rgn.start, new_rgn.start);
        for (idx,stmt) in bitcode.iter().enumerate() {
            match stmt {
                Statement::Expression{ op: Add(Value::Constant(a),Value::Variable(b)), result } => {
                    assert_eq!(a, Constant::new(42,32).unwrap());
                    assert_eq!(b, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(result, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(idx, 1);
                }
                Statement::Flow{ op: FlowOperation::Return }=> {
                    assert_eq!(idx, 0);
                }
                _ => { unreachable!() }
            }
        }
    }

    #[test]
    fn insert_end() {
        use Operation::Add;
        let s1 = Statement::Expression{
            op: Add(Value::val(42,32).unwrap(),Value::var(NameRef::new(0),32).unwrap()),
            result: Variable::new(NameRef::new(0),32).unwrap()
        };
        let mut bitcode = Bitcode::default();
        let rgn = bitcode.append(vec![s1]).unwrap();
        let new_rgn = bitcode.insert(rgn.end,vec![Statement::Flow{ op: FlowOperation::Return }]).unwrap();

        assert_eq!(rgn.end, new_rgn.start);
        for (idx,stmt) in bitcode.iter().enumerate() {
            match stmt {
                Statement::Expression{ op: Add(Value::Constant(a),Value::Variable(b)), result } => {
                    assert_eq!(a, Constant::new(42,32).unwrap());
                    assert_eq!(b, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(result, Variable::new(NameRef::new(0),32).unwrap());
                    assert_eq!(idx, 0);
                }
                Statement::Flow{ op: FlowOperation::Return } => {
                    assert_eq!(idx, 1);
                }
                _ => { unreachable!() }
            }
        }
    }
}
