mem='\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f'+
    '\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'+
    '\x00'.repeat(65488) +
    '\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'

let accessWidth = {
  '8_s':  1,
  '8_u':  1,
  '16_s': 2,
  '16_u': 2,
  '':     4,
  'f32':  4,
  'f64':  8,
}

let baseOp = {
  '8_s':  'i32',
  '8_u':  'i32',
  '16_s': 'i32',
  '16_u': 'i32',
  '':     'i32',
  'f32':  'f32',
  'f64':  'f64',
}

function toSigned(width, num) {
  let unsignedMax = Math.pow(2, accessWidth[width] * 8) - 1;
  let signedMax = Math.pow(2, accessWidth[width] * 8 - 1) - 1;

  return (num <= signedMax ? num : -(unsignedMax + 1 - num));
}

function fromLittleEndianNum(width, bytes) {
  let base = 1;
  var res = 0;
  for (var i = 0; i < accessWidth[width]; i++) {
    res += base * bytes[i];
    base *= 256;
  }
  return res;
}

function getInt(width, offset, mem) {
  var bytes = [ ];
  for (var i = offset; i < offset + accessWidth[width]; i++) {
    if (i < mem.length)
      bytes.push(mem.charCodeAt(i));
    else
      bytes.push(0);
  }

  var res = fromLittleEndianNum(width, bytes);
  if (width == '8_s' || width == '16_s' || width == '')
    res = toSigned(width, res);
  return res;
}

function loadTwiceModule(type, ext, offset, align) {
    // TODO: Generate memory from byte string
    return wasmEvalText(
    `(module
       (memory 1)
       (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
       (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
       (data (i32.const 65520) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
       (func (param i32) (param i32) (result ${type})
         (drop (${type}.load${ext}
          offset=${offset}
          ${align != 0 ? 'align=' + align : ''}
          (local.get 0)
         ))
         (${type}.load${ext}
          offset=${offset}
          ${align != 0 ? 'align=' + align : ''}
          (local.get 1)
         )
       ) (export "" (func 0)))`
    ).exports[""];
}

function loadTwiceSameBasePlusConstModule(type, ext, offset, align, addConst) {
    return wasmEvalText(
    `(module
       (memory 1)
       (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
       (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
       (data (i32.const 65520) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
       (func (param i32) (result ${type})
         (drop (${type}.load${ext}
          offset=${offset}
          ${align != 0 ? 'align=' + align : ''}
          (local.get 0)
         ))
         (${type}.load${ext}
          offset=${offset}
          ${align != 0 ? 'align=' + align : ''}
          (i32.add (local.get 0) (i32.const ${addConst}))
         )
       ) (export "" (func 0)))`
    ).exports[""];
}

function loadTwiceSameBasePlusNonConstModule(type, ext, offset, align) {
    return wasmEvalText(
    `(module
       (memory 1)
       (data (i32.const 0) "\\00\\01\\02\\03\\04\\05\\06\\07\\08\\09\\0a\\0b\\0c\\0d\\0e\\0f")
       (data (i32.const 16) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
       (data (i32.const 65520) "\\f0\\f1\\f2\\f3\\f4\\f5\\f6\\f7\\f8\\f9\\fa\\fb\\fc\\fd\\fe\\ff")
       (func (param i32) (param i32) (result ${type})
         (drop (${type}.load${ext}
          offset=${offset}
          ${align != 0 ? 'align=' + align : ''}
          (local.get 0)
         ))
         (${type}.load${ext}
          offset=${offset}
          ${align != 0 ? 'align=' + align : ''}
          (i32.add (local.get 0) (local.get 1))
         )
       ) (export "" (func 0)))`
    ).exports[""];
}

/*
 * On x64 falsely removed bounds checks will be masked by the signal handlers.
 * Thus it is important that these tests be run on x86.
 */

function testOOB(mod, args) {
    assertErrorMessage(() => mod(...args), WebAssembly.RuntimeError, /index out of bounds/);
}

function testOk(mod, args, expected, expectedType) {
    assertEq(mod(...args), expected);
}

// TODO: It would be nice to verify how many BCs are eliminated on positive tests.

const align = 0;
for (let offset of [0, 1, 2, 3, 4, 8, 16, 41, 0xfff8]) {

  var widths = ['8_s', '8_u', '16_s', '16_u', '']

  for (let width of widths) {
    // Accesses of 1 byte.
    let lastValidIndex = 0x10000 - offset - accessWidth[width];
    let op = baseOp[width];

    var mod = loadTwiceModule(op, width, offset, align);

    // Two consecutive loads from two different bases
    testOk(mod, [lastValidIndex, lastValidIndex], getInt(width, lastValidIndex + offset, mem), op);
    testOOB(mod, [lastValidIndex + 42, lastValidIndex + 42]);
    testOOB(mod, [lastValidIndex, lastValidIndex + 42]);

    mod = loadTwiceSameBasePlusConstModule(op, width, offset, align, 1);

    testOk(mod, [lastValidIndex-1], getInt(width, lastValidIndex + offset, mem), op);
    testOOB(mod, [lastValidIndex]);

    // Two consecutive loads from same base with different offsets
    mod = loadTwiceSameBasePlusConstModule(op, width, offset, align, 2);

    testOk(mod, [lastValidIndex-2], getInt(width, lastValidIndex + offset, mem), op);
    testOOB(mod, [lastValidIndex-1, 2]);

    mod = loadTwiceSameBasePlusConstModule(op, width, offset, align, lastValidIndex);

    testOk(mod, [0], getInt(width, lastValidIndex + offset, mem), op);
    testOOB(mod, [1]);

    mod = loadTwiceSameBasePlusNonConstModule(op, width, offset, align);
    testOk(mod, [0, 1], getInt(width, 1 + offset, mem), op);
    testOk(mod, [0, lastValidIndex], getInt(width, lastValidIndex + offset, mem), op);
    testOOB(mod, [1, lastValidIndex])

    // TODO: All of the above with mixed loads and stores

    // TODO: Branching - what do we want?

    // TODO: Just loops
    //         - loop invariant checks
    //         - loop dependant checks remaining inbounds
    //         - loop dependant checks going out-of bounds.
    //
    // TODO: Loops + branching
    //         - loop invariant checks guarded by a loop invariant branch?
  }
}
