if (m_cond != 0)
    NdbCondition_Destroy(m_cond);

/*

   */

#include <ndb_global.h>
#include <NdbMain.h>
#include <NdbApi.hpp>
#include <NdbOut.hpp>
#include <NdbMutex.h>
#include <NdbCondition.h>
#include <NdbThread.h>
#include <NdbTest.hpp>

struct Opt {
  bool m_dbg;
  const char* m_scan;
  const char* m_tname;
  const char* m_xname;
  Opt() :
    m_dbg(true),
    m_scan("tx"),
    m_tname("T"),
    m_xname("X")
    {}
};

static void
printusage()
{
  Opt d;
  ndbout
    << "usage: testDeadlock" << endl
    << "-scan tx        scan table, index [" << d.m_scan << "]" << endl
    ;
}

static Opt g_opt;

static NdbMutex *ndbout_mutex= NULL;
static Ndb_cluster_connection *g_cluster_connection= 0;
#define DBG(x) \
  do { \
    if (! g_opt.m_dbg) break; \
    NdbMutex_Lock(ndbout_mutex); \
    ndbout << "line " << __LINE__ << " " << x << endl; \
    NdbMutex_Unlock(ndbout_mutex); \
  } while (0)

#define CHK(x) \
  do { \
    if (x) break; \
    ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \
    return -1; \
  } while (0)

#define CHN(p, x) \
  do { \
    if (x) break; \
    ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \
    ndbout << (p)->getNdbError() << endl; \
    return -1; \
  } while (0)

// threads

typedef int (*Runstep)(struct Thr& thr);

struct Thr {
  enum State { Wait, Start, Stop, Stopped, Exit };
  State m_state;
  int m_no;
  Runstep m_runstep;
  int m_ret;
  NdbMutex* m_mutex;
  NdbCondition* m_cond;
  NdbThread* m_thread;
  void* m_status;
  Ndb* m_ndb;
  NdbConnection* m_con;
  NdbScanOperation* m_scanop;
  NdbIndexScanOperation* m_indexscanop;
  Thr(int no);
  ~Thr();
  int run();
  void start(Runstep runstep);
  void stop();
  void stopped();
  void lock() { NdbMutex_Lock(m_mutex); }
  void unlock() { NdbMutex_Unlock(m_mutex); }
  void wait() { NdbCondition_Wait(m_cond, m_mutex); }
  void signal() { NdbCondition_Signal(m_cond); }
  void exit();
  void join() { NdbThread_WaitFor(m_thread, &m_status); }
};

static NdbOut&
operator<<(NdbOut& out, const Thr& thr) {
  out << "thr " << thr.m_no;
  return out;
}

extern "C" { static void* runthread(void* arg); }

Thr::Thr(int no)
{
  m_state = Wait;
  m_no = no;
  m_runstep = 0;
  m_ret = 0;
  m_mutex = NdbMutex_Create();
  m_cond = NdbCondition_Create();
  assert(m_mutex != 0 && m_cond != 0);
  const unsigned stacksize = 256 * 1024;
  const NDB_THREAD_PRIO prio = NDB_THREAD_PRIO_LOW;
  m_thread = NdbThread_Create(runthread, (void**)this, stacksize, "me", prio);
  if (m_thread == 0) {
    DBG("create thread failed: errno=" << errno);
    m_ret = -1;
  }
  m_status = 0;
  m_ndb = 0;
  m_con = 0;
  m_scanop = 0;
  m_indexscanop = 0;
}

Thr::~Thr()
{
  if (m_thread != 0)
    NdbThread_Destroy(&m_thread);

  if (m_mutex != 0)
    NdbMutex_Destroy(m_mutex);
}

static void*
runthread(void* arg) {
  Thr& thr = *(Thr*)arg;
  thr.run();
  return 0;
}

int
Thr::run()
{
  DBG(*this << " run");
  while (true) {
    lock();
    while (m_state != Start && m_state != Exit) {
      wait();
    }
    if (m_state == Exit) {
      DBG(*this << " exit");
      unlock();
      break;
    }
    m_ret = (*m_runstep)(*this);
    m_state = Stopped;
    signal();
    unlock();
    if (m_ret != 0) {
      DBG(*this << " error exit");
      break;
    }
  }
  delete m_ndb;
  m_ndb = 0;
  return 0;
}

void
Thr::start(Runstep runstep)
{
  lock();
  m_state = Start;
  m_runstep = runstep;
  signal();
  unlock();
}

void
Thr::stopped()
{
  lock();
  while (m_state != Stopped) {
    wait();
  }
  m_state = Wait;
  unlock();
}

void
Thr::exit()
{
  lock();
  m_state = Exit;
  signal();
  unlock();
}

// general

static int
runstep_connect(Thr& thr)
{
  Ndb* ndb = thr.m_ndb = new Ndb(g_cluster_connection, "TEST_DB");
  CHN(ndb, ndb->init() == 0);
  CHN(ndb, ndb->waitUntilReady() == 0);
  DBG(thr << " connected");
  return 0;
}

static int
runstep_starttx(Thr& thr)
{
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  CHN(ndb, (thr.m_con = ndb->startTransaction()) != 0);
  DBG("thr " << thr.m_no << " tx started");
  return 0;
}

/*
 * WL1822 flush locks
 *
 * Table T with 3 tuples X, Y, Z.
 * Two transactions (* = lock wait).
 *
 * - tx1 reads and locks Z
 * - tx2 scans X, Y, *Z
 * - tx2 returns X, Y before lock wait on Z
 * - tx1 reads and locks *X
 * - api asks for next tx2 result
 * - LQH unlocks X via ACC or TUX [*]
 * - tx1 gets lock on X
 * - tx1 returns X to api
 * - api commits tx1
 * - tx2 gets lock on Z
 * - tx2 returs Z to api
 *
 * The point is deadlock is avoided due to [*].
 * The test is for 1 db node and 1 fragment table.
 */

static char wl1822_scantx = 0;

static const Uint32 wl1822_valA[3] = { 0, 1, 2 };
static const Uint32 wl1822_valB[3] = { 3, 4, 5 };

static Uint32 wl1822_bufA = ~0;
static Uint32 wl1822_bufB = ~0;

// map scan row to key (A) and reverse
static unsigned wl1822_r2k[3] = { 0, 0, 0 };
static unsigned wl1822_k2r[3] = { 0, 0, 0 };

static int
wl1822_createtable(Thr& thr)
{
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  NdbDictionary::Dictionary* dic = ndb->getDictionary();
  // drop T
  if (dic->getTable(g_opt.m_tname) != 0)
    CHN(dic, dic->dropTable(g_opt.m_tname) == 0);
  // create T
  NdbDictionary::Table tab(g_opt.m_tname);
  tab.setFragmentType(NdbDictionary::Object::FragAllSmall);
  { NdbDictionary::Column col("A");
    col.setType(NdbDictionary::Column::Unsigned);
    col.setPrimaryKey(true);
    tab.addColumn(col);
  }
  { NdbDictionary::Column col("B");
    col.setType(NdbDictionary::Column::Unsigned);
    col.setPrimaryKey(false);
    tab.addColumn(col);
  }
  CHN(dic, dic->createTable(tab) == 0);
  // create X
  NdbDictionary::Index ind(g_opt.m_xname);
  ind.setTable(g_opt.m_tname);
  ind.setType(NdbDictionary::Index::OrderedIndex);
  ind.setLogging(false);
  ind.addColumn("B");
  CHN(dic, dic->createIndex(ind) == 0);
  DBG("created " << g_opt.m_tname << ", " << g_opt.m_xname);
  return 0;
}

static int
wl1822_insertrows(Thr& thr)
{
  // insert X, Y, Z
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  NdbConnection* con;
  NdbOperation* op;
  for (unsigned k = 0; k < 3; k++) {
    CHN(ndb, (con = ndb->startTransaction()) != 0);
    CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
    CHN(op, op->insertTuple() == 0);
    CHN(op, op->equal("A", (char*)&wl1822_valA[k]) == 0);
    CHN(op, op->setValue("B", (char*)&wl1822_valB[k]) == 0);
    CHN(con, con->execute(Commit) == 0);
    ndb->closeTransaction(con);
  }
  DBG("inserted X, Y, Z");
  return 0;
}

static int
wl1822_getscanorder(Thr& thr)
{
  // cheat, table order happens to be key order in my test
  wl1822_r2k[0] = 0;
  wl1822_r2k[1] = 1;
  wl1822_r2k[2] = 2;
  wl1822_k2r[0] = 0;
  wl1822_k2r[1] = 1;
  wl1822_k2r[2] = 2;
  DBG("scan order determined");
  return 0;
}

static int
wl1822_tx1_readZ(Thr& thr)
{
  // tx1 read Z with exclusive lock
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbOperation* op;
  CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
  CHN(op, op->readTupleExclusive() == 0);
  CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0);
  wl1822_bufB = ~0;
  CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]);
  DBG("tx1 locked Z");
  return 0;
}

static int
wl1822_tx2_scanXY(Thr& thr)
{
  // tx2 scan X, Y with exclusive lock
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbScanOperation* scanop;
  NdbIndexScanOperation* indexscanop;
  NdbResultSet* rs;
  if (wl1822_scantx == 't') {
    CHN(con, (scanop = thr.m_scanop = con->getNdbScanOperation(g_opt.m_tname)) != 0);
    DBG("tx2 scan exclusive " << g_opt.m_tname);
  }
  if (wl1822_scantx == 'x') {
    CHN(con, (scanop = thr.m_scanop = indexscanop = thr.m_indexscanop = con->getNdbIndexScanOperation(g_opt.m_xname, g_opt.m_tname)) != 0);
    DBG("tx2 scan exclusive " << g_opt.m_xname);
  }
  CHN(scanop, scanop->readTuplesExclusive(16) == 0);
  CHN(scanop, scanop->getValue("A", (char*)&wl1822_bufA) != 0);
  CHN(scanop, scanop->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  unsigned row = 0;
  while (row < 2) {
    DBG("before row " << row);
    int ret;
    wl1822_bufA = wl1822_bufB = ~0;
    CHN(con, (ret = scanop->nextResult(true)) == 0);
    DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB);
    CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]);
    CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]);
    row++;
  }
  return 0;
}

static int
wl1822_tx1_readX_commit(Thr& thr)
{
  // tx1 read X with exclusive lock and commit
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbOperation* op;
  CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
  CHN(op, op->readTupleExclusive() == 0);
  CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0);
  wl1822_bufB = ~0;
  CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]);
  DBG("tx1 locked X");
  CHN(con, con->execute(Commit) == 0);
  DBG("tx1 commit");
  return 0;
}

static int
wl1822_tx2_scanZ_close(Thr& thr)
{
  // tx2 scan Z with exclusive lock and close scan
  Ndb* ndb = thr.m_ndb;
  NdbConnection* con = thr.m_con;
  NdbScanOperation* scanop = thr.m_scanop;
  assert(ndb != 0 && con != 0 && scanop != 0);
  unsigned row = 2;
  while (true) {
    DBG("before row " << row);
    int ret;
    wl1822_bufA = wl1822_bufB = ~0;
    CHN(con, (ret = scanop->nextResult(true)) == 0 || ret == 1);
    if (ret == 1)
      break;
    DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB);
    CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]);
    CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]);
    row++;
  }
  ndb->closeTransaction(con);
  CHK(row == 3);
  return 0;
}

// threads are synced between each step
static Runstep wl1822_step[][2] = {
  { runstep_connect, runstep_connect },
  { wl1822_createtable, 0 },
  { wl1822_insertrows, 0 },
  { wl1822_getscanorder, 0 },
  { runstep_starttx, runstep_starttx },
  { wl1822_tx1_readZ, 0 },
  { 0, wl1822_tx2_scanXY },
  { wl1822_tx1_readX_commit, wl1822_tx2_scanZ_close }
};
const unsigned wl1822_stepcount = sizeof(wl1822_step)/sizeof(wl1822_step[0]);

static int
wl1822_main(char scantx)
{
  wl1822_scantx = scantx;
  static const unsigned thrcount = 2;
  // create threads for tx1 and tx2
  Thr* thrlist[2];
  int n;
  for (n = 0; n < thrcount; n++) {
    Thr& thr = *(thrlist[n] = new Thr(1 + n));
    CHK(thr.m_ret == 0);
  }
  // run the steps
  for (unsigned i = 0; i < wl1822_stepcount; i++) {
    DBG("step " << i << " start");
    for (n = 0; n < thrcount; n++) {
      Thr& thr = *thrlist[n];
      Runstep runstep = wl1822_step[i][n];
      if (runstep != 0)
        thr.start(runstep);
    }
    for (n = 0; n < thrcount; n++) {
      Thr& thr = *thrlist[n];
      Runstep runstep = wl1822_step[i][n];
      if (runstep != 0)
        thr.stopped();
    }
  }
  // delete threads
  for (n = 0; n < thrcount; n++) {
    Thr& thr = *thrlist[n];
    thr.exit();
    thr.join();
    delete &thr;
  }
  return 0;
}

NDB_COMMAND(testOdbcDriver, "testDeadlock", "testDeadlock", "testDeadlock", 65535)
{
  ndb_init();
  if (ndbout_mutex == NULL)
    ndbout_mutex= NdbMutex_Create();
  while (++argv, --argc > 0) {
    const char* arg = argv[0];
    if (strcmp(arg, "-scan") == 0) {
      if (++argv, --argc > 0) {
        g_opt.m_scan = strdup(argv[0]);
        continue;
      }
    }
    printusage();
    return NDBT_ProgramExit(NDBT_WRONGARGS);
  }

  Ndb_cluster_connection con;
  if(con.connect(12, 5, 1) != 0)
  {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  g_cluster_connection= &con;

  if (
      strchr(g_opt.m_scan, 't') != 0 && wl1822_main('t') == -1 ||
      strchr(g_opt.m_scan, 'x') != 0 && wl1822_main('x') == -1
      ) {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  return NDBT_ProgramExit(NDBT_OK);
}


for (unsigned i = 0; i < wl1822_stepcount; i++) {
    DBG("step " << i << " start");
    for (n = 0; n < thrcount; n++) {
      Thr& thr = *thrlist[n];
      Runstep runstep = wl1822_step[i][n];
      if (runstep != 0)
        thr.start(runstep);
    }
    for (n = 0; n < thrcount; n++) {
      Thr& thr = *thrlist[n];
      Runstep runstep = wl1822_step[i][n];
      if (runstep != 0)
        thr.stopped();
    }
  }

/*

   */

#include <ndb_global.h>
#include <NdbMain.h>
#include <NdbApi.hpp>
#include <NdbOut.hpp>
#include <NdbMutex.h>
#include <NdbCondition.h>
#include <NdbThread.h>
#include <NdbTest.hpp>

struct Opt {
  bool m_dbg;
  const char* m_scan;
  const char* m_tname;
  const char* m_xname;
  Opt() :
    m_dbg(true),
    m_scan("tx"),
    m_tname("T"),
    m_xname("X")
    {}
};

static void
printusage()
{
  Opt d;
  ndbout
    << "usage: testDeadlock" << endl
    << "-scan tx        scan table, index [" << d.m_scan << "]" << endl
    ;
}

static Opt g_opt;

static NdbMutex *ndbout_mutex= NULL;
static Ndb_cluster_connection *g_cluster_connection= 0;
#define DBG(x) \
  do { \
    if (! g_opt.m_dbg) break; \
    NdbMutex_Lock(ndbout_mutex); \
    ndbout << "line " << __LINE__ << " " << x << endl; \
    NdbMutex_Unlock(ndbout_mutex); \
  } while (0)

#define CHK(x) \
  do { \
    if (x) break; \
    ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \
    return -1; \
  } while (0)

#define CHN(p, x) \
  do { \
    if (x) break; \
    ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \
    ndbout << (p)->getNdbError() << endl; \
    return -1; \
  } while (0)

// threads

typedef int (*Runstep)(struct Thr& thr);

struct Thr {
  enum State { Wait, Start, Stop, Stopped, Exit };
  State m_state;
  int m_no;
  Runstep m_runstep;
  int m_ret;
  NdbMutex* m_mutex;
  NdbCondition* m_cond;
  NdbThread* m_thread;
  void* m_status;
  Ndb* m_ndb;
  NdbConnection* m_con;
  NdbScanOperation* m_scanop;
  NdbIndexScanOperation* m_indexscanop;
  Thr(int no);
  ~Thr();
  int run();
  void start(Runstep runstep);
  void stop();
  void stopped();
  void lock() { NdbMutex_Lock(m_mutex); }
  void unlock() { NdbMutex_Unlock(m_mutex); }
  void wait() { NdbCondition_Wait(m_cond, m_mutex); }
  void signal() { NdbCondition_Signal(m_cond); }
  void exit();
  void join() { NdbThread_WaitFor(m_thread, &m_status); }
};

static NdbOut&
operator<<(NdbOut& out, const Thr& thr) {
  out << "thr " << thr.m_no;
  return out;
}

extern "C" { static void* runthread(void* arg); }

Thr::Thr(int no)
{
  m_state = Wait;
  m_no = no;
  m_runstep = 0;
  m_ret = 0;
  m_mutex = NdbMutex_Create();
  m_cond = NdbCondition_Create();
  assert(m_mutex != 0 && m_cond != 0);
  const unsigned stacksize = 256 * 1024;
  const NDB_THREAD_PRIO prio = NDB_THREAD_PRIO_LOW;
  m_thread = NdbThread_Create(runthread, (void**)this, stacksize, "me", prio);
  if (m_thread == 0) {
    DBG("create thread failed: errno=" << errno);
    m_ret = -1;
  }
  m_status = 0;
  m_ndb = 0;
  m_con = 0;
  m_scanop = 0;
  m_indexscanop = 0;
}

Thr::~Thr()
{
  if (m_thread != 0)
    NdbThread_Destroy(&m_thread);
  if (m_cond != 0)
    NdbCondition_Destroy(m_cond);
  if (m_mutex != 0)
    NdbMutex_Destroy(m_mutex);
}

static void*
runthread(void* arg) {
  Thr& thr = *(Thr*)arg;
  thr.run();
  return 0;
}

int
Thr::run()
{
  DBG(*this << " run");
  while (true) {
    lock();
    while (m_state != Start && m_state != Exit) {
      wait();
    }
    if (m_state == Exit) {
      DBG(*this << " exit");
      unlock();
      break;
    }
    m_ret = (*m_runstep)(*this);
    m_state = Stopped;
    signal();
    unlock();
    if (m_ret != 0) {
      DBG(*this << " error exit");
      break;
    }
  }
  delete m_ndb;
  m_ndb = 0;
  return 0;
}

void
Thr::start(Runstep runstep)
{
  lock();
  m_state = Start;
  m_runstep = runstep;
  signal();
  unlock();
}

void
Thr::stopped()
{
  lock();
  while (m_state != Stopped) {
    wait();
  }
  m_state = Wait;
  unlock();
}

void
Thr::exit()
{
  lock();
  m_state = Exit;
  signal();
  unlock();
}

// general

static int
runstep_connect(Thr& thr)
{
  Ndb* ndb = thr.m_ndb = new Ndb(g_cluster_connection, "TEST_DB");
  CHN(ndb, ndb->init() == 0);
  CHN(ndb, ndb->waitUntilReady() == 0);
  DBG(thr << " connected");
  return 0;
}

static int
runstep_starttx(Thr& thr)
{
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  CHN(ndb, (thr.m_con = ndb->startTransaction()) != 0);
  DBG("thr " << thr.m_no << " tx started");
  return 0;
}

/*
 * WL1822 flush locks
 *
 * Table T with 3 tuples X, Y, Z.
 * Two transactions (* = lock wait).
 *
 * - tx1 reads and locks Z
 * - tx2 scans X, Y, *Z
 * - tx2 returns X, Y before lock wait on Z
 * - tx1 reads and locks *X
 * - api asks for next tx2 result
 * - LQH unlocks X via ACC or TUX [*]
 * - tx1 gets lock on X
 * - tx1 returns X to api
 * - api commits tx1
 * - tx2 gets lock on Z
 * - tx2 returs Z to api
 *
 * The point is deadlock is avoided due to [*].
 * The test is for 1 db node and 1 fragment table.
 */

static char wl1822_scantx = 0;

static const Uint32 wl1822_valA[3] = { 0, 1, 2 };
static const Uint32 wl1822_valB[3] = { 3, 4, 5 };

static Uint32 wl1822_bufA = ~0;
static Uint32 wl1822_bufB = ~0;

// map scan row to key (A) and reverse
static unsigned wl1822_r2k[3] = { 0, 0, 0 };
static unsigned wl1822_k2r[3] = { 0, 0, 0 };

static int
wl1822_createtable(Thr& thr)
{
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  NdbDictionary::Dictionary* dic = ndb->getDictionary();
  // drop T
  if (dic->getTable(g_opt.m_tname) != 0)
    CHN(dic, dic->dropTable(g_opt.m_tname) == 0);
  // create T
  NdbDictionary::Table tab(g_opt.m_tname);
  tab.setFragmentType(NdbDictionary::Object::FragAllSmall);
  { NdbDictionary::Column col("A");
    col.setType(NdbDictionary::Column::Unsigned);
    col.setPrimaryKey(true);
    tab.addColumn(col);
  }
  { NdbDictionary::Column col("B");
    col.setType(NdbDictionary::Column::Unsigned);
    col.setPrimaryKey(false);
    tab.addColumn(col);
  }
  CHN(dic, dic->createTable(tab) == 0);
  // create X
  NdbDictionary::Index ind(g_opt.m_xname);
  ind.setTable(g_opt.m_tname);
  ind.setType(NdbDictionary::Index::OrderedIndex);
  ind.setLogging(false);
  ind.addColumn("B");
  CHN(dic, dic->createIndex(ind) == 0);
  DBG("created " << g_opt.m_tname << ", " << g_opt.m_xname);
  return 0;
}

static int
wl1822_insertrows(Thr& thr)
{
  // insert X, Y, Z
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  NdbConnection* con;
  NdbOperation* op;
  for (unsigned k = 0; k < 3; k++) {
    CHN(ndb, (con = ndb->startTransaction()) != 0);
    CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
    CHN(op, op->insertTuple() == 0);
    CHN(op, op->equal("A", (char*)&wl1822_valA[k]) == 0);
    CHN(op, op->setValue("B", (char*)&wl1822_valB[k]) == 0);
    CHN(con, con->execute(Commit) == 0);
    ndb->closeTransaction(con);
  }
  DBG("inserted X, Y, Z");
  return 0;
}

static int
wl1822_getscanorder(Thr& thr)
{
  // cheat, table order happens to be key order in my test
  wl1822_r2k[0] = 0;
  wl1822_r2k[1] = 1;
  wl1822_r2k[2] = 2;
  wl1822_k2r[0] = 0;
  wl1822_k2r[1] = 1;
  wl1822_k2r[2] = 2;
  DBG("scan order determined");
  return 0;
}

static int
wl1822_tx1_readZ(Thr& thr)
{
  // tx1 read Z with exclusive lock
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbOperation* op;
  CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
  CHN(op, op->readTupleExclusive() == 0);
  CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0);
  wl1822_bufB = ~0;
  CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]);
  DBG("tx1 locked Z");
  return 0;
}

static int
wl1822_tx2_scanXY(Thr& thr)
{
  // tx2 scan X, Y with exclusive lock
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbScanOperation* scanop;
  NdbIndexScanOperation* indexscanop;
  NdbResultSet* rs;
  if (wl1822_scantx == 't') {
    CHN(con, (scanop = thr.m_scanop = con->getNdbScanOperation(g_opt.m_tname)) != 0);
    DBG("tx2 scan exclusive " << g_opt.m_tname);
  }
  if (wl1822_scantx == 'x') {
    CHN(con, (scanop = thr.m_scanop = indexscanop = thr.m_indexscanop = con->getNdbIndexScanOperation(g_opt.m_xname, g_opt.m_tname)) != 0);
    DBG("tx2 scan exclusive " << g_opt.m_xname);
  }
  CHN(scanop, scanop->readTuplesExclusive(16) == 0);
  CHN(scanop, scanop->getValue("A", (char*)&wl1822_bufA) != 0);
  CHN(scanop, scanop->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  unsigned row = 0;
  while (row < 2) {
    DBG("before row " << row);
    int ret;
    wl1822_bufA = wl1822_bufB = ~0;
    CHN(con, (ret = scanop->nextResult(true)) == 0);
    DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB);
    CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]);
    CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]);
    row++;
  }
  return 0;
}

static int
wl1822_tx1_readX_commit(Thr& thr)
{
  // tx1 read X with exclusive lock and commit
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbOperation* op;
  CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
  CHN(op, op->readTupleExclusive() == 0);
  CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0);
  wl1822_bufB = ~0;
  CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]);
  DBG("tx1 locked X");
  CHN(con, con->execute(Commit) == 0);
  DBG("tx1 commit");
  return 0;
}

static int
wl1822_tx2_scanZ_close(Thr& thr)
{
  // tx2 scan Z with exclusive lock and close scan
  Ndb* ndb = thr.m_ndb;
  NdbConnection* con = thr.m_con;
  NdbScanOperation* scanop = thr.m_scanop;
  assert(ndb != 0 && con != 0 && scanop != 0);
  unsigned row = 2;
  while (true) {
    DBG("before row " << row);
    int ret;
    wl1822_bufA = wl1822_bufB = ~0;
    CHN(con, (ret = scanop->nextResult(true)) == 0 || ret == 1);
    if (ret == 1)
      break;
    DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB);
    CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]);
    CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]);
    row++;
  }
  ndb->closeTransaction(con);
  CHK(row == 3);
  return 0;
}

// threads are synced between each step
static Runstep wl1822_step[][2] = {
  { runstep_connect, runstep_connect },
  { wl1822_createtable, 0 },
  { wl1822_insertrows, 0 },
  { wl1822_getscanorder, 0 },
  { runstep_starttx, runstep_starttx },
  { wl1822_tx1_readZ, 0 },
  { 0, wl1822_tx2_scanXY },
  { wl1822_tx1_readX_commit, wl1822_tx2_scanZ_close }
};
const unsigned wl1822_stepcount = sizeof(wl1822_step)/sizeof(wl1822_step[0]);

static int
wl1822_main(char scantx)
{
  wl1822_scantx = scantx;
  static const unsigned thrcount = 2;
  // create threads for tx1 and tx2
  Thr* thrlist[2];
  int n;
  for (n = 0; n < thrcount; n++) {
    Thr& thr = *(thrlist[n] = new Thr(1 + n));
    CHK(thr.m_ret == 0);
  }
  // run the steps

  // delete threads
  for (n = 0; n < thrcount; n++) {
    Thr& thr = *thrlist[n];
    thr.exit();
    thr.join();
    delete &thr;
  }
  return 0;
}

NDB_COMMAND(testOdbcDriver, "testDeadlock", "testDeadlock", "testDeadlock", 65535)
{
  ndb_init();
  if (ndbout_mutex == NULL)
    ndbout_mutex= NdbMutex_Create();
  while (++argv, --argc > 0) {
    const char* arg = argv[0];
    if (strcmp(arg, "-scan") == 0) {
      if (++argv, --argc > 0) {
        g_opt.m_scan = strdup(argv[0]);
        continue;
      }
    }
    printusage();
    return NDBT_ProgramExit(NDBT_WRONGARGS);
  }

  Ndb_cluster_connection con;
  if(con.connect(12, 5, 1) != 0)
  {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  g_cluster_connection= &con;

  if (
      strchr(g_opt.m_scan, 't') != 0 && wl1822_main('t') == -1 ||
      strchr(g_opt.m_scan, 'x') != 0 && wl1822_main('x') == -1
      ) {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  return NDBT_ProgramExit(NDBT_OK);
}


while (true) {
    lock();
    while (m_state != Start && m_state != Exit) {
      wait();
    }
    if (m_state == Exit) {
      DBG(*this << " exit");
      unlock();
      break;
    }
    m_ret = (*m_runstep)(*this);
    m_state = Stopped;
    signal();
    unlock();
    if (m_ret != 0) {
      DBG(*this << " error exit");
      break;
    }
  }

/*

   */

#include <ndb_global.h>
#include <NdbMain.h>
#include <NdbApi.hpp>
#include <NdbOut.hpp>
#include <NdbMutex.h>
#include <NdbCondition.h>
#include <NdbThread.h>
#include <NdbTest.hpp>

struct Opt {
  bool m_dbg;
  const char* m_scan;
  const char* m_tname;
  const char* m_xname;
  Opt() :
    m_dbg(true),
    m_scan("tx"),
    m_tname("T"),
    m_xname("X")
    {}
};

static void
printusage()
{
  Opt d;
  ndbout
    << "usage: testDeadlock" << endl
    << "-scan tx        scan table, index [" << d.m_scan << "]" << endl
    ;
}

static Opt g_opt;

static NdbMutex *ndbout_mutex= NULL;
static Ndb_cluster_connection *g_cluster_connection= 0;
#define DBG(x) \
  do { \
    if (! g_opt.m_dbg) break; \
    NdbMutex_Lock(ndbout_mutex); \
    ndbout << "line " << __LINE__ << " " << x << endl; \
    NdbMutex_Unlock(ndbout_mutex); \
  } while (0)

#define CHK(x) \
  do { \
    if (x) break; \
    ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \
    return -1; \
  } while (0)

#define CHN(p, x) \
  do { \
    if (x) break; \
    ndbout << "line " << __LINE__ << ": " << #x << " failed" << endl; \
    ndbout << (p)->getNdbError() << endl; \
    return -1; \
  } while (0)

// threads

typedef int (*Runstep)(struct Thr& thr);

struct Thr {
  enum State { Wait, Start, Stop, Stopped, Exit };
  State m_state;
  int m_no;
  Runstep m_runstep;
  int m_ret;
  NdbMutex* m_mutex;
  NdbCondition* m_cond;
  NdbThread* m_thread;
  void* m_status;
  Ndb* m_ndb;
  NdbConnection* m_con;
  NdbScanOperation* m_scanop;
  NdbIndexScanOperation* m_indexscanop;
  Thr(int no);
  ~Thr();
  int run();
  void start(Runstep runstep);
  void stop();
  void stopped();
  void lock() { NdbMutex_Lock(m_mutex); }
  void unlock() { NdbMutex_Unlock(m_mutex); }
  void wait() { NdbCondition_Wait(m_cond, m_mutex); }
  void signal() { NdbCondition_Signal(m_cond); }
  void exit();
  void join() { NdbThread_WaitFor(m_thread, &m_status); }
};

static NdbOut&
operator<<(NdbOut& out, const Thr& thr) {
  out << "thr " << thr.m_no;
  return out;
}

extern "C" { static void* runthread(void* arg); }

Thr::Thr(int no)
{
  m_state = Wait;
  m_no = no;
  m_runstep = 0;
  m_ret = 0;
  m_mutex = NdbMutex_Create();
  m_cond = NdbCondition_Create();
  assert(m_mutex != 0 && m_cond != 0);
  const unsigned stacksize = 256 * 1024;
  const NDB_THREAD_PRIO prio = NDB_THREAD_PRIO_LOW;
  m_thread = NdbThread_Create(runthread, (void**)this, stacksize, "me", prio);
  if (m_thread == 0) {
    DBG("create thread failed: errno=" << errno);
    m_ret = -1;
  }
  m_status = 0;
  m_ndb = 0;
  m_con = 0;
  m_scanop = 0;
  m_indexscanop = 0;
}

Thr::~Thr()
{
  if (m_thread != 0)
    NdbThread_Destroy(&m_thread);
  if (m_cond != 0)
    NdbCondition_Destroy(m_cond);
  if (m_mutex != 0)
    NdbMutex_Destroy(m_mutex);
}

static void*
runthread(void* arg) {
  Thr& thr = *(Thr*)arg;
  thr.run();
  return 0;
}

int
Thr::run()
{
  DBG(*this << " run");

  delete m_ndb;
  m_ndb = 0;
  return 0;
}

void
Thr::start(Runstep runstep)
{
  lock();
  m_state = Start;
  m_runstep = runstep;
  signal();
  unlock();
}

void
Thr::stopped()
{
  lock();
  while (m_state != Stopped) {
    wait();
  }
  m_state = Wait;
  unlock();
}

void
Thr::exit()
{
  lock();
  m_state = Exit;
  signal();
  unlock();
}

// general

static int
runstep_connect(Thr& thr)
{
  Ndb* ndb = thr.m_ndb = new Ndb(g_cluster_connection, "TEST_DB");
  CHN(ndb, ndb->init() == 0);
  CHN(ndb, ndb->waitUntilReady() == 0);
  DBG(thr << " connected");
  return 0;
}

static int
runstep_starttx(Thr& thr)
{
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  CHN(ndb, (thr.m_con = ndb->startTransaction()) != 0);
  DBG("thr " << thr.m_no << " tx started");
  return 0;
}

/*
 * WL1822 flush locks
 *
 * Table T with 3 tuples X, Y, Z.
 * Two transactions (* = lock wait).
 *
 * - tx1 reads and locks Z
 * - tx2 scans X, Y, *Z
 * - tx2 returns X, Y before lock wait on Z
 * - tx1 reads and locks *X
 * - api asks for next tx2 result
 * - LQH unlocks X via ACC or TUX [*]
 * - tx1 gets lock on X
 * - tx1 returns X to api
 * - api commits tx1
 * - tx2 gets lock on Z
 * - tx2 returs Z to api
 *
 * The point is deadlock is avoided due to [*].
 * The test is for 1 db node and 1 fragment table.
 */

static char wl1822_scantx = 0;

static const Uint32 wl1822_valA[3] = { 0, 1, 2 };
static const Uint32 wl1822_valB[3] = { 3, 4, 5 };

static Uint32 wl1822_bufA = ~0;
static Uint32 wl1822_bufB = ~0;

// map scan row to key (A) and reverse
static unsigned wl1822_r2k[3] = { 0, 0, 0 };
static unsigned wl1822_k2r[3] = { 0, 0, 0 };

static int
wl1822_createtable(Thr& thr)
{
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  NdbDictionary::Dictionary* dic = ndb->getDictionary();
  // drop T
  if (dic->getTable(g_opt.m_tname) != 0)
    CHN(dic, dic->dropTable(g_opt.m_tname) == 0);
  // create T
  NdbDictionary::Table tab(g_opt.m_tname);
  tab.setFragmentType(NdbDictionary::Object::FragAllSmall);
  { NdbDictionary::Column col("A");
    col.setType(NdbDictionary::Column::Unsigned);
    col.setPrimaryKey(true);
    tab.addColumn(col);
  }
  { NdbDictionary::Column col("B");
    col.setType(NdbDictionary::Column::Unsigned);
    col.setPrimaryKey(false);
    tab.addColumn(col);
  }
  CHN(dic, dic->createTable(tab) == 0);
  // create X
  NdbDictionary::Index ind(g_opt.m_xname);
  ind.setTable(g_opt.m_tname);
  ind.setType(NdbDictionary::Index::OrderedIndex);
  ind.setLogging(false);
  ind.addColumn("B");
  CHN(dic, dic->createIndex(ind) == 0);
  DBG("created " << g_opt.m_tname << ", " << g_opt.m_xname);
  return 0;
}

static int
wl1822_insertrows(Thr& thr)
{
  // insert X, Y, Z
  Ndb* ndb = thr.m_ndb;
  assert(ndb != 0);
  NdbConnection* con;
  NdbOperation* op;
  for (unsigned k = 0; k < 3; k++) {
    CHN(ndb, (con = ndb->startTransaction()) != 0);
    CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
    CHN(op, op->insertTuple() == 0);
    CHN(op, op->equal("A", (char*)&wl1822_valA[k]) == 0);
    CHN(op, op->setValue("B", (char*)&wl1822_valB[k]) == 0);
    CHN(con, con->execute(Commit) == 0);
    ndb->closeTransaction(con);
  }
  DBG("inserted X, Y, Z");
  return 0;
}

static int
wl1822_getscanorder(Thr& thr)
{
  // cheat, table order happens to be key order in my test
  wl1822_r2k[0] = 0;
  wl1822_r2k[1] = 1;
  wl1822_r2k[2] = 2;
  wl1822_k2r[0] = 0;
  wl1822_k2r[1] = 1;
  wl1822_k2r[2] = 2;
  DBG("scan order determined");
  return 0;
}

static int
wl1822_tx1_readZ(Thr& thr)
{
  // tx1 read Z with exclusive lock
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbOperation* op;
  CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
  CHN(op, op->readTupleExclusive() == 0);
  CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0);
  wl1822_bufB = ~0;
  CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]);
  DBG("tx1 locked Z");
  return 0;
}

static int
wl1822_tx2_scanXY(Thr& thr)
{
  // tx2 scan X, Y with exclusive lock
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbScanOperation* scanop;
  NdbIndexScanOperation* indexscanop;
  NdbResultSet* rs;
  if (wl1822_scantx == 't') {
    CHN(con, (scanop = thr.m_scanop = con->getNdbScanOperation(g_opt.m_tname)) != 0);
    DBG("tx2 scan exclusive " << g_opt.m_tname);
  }
  if (wl1822_scantx == 'x') {
    CHN(con, (scanop = thr.m_scanop = indexscanop = thr.m_indexscanop = con->getNdbIndexScanOperation(g_opt.m_xname, g_opt.m_tname)) != 0);
    DBG("tx2 scan exclusive " << g_opt.m_xname);
  }
  CHN(scanop, scanop->readTuplesExclusive(16) == 0);
  CHN(scanop, scanop->getValue("A", (char*)&wl1822_bufA) != 0);
  CHN(scanop, scanop->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  unsigned row = 0;
  while (row < 2) {
    DBG("before row " << row);
    int ret;
    wl1822_bufA = wl1822_bufB = ~0;
    CHN(con, (ret = scanop->nextResult(true)) == 0);
    DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB);
    CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]);
    CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]);
    row++;
  }
  return 0;
}

static int
wl1822_tx1_readX_commit(Thr& thr)
{
  // tx1 read X with exclusive lock and commit
  NdbConnection* con = thr.m_con;
  assert(con != 0);
  NdbOperation* op;
  CHN(con, (op = con->getNdbOperation(g_opt.m_tname)) != 0);
  CHN(op, op->readTupleExclusive() == 0);
  CHN(op, op->equal("A", wl1822_valA[wl1822_r2k[2]]) == 0);
  wl1822_bufB = ~0;
  CHN(op, op->getValue("B", (char*)&wl1822_bufB) != 0);
  CHN(con, con->execute(NoCommit) == 0);
  CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[2]]);
  DBG("tx1 locked X");
  CHN(con, con->execute(Commit) == 0);
  DBG("tx1 commit");
  return 0;
}

static int
wl1822_tx2_scanZ_close(Thr& thr)
{
  // tx2 scan Z with exclusive lock and close scan
  Ndb* ndb = thr.m_ndb;
  NdbConnection* con = thr.m_con;
  NdbScanOperation* scanop = thr.m_scanop;
  assert(ndb != 0 && con != 0 && scanop != 0);
  unsigned row = 2;
  while (true) {
    DBG("before row " << row);
    int ret;
    wl1822_bufA = wl1822_bufB = ~0;
    CHN(con, (ret = scanop->nextResult(true)) == 0 || ret == 1);
    if (ret == 1)
      break;
    DBG("got row " << row << " a=" << wl1822_bufA << " b=" << wl1822_bufB);
    CHK(wl1822_bufA == wl1822_valA[wl1822_r2k[row]]);
    CHK(wl1822_bufB == wl1822_valB[wl1822_r2k[row]]);
    row++;
  }
  ndb->closeTransaction(con);
  CHK(row == 3);
  return 0;
}

// threads are synced between each step
static Runstep wl1822_step[][2] = {
  { runstep_connect, runstep_connect },
  { wl1822_createtable, 0 },
  { wl1822_insertrows, 0 },
  { wl1822_getscanorder, 0 },
  { runstep_starttx, runstep_starttx },
  { wl1822_tx1_readZ, 0 },
  { 0, wl1822_tx2_scanXY },
  { wl1822_tx1_readX_commit, wl1822_tx2_scanZ_close }
};
const unsigned wl1822_stepcount = sizeof(wl1822_step)/sizeof(wl1822_step[0]);

static int
wl1822_main(char scantx)
{
  wl1822_scantx = scantx;
  static const unsigned thrcount = 2;
  // create threads for tx1 and tx2
  Thr* thrlist[2];
  int n;
  for (n = 0; n < thrcount; n++) {
    Thr& thr = *(thrlist[n] = new Thr(1 + n));
    CHK(thr.m_ret == 0);
  }
  // run the steps
  for (unsigned i = 0; i < wl1822_stepcount; i++) {
    DBG("step " << i << " start");
    for (n = 0; n < thrcount; n++) {
      Thr& thr = *thrlist[n];
      Runstep runstep = wl1822_step[i][n];
      if (runstep != 0)
        thr.start(runstep);
    }
    for (n = 0; n < thrcount; n++) {
      Thr& thr = *thrlist[n];
      Runstep runstep = wl1822_step[i][n];
      if (runstep != 0)
        thr.stopped();
    }
  }
  // delete threads
  for (n = 0; n < thrcount; n++) {
    Thr& thr = *thrlist[n];
    thr.exit();
    thr.join();
    delete &thr;
  }
  return 0;
}

NDB_COMMAND(testOdbcDriver, "testDeadlock", "testDeadlock", "testDeadlock", 65535)
{
  ndb_init();
  if (ndbout_mutex == NULL)
    ndbout_mutex= NdbMutex_Create();
  while (++argv, --argc > 0) {
    const char* arg = argv[0];
    if (strcmp(arg, "-scan") == 0) {
      if (++argv, --argc > 0) {
        g_opt.m_scan = strdup(argv[0]);
        continue;
      }
    }
    printusage();
    return NDBT_ProgramExit(NDBT_WRONGARGS);
  }

  Ndb_cluster_connection con;
  if(con.connect(12, 5, 1) != 0)
  {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  g_cluster_connection= &con;

  if (
      strchr(g_opt.m_scan, 't') != 0 && wl1822_main('t') == -1 ||
      strchr(g_opt.m_scan, 'x') != 0 && wl1822_main('x') == -1
      ) {
    return NDBT_ProgramExit(NDBT_FAILED);
  }
  return NDBT_ProgramExit(NDBT_OK);
}


#include <string>

#include "gmock/gmock.h"
#include "test/opt/assembly_builder.h"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

using CombineAccessChainsTest = PassTest<::testing::Test>;

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpAccessChain %ptr_Workgroup_uint %var %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromInBoundsAccessChainConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpInBoundsAccessChain %ptr_Workgroup_uint %var %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainCombineConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int2]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpAccessChain %ptr_Workgroup_uint %var %uint_1
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainNonConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[ld1:%\w+]] = OpLoad
; CHECK: [[ld2:%\w+]] = OpLoad
; CHECK: [[add:%\w+]] = OpIAdd [[int]] [[ld1]] [[ld2]]
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[add]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%local_var = OpVariable %ptr_Function_uint Function
%ld1 = OpLoad %uint %local_var
%gep = OpAccessChain %ptr_Workgroup_uint %var %ld1
%ld2 = OpLoad %uint %local_var
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld2
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainExtraIndices) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%uint_array_4_array_4_array_4 = OpTypeArray %uint_array_4_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%ptr_Workgroup_uint_array_4_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1 %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_2 %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       PtrAccessChainFromPtrAccessChainCombineElementOperand) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int6]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3 %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       PtrAccessChainFromPtrAccessChainOnlyElementOperand) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       PtrAccessChainFromPtrAccessCombineNonElementIndex) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int3]] [[int3]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3 %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3 %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       AccessChainFromPtrAccessChainOnlyElementOperand) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int3]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

{
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1 %uint_2
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, AccessChainFromAccessChainAppend) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%ptr_gep = OpAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_2
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NonConstantStructSlide) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[ld:%\w+]] = OpLoad
; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[ld]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%struct = OpTypeStruct %uint %uint
%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%wg_var = OpVariable %ptr_Workgroup_struct Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%1 = OpLabel
%func_var = OpVariable %ptr_Function_uint Function
%ld = OpLoad %uint %func_var
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_struct %wg_var %ld
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, DontCombineNonConstantStructSlide) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[ld:%\w+]] = OpLoad
; CHECK: [[gep:%\w+]] = OpAccessChain
; CHECK: OpPtrAccessChain {{%\w+}} [[gep]] [[ld]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_4 = OpConstant %uint 4
%struct = OpTypeStruct %uint %uint
%struct_array_4 = OpTypeArray %struct %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
%ptr_Workgroup_struct_array_4 = OpTypePointer Workgroup %struct_array_4
%wg_var = OpVariable %ptr_Workgroup_struct_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%1 = OpLabel
%func_var = OpVariable %ptr_Function_uint Function
%ld = OpLoad %uint %func_var
%gep = OpAccessChain %ptr_Workgroup_struct %wg_var %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, CombineNonConstantStructSlideElement) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[ld:%\w+]] = OpLoad
; CHECK: [[add:%\w+]] = OpIAdd {{%\w+}} [[ld]] [[ld]]
; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[add]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_4 = OpConstant %uint 4
%struct = OpTypeStruct %uint %uint
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
%wg_var = OpVariable %ptr_Workgroup_struct Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%1 = OpLabel
%func_var = OpVariable %ptr_Function_uint Function
%ld = OpLoad %uint %func_var
%gep = OpPtrAccessChain %ptr_Workgroup_struct %wg_var %ld
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromInBoundsPtrAccessChain) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, InBoundsPtrAccessChainFromPtrAccessChain) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       InBoundsPtrAccessChainFromInBoundsPtrAccessChain) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpInBoundsPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NoIndexAccessChains) {
  const std::string text = R"(
; CHECK: [[var:%\w+]] = OpVariable
; CHECK-NOT: OpConstant
; CHECK: [[gep:%\w+]] = OpAccessChain {{%\w+}} [[var]]
; CHECK: OpAccessChain {{%\w+}} [[var]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpAccessChain %ptr_Workgroup_uint %var
%gep2 = OpAccessChain %ptr_Workgroup_uint %gep1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NoIndexPtrAccessChains) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable
; CHECK: [[gep:%\w+]] = OpPtrAccessChain {{%\w+}} [[var]] [[int0]]
; CHECK: OpCopyObject {{%\w+}} [[gep]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpPtrAccessChain %ptr_Workgroup_uint %var %uint_0
%gep2 = OpAccessChain %ptr_Workgroup_uint %gep1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NoIndexPtrAccessChains2) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable
; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpAccessChain %ptr_Workgroup_uint %var
%gep2 = OpPtrAccessChain %ptr_Workgroup_uint %gep1 %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, CombineMixedSign) {
  const std::string text = R"(
; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
; CHECK: [[var:%\w+]] = OpVariable
; CHECK: [[uint2:%\w+]] = OpConstant [[uint]] 2
; CHECK: OpInBoundsPtrAccessChain {{%\w+}} [[var]] [[uint2]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%int = OpTypeInt 32 1
%uint_1 = OpConstant %uint 1
%int_1 = OpConstant %int 1
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpInBoundsPtrAccessChain %ptr_Workgroup_uint %var %uint_1
%gep2 = OpInBoundsPtrAccessChain %ptr_Workgroup_uint %gep1 %int_1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, FunctionDeclaration) {
  // Make sure the pass works with a function declaration that is called.
  const std::string text = R"(OpCapability Addresses
OpCapability Linkage
OpCapability Kernel
OpCapability Int8
%1 = OpExtInstImport "OpenCL.std"
OpMemoryModel Physical64 OpenCL
OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
OpExecutionMode %2 ContractionOff
OpSource Unknown 0
OpDecorate %3 LinkageAttributes "julia_error_7712" Import
%void = OpTypeVoid
%5 = OpTypeFunction %void
%3 = OpFunction %void None %5
OpFunctionEnd
%2 = OpFunction %void None %5
%6 = OpLabel
%7 = OpFunctionCall %void %3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<CombineAccessChains>(text, text, false);
}

}  // namespace
}  // namespace opt
}  // namespace spvtools


#include <string>

#include "gmock/gmock.h"
#include "test/opt/assembly_builder.h"
#include "test/opt/pass_fixture.h"
#include "test/opt/pass_utils.h"

namespace spvtools {
namespace opt {
namespace {

using CombineAccessChainsTest = PassTest<::testing::Test>;

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpAccessChain %ptr_Workgroup_uint %var %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromInBoundsAccessChainConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpInBoundsAccessChain %ptr_Workgroup_uint %var %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainCombineConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int2]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpAccessChain %ptr_Workgroup_uint %var %uint_1
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainNonConstant) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[ld1:%\w+]] = OpLoad
; CHECK: [[ld2:%\w+]] = OpLoad
; CHECK: [[add:%\w+]] = OpIAdd [[int]] [[ld1]] [[ld2]]
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[add]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%local_var = OpVariable %ptr_Function_uint Function
%ld1 = OpLoad %uint %local_var
%gep = OpAccessChain %ptr_Workgroup_uint %var %ld1
%ld2 = OpLoad %uint %local_var
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld2
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromAccessChainExtraIndices) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%uint_array_4_array_4_array_4 = OpTypeArray %uint_array_4_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%ptr_Workgroup_uint_array_4_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1 %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_2 %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       PtrAccessChainFromPtrAccessChainCombineElementOperand) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int6]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3 %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       PtrAccessChainFromPtrAccessChainOnlyElementOperand) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       PtrAccessChainFromPtrAccessCombineNonElementIndex) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int3]] [[int3]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3 %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %uint_3 %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       AccessChainFromPtrAccessChainOnlyElementOperand) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int3]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, AccessChainFromPtrAccessChainAppend) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: [[int3:%\w+]] = OpConstant [[int]] 3
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpPtrAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]] [[int3]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1 %uint_2
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, AccessChainFromAccessChainAppend) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int1:%\w+]] = OpConstant [[int]] 1
; CHECK: [[int2:%\w+]] = OpConstant [[int]] 2
; CHECK: [[ptr_int:%\w+]] = OpTypePointer Workgroup [[int]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: OpAccessChain [[ptr_int]] [[var]] [[int1]] [[int2]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%uint_2 = OpConstant %uint 2
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%uint_array_4_array_4 = OpTypeArray %uint_array_4 %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%ptr_Workgroup_uint_array_4_array_4 = OpTypePointer Workgroup %uint_array_4_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%ptr_gep = OpAccessChain %ptr_Workgroup_uint_array_4 %var %uint_1
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_2
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NonConstantStructSlide) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[ld:%\w+]] = OpLoad
; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[ld]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%struct = OpTypeStruct %uint %uint
%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%wg_var = OpVariable %ptr_Workgroup_struct Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%1 = OpLabel
%func_var = OpVariable %ptr_Function_uint Function
%ld = OpLoad %uint %func_var
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_struct %wg_var %ld
%gep = OpAccessChain %ptr_Workgroup_uint %ptr_gep %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAnd

}

TEST_F(CombineAccessChainsTest, DontCombineNonConstantStructSlide) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[ld:%\w+]] = OpLoad
; CHECK: [[gep:%\w+]] = OpAccessChain
; CHECK: OpPtrAccessChain {{%\w+}} [[gep]] [[ld]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_4 = OpConstant %uint 4
%struct = OpTypeStruct %uint %uint
%struct_array_4 = OpTypeArray %struct %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
%ptr_Workgroup_struct_array_4 = OpTypePointer Workgroup %struct_array_4
%wg_var = OpVariable %ptr_Workgroup_struct_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%1 = OpLabel
%func_var = OpVariable %ptr_Function_uint Function
%ld = OpLoad %uint %func_var
%gep = OpAccessChain %ptr_Workgroup_struct %wg_var %uint_0
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, CombineNonConstantStructSlideElement) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[ld:%\w+]] = OpLoad
; CHECK: [[add:%\w+]] = OpIAdd {{%\w+}} [[ld]] [[ld]]
; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[add]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_4 = OpConstant %uint 4
%struct = OpTypeStruct %uint %uint
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Function_uint = OpTypePointer Function %uint
%ptr_Workgroup_struct = OpTypePointer Workgroup %struct
%wg_var = OpVariable %ptr_Workgroup_struct Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%1 = OpLabel
%func_var = OpVariable %ptr_Function_uint Function
%ld = OpLoad %uint %func_var
%gep = OpPtrAccessChain %ptr_Workgroup_struct %wg_var %ld
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint %gep %ld %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, PtrAccessChainFromInBoundsPtrAccessChain) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, InBoundsPtrAccessChainFromPtrAccessChain) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest,
       InBoundsPtrAccessChainFromInBoundsPtrAccessChain) {
  const std::string text = R"(
; CHECK: [[int:%\w+]] = OpTypeInt 32 0
; CHECK: [[int4:%\w+]] = OpConstant [[int]] 4
; CHECK: [[array:%\w+]] = OpTypeArray [[int]] [[int4]]
; CHECK: [[ptr_array:%\w+]] = OpTypePointer Workgroup [[array]]
; CHECK: [[var:%\w+]] = OpVariable {{%\w+}} Workgroup
; CHECK: [[int6:%\w+]] = OpConstant [[int]] 6
; CHECK: OpInBoundsPtrAccessChain [[ptr_array]] [[var]] [[int6]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %main "main"
OpExecutionMode %main OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%uint_3 = OpConstant %uint 3
%uint_4 = OpConstant %uint 4
%uint_array_4 = OpTypeArray %uint %uint_4
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%ptr_Workgroup_uint_array_4 = OpTypePointer Workgroup %uint_array_4
%var = OpVariable %ptr_Workgroup_uint_array_4 Workgroup
%void_func = OpTypeFunction %void
%main = OpFunction %void None %void_func
%main_lab = OpLabel
%gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %var %uint_3
%ptr_gep = OpInBoundsPtrAccessChain %ptr_Workgroup_uint_array_4 %gep %uint_3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NoIndexAccessChains) {
  const std::string text = R"(
; CHECK: [[var:%\w+]] = OpVariable
; CHECK-NOT: OpConstant
; CHECK: [[gep:%\w+]] = OpAccessChain {{%\w+}} [[var]]
; CHECK: OpAccessChain {{%\w+}} [[var]]
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpAccessChain %ptr_Workgroup_uint %var
%gep2 = OpAccessChain %ptr_Workgroup_uint %gep1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NoIndexPtrAccessChains) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable
; CHECK: [[gep:%\w+]] = OpPtrAccessChain {{%\w+}} [[var]] [[int0]]
; CHECK: OpCopyObject {{%\w+}} [[gep]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpPtrAccessChain %ptr_Workgroup_uint %var %uint_0
%gep2 = OpAccessChain %ptr_Workgroup_uint %gep1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, NoIndexPtrAccessChains2) {
  const std::string text = R"(
; CHECK: [[int0:%\w+]] = OpConstant {{%\w+}} 0
; CHECK: [[var:%\w+]] = OpVariable
; CHECK: OpPtrAccessChain {{%\w+}} [[var]] [[int0]]
OpCapability Shader
OpCapability VariablePointers
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%uint_0 = OpConstant %uint 0
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpAccessChain %ptr_Workgroup_uint %var
%gep2 = OpPtrAccessChain %ptr_Workgroup_uint %gep1 %uint_0
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, CombineMixedSign) {
  const std::string text = R"(
; CHECK: [[uint:%\w+]] = OpTypeInt 32 0
; CHECK: [[var:%\w+]] = OpVariable
; CHECK: [[uint2:%\w+]] = OpConstant [[uint]] 2
; CHECK: OpInBoundsPtrAccessChain {{%\w+}} [[var]] [[uint2]]
OpCapability Shader
OpCapability VariablePointers
OpCapability Addresses
OpExtension "SPV_KHR_variable_pointers"
OpMemoryModel Logical GLSL450
OpEntryPoint Fragment %func "func"
OpExecutionMode %func OriginUpperLeft
%void = OpTypeVoid
%uint = OpTypeInt 32 0
%int = OpTypeInt 32 1
%uint_1 = OpConstant %uint 1
%int_1 = OpConstant %int 1
%ptr_Workgroup_uint = OpTypePointer Workgroup %uint
%var = OpVariable %ptr_Workgroup_uint Workgroup
%void_func = OpTypeFunction %void
%func = OpFunction %void None %void_func
%1 = OpLabel
%gep1 = OpInBoundsPtrAccessChain %ptr_Workgroup_uint %var %uint_1
%gep2 = OpInBoundsPtrAccessChain %ptr_Workgroup_uint %gep1 %int_1
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndMatch<CombineAccessChains>(text, true);
}

TEST_F(CombineAccessChainsTest, FunctionDeclaration) {
  // Make sure the pass works with a function declaration that is called.
  const std::string text = R"(OpCapability Addresses
OpCapability Linkage
OpCapability Kernel
OpCapability Int8
%1 = OpExtInstImport "OpenCL.std"
OpMemoryModel Physical64 OpenCL
OpEntryPoint Kernel %2 "_Z23julia__1166_kernel_77094Bool"
OpExecutionMode %2 ContractionOff
OpSource Unknown 0
OpDecorate %3 LinkageAttributes "julia_error_7712" Import
%void = OpTypeVoid
%5 = OpTypeFunction %void
%3 = OpFunction %void None %5
OpFunctionEnd
%2 = OpFunction %void None %5
%6 = OpLabel
%7 = OpFunctionCall %void %3
OpReturn
OpFunctionEnd
)";

  SinglePassRunAndCheck<CombineAccessChains>(text, text, false);
}

}  // namespace
}  // namespace opt
}  // namespace spvtools


ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)

// O(n) space with hashing
#include <unordered_set>
using std::unordered_set;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:

{
        unordered_set<ListNode*> us;
        ListNode *ptr = headA;
        while (ptr != NULL) {
            us.insert(ptr);
            ptr = ptr->next;
        }
        ptr = headB;
        while (ptr != NULL) {
            if (us.find(ptr) != us.end()) {
                break;
            }
            us.insert(ptr);
            ptr = ptr->next;
        }
        us.clear();
        return ptr;
    }
};


{
        unordered_set<ListNode*> us;
        ListNode *ptr = headA;
        while (ptr != NULL) {
            us.insert(ptr);
            ptr = ptr->next;
        }
        ptr = headB;
        while (ptr != NULL) {
            if (us.find(ptr) != us.end()) {
                break;
            }
            us.insert(ptr);
            ptr = ptr->next;
        }
        us.clear();
        return ptr;
    }

// O(n) space with hashing
#include <unordered_set>
using std::unordered_set;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)


// O(n) space with hashing
#include <unordered_set>
using std::unordered_set;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> us;
        ListNode *pt

        while (ptr != NULL) {
            us.insert(ptr);
            ptr = ptr->next;
        }
        ptr = headB;
        while (ptr != NULL) {
            if (us.find(ptr) != us.end()) {
                break;
            }
            us.insert(ptr);
            ptr = ptr->next;
        }
        us.clear();
        return ptr;
    }
};


if (us.find(ptr) != us.end()) {
                break;
            }

// O(n) space with hashing
#include <unordered_set>
using std::unordered_set;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> us;
        ListNode *ptr = headA;
        while (ptr != NULL) {
            us.insert(ptr);
            ptr = ptr->next;
        }
        ptr = headB;
        while (ptr != NULL) {

            us.insert(ptr);
            ptr = ptr->next;
        }
        us.clear();
        return ptr;
    }
};


while (ptr != NULL) {
            if (us.find(ptr) != us.end()) {
                break;
            }
            us.insert(ptr);
            ptr = ptr->next;
        }

// O(n) space with hashing
#include <unordered_set>
using std::unordered_set;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        unordered_set<ListNode*> us;
        ListNode *ptr = headA;
        while (ptr != NULL) {
            us.insert(ptr);
            ptr = ptr->next;
        }
        ptr = headB;

        us.clear();
        return ptr;
    }
};


/**
 */

#include <aws/kinesis-video-signaling/model/SendAlexaOfferToMasterResult.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/core/AmazonWebServiceResult.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/UnreferencedParam.h>

#include <utility>

using namespace Aws::KinesisVideoSignalingChannels::Model;
using namespace Aws::Utils::Json;
using namespace Aws::Utils;
using namespace Aws;

{
}

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  *this = result;
}

SendAlexaOfferToMasterResult& SendAlexaOfferToMasterResult::operator =(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  JsonView jsonValue = result.GetPayload().View();
  if(jsonValue.ValueExists("Answer"))
  {
    m_answer = jsonValue.GetString("Answer");

  }

  return *this;
}


/**
 */

#include <aws/kinesis-video-signaling/model/SendAlexaOfferToMasterResult.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/core/AmazonWebServiceResult.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/UnreferencedParam.h>

#include <utility>

using namespace Aws::KinesisVideoSignalingChannels::Model;
using namespace Aws::Utils::Json;
using namespace Aws::Utils;
using namespace Aws;

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult()

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  *this = result;
}

SendAlexaOfferToMasterResult& SendAlexaOfferToMasterResult::operator =(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  JsonView jsonValue = result.GetPayload().View();
  if(jsonValue.ValueExists("Answer"))
  {
    m_answer = jsonValue.GetString("Answer");

  }

  return *this;
}


/**
 */

#include <aws/kinesis-video-signaling/model/SendAlexaOfferToMasterResult.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/core/AmazonWebServiceResult.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/UnreferencedParam.h>

#include <utility>

using namespace Aws::KinesisVideoSignalingChannels::Model;
using namespace Aws::Utils::Json;
using namespace Aws::Utils;
using namespace Aws;

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult()
{
}

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  *this

}

SendAlexaOfferToMasterResult& SendAlexaOfferToMasterResult::operator =(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  JsonView jsonValue = result.GetPayload().View();
  if(jsonValue.ValueExists("Answer"))
  {
    m_answer = jsonValue.GetString("Answer");

  }

  return *this;
}


if(jsonValue.ValueExists("Answer"))
  {
    m_answer = jsonValue.GetString("Answer");

  }

/**
 */

#include <aws/kinesis-video-signaling/model/SendAlexaOfferToMasterResult.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <aws/core/AmazonWebServiceResult.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/UnreferencedParam.h>

#include <utility>

using namespace Aws::KinesisVideoSignalingChannels::Model;
using namespace Aws::Utils::Json;
using namespace Aws::Utils;
using namespace Aws;

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult()
{
}

SendAlexaOfferToMasterResult::SendAlexaOfferToMasterResult(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  *this = result;
}

SendAlexaOfferToMasterResult& SendAlexaOfferToMasterResult::operator =(const Aws::AmazonWebServiceResult<JsonValue>& result)
{
  JsonView jsonValue = result.GetPayload().View();