Initial backend compiler for RSoV

Bug: 30964317

This compiler compiles RenderScript LLVM bitcode into SPIR-V.

This was done by Jakub Kuderski as part of his 2016 summer internship
at Google.

Test: build

Change-Id: I395cdab2b97451b9e0a9b866af2d112ead73ab72
diff --git a/rsov/Android.mk b/rsov/Android.mk
new file mode 100644
index 0000000..2991304
--- /dev/null
+++ b/rsov/Android.mk
@@ -0,0 +1,8 @@
+#=====================================================================
+# Include Subdirectories
+#=====================================================================
+
+LOCAL_PATH:=$(call my-dir)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/rsov/compiler/Android.mk b/rsov/compiler/Android.mk
new file mode 100644
index 0000000..7f31d67
--- /dev/null
+++ b/rsov/compiler/Android.mk
@@ -0,0 +1,139 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+LLVM_ROOT_PATH := external/llvm
+LIBBCC_ROOT_PATH := frameworks/compile/libbcc
+LIBSPIRV_ROOT_PATH := external/spirv-llvm/lib/SPIRV
+
+FORCE_RS2SPIRV_DEBUG_BUILD ?= false
+RS2SPRIV_DEVICE_BUILD ?= true
+
+RS2SPIRV_SOURCES := \
+  rs2spirv.cpp \
+  GlobalMergePass.cpp \
+  InlinePreparationPass.cpp \
+  LinkerModule.cpp \
+  ReflectionPass.cpp \
+  RSAllocationUtils.cpp \
+  RSSPIRVWriter.cpp \
+  unit_tests/LinkerModuleTests.cpp
+
+RS2SPIRV_INCLUDES := \
+  $(LIBSPIRV_ROOT_PATH) \
+  $(LIBSPIRV_ROOT_PATH)/Mangler \
+  $(LIBSPIRV_ROOT_PATH)/libSPIRV \
+  $(LIBBCC_ROOT_PATH)/include \
+  $(LLVM_ROOT_PATH)/include \
+  $(LLVM_ROOT_PATH)/host/include
+
+#=====================================================================
+# Host Executable rs2spirv
+#=====================================================================
+
+# Don't build for unbundled branches
+ifeq (,$(TARGET_BUILD_APPS))
+
+include $(CLEAR_VARS)
+include $(CLEAR_TBLGEN_VARS)
+
+LOCAL_SRC_FILES := \
+  $(RS2SPIRV_SOURCES)
+
+LOCAL_C_INCLUDES := \
+  $(RS2SPIRV_INCLUDES)
+
+LOCAL_MODULE := rs2spirv
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+# TODO: handle windows and darwin
+
+LOCAL_MODULE_HOST_OS := linux
+LOCAL_IS_HOST_MODULE := true
+
+LOCAL_SHARED_LIBRARIES_linux += libLLVM libbcinfo libSPIRV
+
+# TODO: fix the remaining warnings
+
+LOCAL_CFLAGS += $(TOOL_CFLAGS) \
+  -D_SPIRV_LLVM_API \
+  -Wno-error=pessimizing-move \
+  -Wno-error=unused-variable \
+  -Wno-error=unused-private-field \
+  -Wno-error=unused-function \
+  -Wno-error=dangling-else \
+  -Wno-error=ignored-qualifiers \
+  -Wno-error=non-virtual-dtor
+
+ifeq (true, $(FORCE_RS2SPIRV_DEBUG_BUILD))
+  LOCAL_CFLAGS += -O0 -DRS2SPIRV_DEBUG=1
+endif
+
+include $(LLVM_ROOT_PATH)/llvm.mk
+include $(LLVM_GEN_INTRINSICS_MK)
+include $(LLVM_GEN_ATTRIBUTES_MK)
+include $(LLVM_HOST_BUILD_MK)
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # Don't build in unbundled branches
+
+#=====================================================================
+# Device Executable rs2spirv
+#=====================================================================
+
+ifneq (true,$(RS2SPRIV_DEVICE_BUILD)))
+
+include $(CLEAR_VARS)
+include $(CLEAR_TBLGEN_VARS)
+
+LOCAL_SRC_FILES := \
+  $(RS2SPIRV_SOURCES)
+
+LOCAL_C_INCLUDES := \
+  $(RS2SPIRV_INCLUDES)
+
+LOCAL_MODULE := rs2spirv
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+LOCAL_SHARED_LIBRARIES += libLLVM libbcinfo libSPIRV
+
+LOCAL_CFLAGS += $(TOOL_CFLAGS) \
+  -D_SPIRV_LLVM_API \
+  -Wno-error=pessimizing-move \
+  -Wno-error=unused-variable \
+  -Wno-error=unused-private-field \
+  -Wno-error=unused-function \
+  -Wno-error=dangling-else \
+  -Wno-error=ignored-qualifiers \
+  -Wno-error=non-virtual-dtor \
+
+ifeq (true, $(FORCE_RS2SPIRV_DEBUG_BUILD))
+  LOCAL_CFLAGS += -O0 -DRS2SPIRV_DEBUG=1
+endif
+
+include $(LLVM_GEN_INTRINSICS_MK)
+include $(LLVM_GEN_ATTRIBUTES_MK)
+include $(LLVM_DEVICE_BUILD_MK)
+include $(BUILD_EXECUTABLE)
+
+endif # Don't build in unbundled branches
+
+#=====================================================================
+# Include Subdirectories
+#=====================================================================
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/rsov/compiler/GlobalMergePass.cpp b/rsov/compiler/GlobalMergePass.cpp
new file mode 100644
index 0000000..e177de8
--- /dev/null
+++ b/rsov/compiler/GlobalMergePass.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "GlobalMergePass.h"
+
+#include "RSAllocationUtils.h"
+#include "llvm/IR/Attributes.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DataLayout.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/Debug.h"
+
+#define DEBUG_TYPE "rs2spirv-global-merge"
+
+using namespace llvm;
+
+namespace rs2spirv {
+
+namespace {
+
+class GlobalMergePass : public ModulePass {
+public:
+  static char ID;
+  GlobalMergePass() : ModulePass(ID) {}
+  const char *getPassName() const override { return "GlobalMergePass"; }
+
+  bool runOnModule(Module &M) override {
+    DEBUG(dbgs() << "RS2SPIRVGlobalMergePass\n");
+    DEBUG(M.dump());
+
+    const auto &DL = M.getDataLayout();
+    SmallVector<GlobalVariable *, 8> Globals;
+    const bool CollectRes = collectGlobals(M, Globals);
+    if (!CollectRes)
+      return false; // Module not modified.
+
+    IntegerType *Int32Ty = Type::getInt32Ty(M.getContext());
+    uint64_t MergedSize = 0;
+    SmallVector<Type *, 8> Tys;
+    Tys.reserve(Globals.size());
+
+    for (auto *GV : Globals) {
+      auto *Ty = GV->getValueType();
+      MergedSize += DL.getTypeAllocSize(Ty);
+      Tys.push_back(Ty);
+    }
+
+    auto *MergedTy = StructType::create(M.getContext(), "struct.__GPUBuffer");
+    MergedTy->setBody(Tys, false);
+    DEBUG(MergedTy->dump());
+    auto *MergedGV =
+        new GlobalVariable(M, MergedTy, false, GlobalValue::ExternalLinkage,
+                           nullptr, "__GPUBlock");
+    MergedGV->setInitializer(nullptr); // TODO: Emit initializers for CPU code.
+
+    Value *Idx[2] = {ConstantInt::get(Int32Ty, 0), nullptr};
+
+    for (size_t i = 0, e = Globals.size(); i != e; ++i) {
+      auto *G = Globals[i];
+      Idx[1] = ConstantInt::get(Int32Ty, i);
+
+      // Keep users in a vector - they get implicitly removed
+      // in the loop below, which would invalidate users() iterators.
+      std::vector<User *> Users(G->user_begin(), G->user_end());
+      for (auto *User : Users) {
+        DEBUG(dbgs() << "User: ");
+        DEBUG(User->dump());
+        auto *Inst = dyn_cast<Instruction>(User);
+
+        // TODO: Consider what should actually happen. Global variables can
+        // appear in ConstantExprs, but this case requires fixing the LLVM-SPIRV
+        // converter, which currently emits ill-formed SPIR-V code.
+        if (!Inst) {
+          errs() << "Found a global variable user that is not an Instruction\n";
+          assert(false);
+          return true; // Module may have been modified.
+        }
+
+        auto *GEP = GetElementPtrInst::CreateInBounds(MergedTy, MergedGV, Idx,
+                                                      "gpu_gep", Inst);
+        for (unsigned k = 0, k_e = User->getNumOperands(); k != k_e; ++k)
+          if (User->getOperand(k) == G)
+            User->setOperand(k, GEP);
+      }
+
+      // TODO: Investigate emitting a GlobalAlias for each global variable.
+      G->eraseFromParent();
+    }
+
+    // Return true, as the pass modifies module.
+    return true;
+  }
+
+private:
+  bool collectGlobals(Module &M, SmallVectorImpl<GlobalVariable *> &Globals) {
+    for (auto &GV : M.globals()) {
+      // TODO: Rethink what should happen with global statics.
+      if (GV.isDeclaration() || GV.isThreadLocal() || GV.hasSection())
+        continue;
+
+      if (isRSAllocation(GV))
+        continue;
+
+      DEBUG(GV.dump());
+      auto *PT = cast<PointerType>(GV.getType());
+
+      const unsigned AddressSpace = PT->getAddressSpace();
+      if (AddressSpace != 0) {
+        errs() << "Unknown address space! (" << AddressSpace
+               << ")\nGlobalMergePass failed!\n";
+        return false;
+      }
+
+      Globals.push_back(&GV);
+    }
+
+    return !Globals.empty();
+  }
+};
+}
+
+char GlobalMergePass::ID = 0;
+
+ModulePass *createGlobalMergePass() { return new GlobalMergePass(); }
+
+} // namespace rs2spirv
diff --git a/rsov/compiler/GlobalMergePass.h b/rsov/compiler/GlobalMergePass.h
new file mode 100644
index 0000000..c402e59
--- /dev/null
+++ b/rsov/compiler/GlobalMergePass.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS2SPIRV_GLOBAL_MERGE_PASS_H
+#define RS2SPIRV_GLOBAL_MERGE_PASS_H
+
+namespace llvm {
+class ModulePass;
+}
+
+namespace rs2spirv {
+
+llvm::ModulePass *createGlobalMergePass();
+
+} // namespace rs2spirv
+
+#endif
diff --git a/rsov/compiler/InlinePreparationPass.cpp b/rsov/compiler/InlinePreparationPass.cpp
new file mode 100644
index 0000000..9e016fa
--- /dev/null
+++ b/rsov/compiler/InlinePreparationPass.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "InlinePreparationPass.h"
+
+#include "bcinfo/MetadataExtractor.h"
+
+#include "llvm/ADT/StringSet.h"
+#include "llvm/IR/Attributes.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/Debug.h"
+
+#define DEBUG_TYPE "rs2spirv-inline"
+
+using namespace llvm;
+
+namespace rs2spirv {
+
+namespace {
+
+class InlinePreparationPass : public ModulePass {
+  bcinfo::MetadataExtractor &ME;
+
+public:
+  static char ID;
+  explicit InlinePreparationPass(bcinfo::MetadataExtractor &Extractor)
+      : ModulePass(ID), ME(Extractor) {}
+
+  const char *getPassName() const override { return "InlinePreparationPass"; }
+
+  bool runOnModule(Module &M) override {
+    DEBUG(dbgs() << "InlinePreparationPass\n");
+
+    const size_t RSKernelNum = ME.getExportForEachSignatureCount();
+    const char **RSKernelNames = ME.getExportForEachNameList();
+    if (RSKernelNum == 0)
+      DEBUG(dbgs() << "InlinePreparationPass detected no kernel\n");
+
+    StringSet<> KNames;
+    for (size_t i = 0; i < RSKernelNum; ++i)
+      KNames.insert(RSKernelNames[i]);
+
+    for (auto &F : M.functions()) {
+      if (F.isDeclaration())
+        continue;
+
+      const auto FName = F.getName();
+
+      // TODO: Consider inlining kernels (i.e. kernels calling other kernels)
+      // when multi-kernel module support is ready.
+      if (KNames.count(FName) != 0)
+        continue; // Skip kernels.
+
+      F.addFnAttr(Attribute::AlwaysInline);
+      F.setLinkage(GlobalValue::InternalLinkage);
+      DEBUG(dbgs() << "Marked as alwaysinline:\t" << FName << '\n');
+    }
+
+    // Return true, as the pass modifies module.
+    return true;
+  }
+};
+}
+
+char InlinePreparationPass::ID = 0;
+
+ModulePass *createInlinePreparationPass(bcinfo::MetadataExtractor &ME) {
+  return new InlinePreparationPass(ME);
+}
+
+} // namespace rs2spirv
diff --git a/rsov/compiler/InlinePreparationPass.h b/rsov/compiler/InlinePreparationPass.h
new file mode 100644
index 0000000..28f2a74
--- /dev/null
+++ b/rsov/compiler/InlinePreparationPass.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS2SPIRV_INLINE_PREPARATION_PASS_H
+#define RS2SPIRV_INLINE_PREPARATION_PASS_H
+
+namespace llvm {
+class ModulePass;
+}
+
+namespace bcinfo {
+class MetadataExtractor;
+}
+
+namespace rs2spirv {
+
+llvm::ModulePass *
+createInlinePreparationPass(bcinfo::MetadataExtractor &Extractor);
+
+} // namespace rs2spirv
+
+#endif
diff --git a/rsov/compiler/LinkerModule.cpp b/rsov/compiler/LinkerModule.cpp
new file mode 100644
index 0000000..438af09
--- /dev/null
+++ b/rsov/compiler/LinkerModule.cpp
@@ -0,0 +1,470 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "LinkerModule.h"
+
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <fstream>
+#include <sstream>
+
+#define DEBUG_TYPE "rs2spirv-module"
+
+using namespace llvm;
+
+namespace rs2spirv {
+
+bool SPIRVLine::hasCode() const {
+  StringRef S(Line);
+  S = S.trim();
+  if (S.empty())
+    return false;
+  if (S[0] == ';')
+    return false;
+
+  return true;
+}
+
+static Optional<StringRef> GetFirstId(StringRef S, size_t StartPos,
+                                      size_t &EndPos) {
+  size_t Pos = S.find('%', StartPos);
+  if (Pos == StringRef::npos) {
+    return None;
+  }
+
+  const auto PosB = Pos;
+  while (++Pos < S.size() && isspace(S[Pos]) == 0)
+    ;
+
+  EndPos = Pos;
+  return StringRef(S.data() + PosB, EndPos - PosB);
+}
+
+void SPIRVLine::getIdentifiers(SmallVectorImpl<StringRef> &Out,
+                               size_t StartPos) const {
+  const StringRef S(Line);
+
+  size_t Pos = StartPos;
+  Optional<StringRef> Res;
+  while ((Res = GetFirstId(S, Pos, Pos)))
+    Out.push_back(*Res);
+}
+
+Optional<StringRef> SPIRVLine::getLHSIdentifier() const {
+  size_t EndPos;
+  const auto Id = GetFirstId(Line, 0, EndPos);
+  if (!Id)
+    return None;
+
+  if (!contains("="))
+    return None;
+
+  return *Id;
+}
+
+Optional<StringRef> SPIRVLine::getRHS() const {
+  const auto EqPos = Line.find('=', 0);
+  if (EqPos == std::string::npos)
+    return None;
+
+  return StringRef(Line.c_str() + EqPos + 1).trim();
+}
+
+void SPIRVLine::getRHSIdentifiers(SmallVectorImpl<StringRef> &Out) const {
+  const auto RHS = getRHS();
+  if (!RHS)
+    return;
+
+  size_t Pos = 0;
+  Optional<StringRef> Res;
+  while ((Res = GetFirstId(*RHS, Pos, Pos)))
+    Out.push_back(*Res);
+}
+
+bool SPIRVLine::replaceStr(StringRef Original, StringRef New) {
+  const size_t Pos = StringRef(Line).find(Original);
+  if (Pos == StringRef::npos)
+    return false;
+
+  Line.replace(Pos, Original.size(), New.str());
+  return true;
+}
+
+bool SPIRVLine::replaceId(StringRef Original, StringRef New) {
+  size_t Pos = StringRef(Line).find(Original, 0);
+  if (Pos == StringRef::npos)
+    return false;
+
+  const auto OneAfter = Pos + Original.size();
+  if (OneAfter < Line.size() && isspace(Line[OneAfter]) == 0) {
+    Pos = StringRef(Line).find(Original, OneAfter);
+    if (Pos == StringRef::npos)
+      return false;
+  }
+
+  Line.replace(Pos, Original.size(), New.str());
+  return true;
+}
+
+bool SPIRVLine::contains(StringRef S) const {
+  return StringRef(Line).find(S, 0) != StringRef::npos;
+}
+
+void SPIRVLine::markAsEmpty() { Line = "; <<empty>>"; }
+
+Block &Block::operator=(const Block &B) {
+  assert(Kind == B.Kind);
+  assert(Name == B.Name);
+  Lines = B.Lines;
+
+  return *this;
+}
+
+bool Block::addLine(SPIRVLine L, bool trim) {
+  if (trim)
+    L.trim();
+
+  Lines.emplace_back(std::move(L));
+  return true;
+}
+
+SPIRVLine &Block::getLastLine() {
+  assert(!Lines.empty());
+  return Lines.back();
+}
+
+const SPIRVLine &Block::getLastLine() const {
+  assert(!Lines.empty());
+  return Lines.back();
+}
+
+void Block::appendToStream(std::ostream &OS) const {
+  for (const auto &L : Lines)
+    OS << L.str() << '\n';
+}
+
+void Block::dump() const {
+  dbgs() << "\n" << Name << "Block: {\n\n";
+  for (const auto &L : Lines) {
+    if (L.hasCode())
+      dbgs() << '\t';
+    dbgs() << L.str() << '\n';
+  }
+  dbgs() << "\n} (" << Name << "Block)\n\n";
+}
+
+void Block::replaceAllIds(StringRef Old, StringRef New) {
+  for (auto &L : Lines)
+    while (L.replaceId(Old, New))
+      ;
+}
+
+bool Block::hasCode() const {
+  return std::any_of(Lines.begin(), Lines.end(),
+                     [](const SPIRVLine &L) { return L.hasCode(); });
+}
+
+size_t Block::getIdCount(StringRef Id) const {
+  size_t Res = 0;
+  for (const auto &L : Lines) {
+    SmallVector<StringRef, 4> Ids;
+    L.getIdentifiers(Ids);
+    Res += std::count(Ids.begin(), Ids.end(), Id);
+  }
+
+  return Res;
+}
+
+void Block::removeNonCodeLines() {
+  Lines.erase(std::remove_if(Lines.begin(), Lines.end(),
+                             [](const SPIRVLine &L) { return !L.hasCode(); }),
+              Lines.end());
+}
+
+bool HeaderBlock::getRSKernelNames(SmallVectorImpl<StringRef> &Out) const {
+  for (const auto &L : Lines)
+    if (L.contains("OpString")) {
+      const Optional<StringRef> Name = L.getLHSIdentifier();
+      if (Name && *Name == "%RS_KERNELS") {
+        auto LStr = L.str();
+        LStr.erase(std::remove(LStr.begin(), LStr.end(), '"'), LStr.end());
+
+        SPIRVLine(LStr).getRHSIdentifiers(Out);
+        return true;
+      }
+    }
+
+  return false;
+}
+
+StringRef FunctionBlock::getFunctionName() const {
+  assert(!Lines.empty());
+  assert(Lines.front().contains("OpFunction"));
+
+  Optional<StringRef> Name = Lines.front().getLHSIdentifier();
+  assert(Name);
+  return *Name;
+}
+
+size_t FunctionBlock::getArity() const {
+  size_t A = 0;
+  for (const auto &L : Lines)
+    if (L.contains("OpFunctionParameter"))
+      ++A;
+
+  return A;
+}
+
+void FunctionBlock::getArgNames(SmallVectorImpl<StringRef> &Out) const {
+  for (const auto &L : Lines)
+    if (L.contains("OpFunctionParameter")) {
+      Optional<StringRef> Id = L.getLHSIdentifier();
+      assert(Id);
+      Out.push_back(*Id);
+    }
+}
+
+Optional<StringRef> FunctionBlock::getRetValName() const {
+  for (const auto &L : Lines)
+    if (L.contains("OpReturnValue")) {
+      SmallVector<StringRef, 1> Id;
+      L.getIdentifiers(Id);
+      assert(Id.size() == 1);
+      return Id.front();
+    }
+
+  return None;
+}
+
+iterator_range<Block::const_line_iter> FunctionBlock::body() const {
+  auto It = Lines.begin();
+  const auto End = Lines.end();
+
+  while (It != End && !It->contains("OpLabel"))
+    ++It;
+
+  assert(It != End);
+
+  ++It;
+  const auto BBegin = It;
+
+  while (It != End && !It->contains("OpReturn"))
+    ++It;
+
+  assert(It != End);
+
+  return make_range(BBegin, It);
+}
+
+void FunctionBlock::getCalledFunctions(
+    llvm::SmallVectorImpl<llvm::StringRef> &Out) const {
+  for (const auto &L : Lines)
+    if (L.contains("OpFunctionCall")) {
+      SmallVector<StringRef, 4> Ids;
+      L.getRHSIdentifiers(Ids);
+      assert(Ids.size() >= 2);
+
+      Out.push_back(Ids[1]);
+    }
+}
+
+bool FunctionBlock::hasFunctionCalls() const {
+  SmallVector<StringRef, 4> Callees;
+  getCalledFunctions(Callees);
+  return !Callees.empty();
+}
+
+bool FunctionBlock::isDirectlyRecursive() const {
+  SmallVector<StringRef, 4> Callees;
+  getCalledFunctions(Callees);
+
+  const auto FName = getFunctionName();
+  return std::find(Callees.begin(), Callees.end(), FName) != Callees.end();
+}
+
+bool FunctionBlock::isReturnTypeVoid() const {
+  assert(Lines.size() >= 4);
+  // At least 4 lines: OpFunction, OpLabel, OpReturn, OpFunctionEnd.
+
+  SmallVector<StringRef, 2> Ids;
+  Lines.front().getRHSIdentifiers(Ids);
+  assert(Ids.size() == 2);
+
+  if (Ids.front() != "%void" && Ids.front() != "%rs_linker_void")
+    return false;
+
+  SPIRVLine SecondLast = Lines[Lines.size() - 2];
+  SecondLast.trim();
+  return SecondLast.str() == "OpReturn";
+}
+
+LinkerModule::LinkerModule(std::istream &ModuleIn) {
+  std::string Temp;
+  std::vector<SPIRVLine> Ls;
+  while (std::getline(ModuleIn, Temp))
+    Ls.push_back(StringRef(Temp));
+
+  auto It = Ls.begin();
+  const auto End = Ls.end();
+
+  {
+    auto &HeaderBlck = addBlock<HeaderBlock>();
+    while (It != End && !It->contains("OpDecorate"))
+      HeaderBlck.addLine(*(It++));
+  }
+
+  {
+    auto &DcrBlck = addBlock<DecorBlock>();
+    while (It != End && !It->contains("OpType"))
+      DcrBlck.addLine(*(It++));
+
+    DcrBlck.removeNonCodeLines();
+  }
+
+  {
+    auto &TypeAndConstBlck = addBlock<TypeAndConstBlock>();
+    auto &VarBlck = addBlock<VarBlock>();
+
+    while (It != End && !It->contains("OpFunction")) {
+      if (!It->hasCode()) {
+        ++It;
+        continue;
+      }
+
+      if (It->contains("OpType") || It->contains("OpConstant")) {
+        TypeAndConstBlck.addLine(*It);
+      } else {
+        VarBlck.addLine(*It);
+      }
+
+      ++It;
+    }
+
+    TypeAndConstBlck.removeNonCodeLines();
+    VarBlck.removeNonCodeLines();
+  }
+
+  while (It != End) {
+    // Consume empty lines between blocks.
+    if (It->empty()) {
+      ++It;
+      continue;
+    }
+
+    Optional<StringRef> Id = It->getLHSIdentifier();
+    assert(Id && "Functions should start with OpFunction");
+
+    FunctionBlock &FunBlck =
+        *Id == "%main" ? addBlock<MainFunBlock>() : addBlock<FunctionBlock>();
+    bool HasReturn = false;
+
+    while (It != End) {
+      if (It->empty()) {
+        ++It;
+        continue;
+      }
+      HasReturn |= It->contains("OpReturn");
+
+      FunBlck.addLine(*(It++));
+      if (FunBlck.getLastLine().contains("OpFunctionEnd"))
+        break;
+    }
+
+    FunBlck.removeNonCodeLines();
+
+    if (!HasReturn) {
+      FunDeclBlock FunDeclBlck;
+      for (auto &L : FunBlck.lines())
+        FunDeclBlck.addLine(std::move(L));
+
+      Blocks.pop_back();
+      addBlock<FunDeclBlock>(std::move(FunDeclBlck));
+    }
+  }
+
+  removeNonCode();
+}
+
+void LinkerModule::fixBlockOrder() {
+  std::stable_sort(Blocks.begin(), Blocks.end(),
+                   [](const block_ptr &LHS, const block_ptr &RHS) {
+                     return LHS->getKind() < RHS->getKind();
+                   });
+}
+
+bool LinkerModule::saveToFile(StringRef FName) const {
+  std::ofstream Out(FName, std::ios::trunc);
+  if (!Out.good())
+    return false;
+
+  for (const auto &BPtr : blocks()) {
+    if (!isa<HeaderBlock>(BPtr.get()))
+      Out << "\n\n; " << BPtr->Name.str() << "\n\n";
+
+    for (const auto &L : BPtr->lines()) {
+      if (L.hasCode())
+        Out << "\t";
+      Out << L.str() << '\n';
+    }
+  }
+
+  return true;
+}
+
+void LinkerModule::removeEmptyBlocks() {
+  removeBlocksIf([](const Block &B) { return B.empty(); });
+}
+
+void LinkerModule::removeNonCode() {
+  for (auto &BPtr : Blocks)
+    if (!isa<HeaderBlock>(BPtr.get()))
+      BPtr->removeNonCodeLines();
+
+  removeBlocksIf([](const Block &B) { return !B.hasCode(); });
+}
+
+void LinkerModule::removeUnusedFunctions() {
+  std::vector<std::string> UsedFunctions;
+
+  assert(Blocks.size());
+
+  const auto &MB = getLastBlock<MainFunBlock>();
+  for (const auto &L : MB.lines())
+    if (L.contains("OpFunctionCall")) {
+      SmallVector<StringRef, 4> Ids;
+      L.getRHSIdentifiers(Ids);
+      assert(Ids.size() >= 2);
+
+      const auto &FName = Ids[1];
+      UsedFunctions.push_back(FName.str());
+    }
+
+  removeBlocksIf([&UsedFunctions](const Block &B) {
+    const auto *FunBlck = dyn_cast<FunctionBlock>(&B);
+    if (!FunBlck)
+      return false;
+
+    if (isa<MainFunBlock>(FunBlck))
+      return false;
+
+    const auto FName = FunBlck->getFunctionName().str();
+    return std::find(UsedFunctions.begin(), UsedFunctions.end(), FName) ==
+           UsedFunctions.end();
+  });
+}
+
+} // namespace rs2spirv
diff --git a/rsov/compiler/LinkerModule.h b/rsov/compiler/LinkerModule.h
new file mode 100644
index 0000000..ed29153
--- /dev/null
+++ b/rsov/compiler/LinkerModule.h
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS_LINKER_MODULE_H
+#define RS_LINKER_MODULE_H
+
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ADT/iterator_range.h"
+#include "llvm/Support/Casting.h"
+
+#include <algorithm>
+#include <cassert>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+namespace rs2spirv {
+
+class SPIRVLine {
+  std::string Line;
+
+public:
+  SPIRVLine(llvm::StringRef L) : Line(L.str()) {}
+
+  std::string &str() { return Line; }
+  const std::string &str() const { return Line; }
+
+  void trim() { Line = llvm::StringRef(Line).trim().str(); }
+
+  bool empty() const { return Line.empty(); }
+  bool hasCode() const;
+
+  void getIdentifiers(llvm::SmallVectorImpl<llvm::StringRef> &Out,
+                      size_t StartPos = 0) const;
+  llvm::Optional<llvm::StringRef> getLHSIdentifier() const;
+  void getRHSIdentifiers(llvm::SmallVectorImpl<llvm::StringRef> &Out) const;
+  llvm::Optional<llvm::StringRef> getRHS() const;
+  bool replaceId(llvm::StringRef Original, llvm::StringRef New);
+  bool replaceStr(llvm::StringRef Original, llvm::StringRef New);
+  bool contains(llvm::StringRef S) const;
+
+  void markAsEmpty();
+};
+
+class Block {
+public:
+  enum BlockKind {
+    BK_Header,
+    BK_Decor,
+    BK_TypeAndConst,
+    BK_Var,
+    BK_FunDecl,
+    BK_Function,
+    BK_MainFun
+  };
+
+private:
+  const BlockKind Kind;
+
+protected:
+  llvm::SmallVector<SPIRVLine, 4> Lines;
+
+public:
+  using line_iter = decltype(Lines)::iterator;
+  using const_line_iter = decltype(Lines)::const_iterator;
+
+  const llvm::StringRef Name;
+
+  BlockKind getKind() const { return Kind; }
+
+  Block(BlockKind K, llvm::StringRef N) : Kind(K), Name(N) {}
+  Block(const Block &) = default;
+  Block &operator=(const Block &B);
+
+  virtual ~Block() = default;
+
+  virtual bool addLine(SPIRVLine L, bool trim = true);
+
+  llvm::iterator_range<line_iter> lines() {
+    return llvm::make_range(Lines.begin(), Lines.end());
+  }
+
+  llvm::iterator_range<const_line_iter> lines() const {
+    return llvm::make_range(Lines.begin(), Lines.end());
+  }
+
+  size_t getNumLines() const { return Lines.size(); }
+  bool empty() const { return Lines.empty(); }
+  bool hasCode() const;
+  size_t getIdCount(llvm::StringRef Id) const;
+  virtual void replaceAllIds(llvm::StringRef Old, llvm::StringRef New);
+  SPIRVLine &getLastLine();
+  const SPIRVLine &getLastLine() const;
+  virtual void appendToStream(std::ostream &OS) const;
+  void removeNonCodeLines();
+
+  virtual void dump() const;
+};
+
+class HeaderBlock : public Block {
+public:
+  HeaderBlock() : Block(BK_Header, "Header") {}
+  HeaderBlock(const HeaderBlock &) = default;
+  HeaderBlock &operator=(const HeaderBlock &) = default;
+  static bool classof(const Block *B) { return B->getKind() == BK_Header; }
+
+  bool getRSKernelNames(llvm::SmallVectorImpl<llvm::StringRef> &Out) const;
+};
+
+class DecorBlock : public Block {
+public:
+  DecorBlock() : Block(BK_Decor, "Decor") {}
+  DecorBlock(const DecorBlock &) = default;
+  DecorBlock &operator=(const DecorBlock &) = default;
+  static bool classof(const Block *B) { return B->getKind() == BK_Decor; }
+};
+
+// Block containing (interleaved) type and constant definitions.
+class TypeAndConstBlock : public Block {
+public:
+  TypeAndConstBlock() : Block(BK_TypeAndConst, "TypeAndConst") {}
+  TypeAndConstBlock(const TypeAndConstBlock &) = default;
+  TypeAndConstBlock &operator=(const TypeAndConstBlock &) = default;
+  static bool classof(const Block *B) {
+    return B->getKind() == BK_TypeAndConst;
+  }
+};
+
+class VarBlock : public Block {
+public:
+  VarBlock() : Block(BK_Var, "Var") {}
+  VarBlock(const VarBlock &) = default;
+  VarBlock &operator=(const VarBlock &) = default;
+  static bool classof(const Block *B) { return B->getKind() == BK_Var; }
+};
+
+class FunDeclBlock : public Block {
+public:
+  FunDeclBlock() : Block(BK_FunDecl, "FunDecl") {}
+  FunDeclBlock(const FunDeclBlock &) = default;
+  FunDeclBlock &operator=(const FunDeclBlock &) = default;
+  static bool classof(const Block *B) { return B->getKind() == BK_FunDecl; }
+};
+
+class FunctionBlock : public Block {
+public:
+  FunctionBlock() : Block(BK_Function, "Function") {}
+  FunctionBlock(const FunctionBlock &) = default;
+  FunctionBlock &operator=(const FunctionBlock &) = default;
+  static bool classof(const Block *B) {
+    return B->getKind() >= BK_Function && B->getKind() <= BK_MainFun;
+  }
+
+  llvm::StringRef getFunctionName() const;
+  size_t getArity() const;
+  void getArgNames(llvm::SmallVectorImpl<llvm::StringRef> &Out) const;
+  llvm::Optional<llvm::StringRef> getRetValName() const;
+  llvm::iterator_range<const_line_iter> body() const;
+  void getCalledFunctions(llvm::SmallVectorImpl<llvm::StringRef> &Out) const;
+  bool hasFunctionCalls() const;
+  bool isDirectlyRecursive() const;
+  bool isReturnTypeVoid() const;
+
+protected:
+  FunctionBlock(BlockKind BK, llvm::StringRef N) : Block(BK, N) {}
+};
+
+class MainFunBlock : public FunctionBlock {
+public:
+  MainFunBlock() : FunctionBlock(BK_MainFun, "MainFun") {}
+  MainFunBlock(const MainFunBlock &) = default;
+  MainFunBlock &operator=(const MainFunBlock &) = default;
+  static bool classof(const Block *B) { return B->getKind() == BK_MainFun; }
+};
+
+class LinkerModule {
+  using block_ptr = std::unique_ptr<Block>;
+  std::vector<block_ptr> Blocks;
+
+public:
+  using block_iter = decltype(Blocks)::iterator;
+  using const_block_iter = decltype(Blocks)::const_iterator;
+
+  LinkerModule(std::istream &ModuleIn);
+  LinkerModule() = default;
+
+  void dump() const {
+    for (const auto &Blck : Blocks)
+      Blck->dump();
+  }
+
+  std::vector<SPIRVLine *> lines() {
+    std::vector<SPIRVLine *> res;
+    for (auto &B : Blocks)
+      for (auto &L : B->lines())
+        res.emplace_back(&L);
+
+    return res;
+  }
+
+  std::vector<const SPIRVLine *> lines() const {
+    std::vector<const SPIRVLine *> res;
+    for (const auto &B : Blocks)
+      for (const auto &L : B->lines())
+        res.emplace_back(&L);
+
+    return res;
+  }
+
+  llvm::iterator_range<block_iter> blocks() {
+    return llvm::make_range(Blocks.begin(), Blocks.end());
+  }
+
+  llvm::iterator_range<const_block_iter> blocks() const {
+    return llvm::make_range(Blocks.cbegin(), Blocks.cend());
+  }
+
+  template <typename T, typename... Ts> T &addBlock(Ts &&... ts) {
+    Blocks.emplace_back(std::unique_ptr<T>(new T(std::forward<Ts>(ts)...)));
+    return *llvm::cast<T>(Blocks.back().get());
+  }
+
+  template <typename T> T &getLastBlock() {
+    assert(Blocks.size());
+    return *llvm::cast<T>(Blocks.back().get());
+  }
+
+  template <typename T> const T &getLastBlock() const {
+    assert(Blocks.size());
+    return *llvm::cast<T>(Blocks.back().get());
+  }
+
+  template <typename P>
+  void getBlocksIf(llvm::SmallVectorImpl<Block *> &Out, P Predicate) {
+    for (auto &BPtr : Blocks)
+      if (Predicate(*BPtr))
+        Out.push_back(BPtr.get());
+  }
+
+  template <typename P>
+  void getBlocksIf(llvm::SmallVectorImpl<const Block *> &Out,
+                   P Predicate) const {
+    for (const auto &BPtr : Blocks)
+      if (Predicate(*BPtr))
+        Out.push_back(BPtr.get());
+  }
+
+  template <typename P> void removeBlocksIf(P Predicate) {
+    Blocks.erase(std::remove_if(Blocks.begin(), Blocks.end(),
+                                [&Predicate](const block_ptr &BPtr) {
+                                  return Predicate(*BPtr);
+                                }),
+                 Blocks.end());
+  }
+
+  void fixBlockOrder();
+  bool saveToFile(llvm::StringRef FName) const;
+  void removeEmptyBlocks();
+  void removeNonCode();
+  void removeUnusedFunctions();
+};
+
+} // namespace rs2spirv
+
+#endif
\ No newline at end of file
diff --git a/rsov/compiler/RSAllocationUtils.cpp b/rsov/compiler/RSAllocationUtils.cpp
new file mode 100644
index 0000000..dbcb2a8
--- /dev/null
+++ b/rsov/compiler/RSAllocationUtils.cpp
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RSAllocationUtils.h"
+
+#include "SPIRVInternal.h"
+#include "llvm/IR/GlobalVariable.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Transforms/Utils/Cloning.h"
+
+#include <algorithm>
+#include <sstream>
+#include <unordered_map>
+
+#define DEBUG_TYPE "rs2spirv-rs-allocation-utils"
+
+using namespace llvm;
+
+namespace rs2spirv {
+
+bool isRSAllocation(const GlobalVariable &GV) {
+  auto *PT = cast<PointerType>(GV.getType());
+  DEBUG(PT->dump());
+
+  auto *VT = PT->getElementType();
+  DEBUG(VT->dump());
+  std::string TypeName;
+  raw_string_ostream RSO(TypeName);
+  VT->print(RSO);
+  RSO.str(); // Force flush.
+  DEBUG(dbgs() << "TypeName: " << TypeName << '\n');
+
+  return TypeName.find("struct.rs_allocation") != std::string::npos;
+}
+
+bool getRSAllocationInfo(Module &M, SmallVectorImpl<RSAllocationInfo> &Allocs) {
+  DEBUG(dbgs() << "getRSAllocationInfo\n");
+
+  for (auto &GV : M.globals()) {
+    if (GV.isDeclaration() || !isRSAllocation(GV))
+      continue;
+
+    Allocs.push_back({'%' + GV.getName().str(), None, &GV});
+  }
+
+  return true;
+}
+
+bool getRSAllocAccesses(SmallVectorImpl<RSAllocationInfo> &Allocs,
+                        SmallVectorImpl<RSAllocationCallInfo> &Calls) {
+  DEBUG(dbgs() << "getRSGEATCalls\n");
+  DEBUG(dbgs() << "\n\n~~~~~~~~~~~~~~~~~~~~~\n\n");
+
+  std::unordered_map<Value *, GlobalVariable *> Mapping;
+
+  for (auto &A : Allocs) {
+    auto *GV = A.GlobalVar;
+    std::vector<User *> WorkList(GV->user_begin(), GV->user_end());
+    size_t Idx = 0;
+
+    while (Idx < WorkList.size()) {
+      auto *U = WorkList[Idx];
+      DEBUG(dbgs() << "Visiting ");
+      DEBUG(U->dump());
+      ++Idx;
+      auto It = Mapping.find(U);
+      if (It != Mapping.end()) {
+        if (It->second == GV) {
+          continue;
+        } else {
+          errs() << "Duplicate global mapping discovered!\n";
+          errs() << "\nGlobal: ";
+          GV->print(errs());
+          errs() << "\nExisting mapping: ";
+          It->second->print(errs());
+          errs() << "\nUser: ";
+          U->print(errs());
+          errs() << '\n';
+
+          return false;
+        }
+      }
+
+      Mapping[U] = GV;
+      DEBUG(dbgs() << "New mapping: ");
+      DEBUG(U->print(dbgs()));
+      DEBUG(dbgs() << " -> " << GV->getName() << '\n');
+
+      if (auto *FCall = dyn_cast<CallInst>(U)) {
+        if (auto *F = FCall->getCalledFunction()) {
+          const auto FName = F->getName();
+          DEBUG(dbgs() << "Discovered function call to : " << FName << '\n');
+
+          std::string DemangledName;
+          oclIsBuiltin(FName, &DemangledName);
+          const StringRef DemangledNameRef(DemangledName);
+          DEBUG(dbgs() << "Demangled name: " << DemangledNameRef << '\n');
+
+          const StringRef GEAPrefix = "rsGetElementAt_";
+          const StringRef SEAPrefix = "rsSetElementAt_";
+          assert(GEAPrefix.size() == SEAPrefix.size());
+
+          const bool IsGEA = DemangledNameRef.startswith(GEAPrefix);
+          const bool IsSEA = DemangledNameRef.startswith(SEAPrefix);
+
+          assert(!IsGEA || !IsSEA);
+
+          if (IsGEA || IsSEA) {
+            DEBUG(dbgs() << "Found rsAlloc function!\n");
+
+            const auto Kind =
+                IsGEA ? RSAllocAccessKind::GEA : RSAllocAccessKind::SEA;
+
+            const auto RSElementTy =
+                DemangledNameRef.drop_front(GEAPrefix.size());
+
+            Calls.push_back({A, FCall, Kind, RSElementTy.str()});
+            continue;
+          } else if (DemangledNameRef.startswith(GEAPrefix.drop_back()) ||
+                     DemangledNameRef.startswith(SEAPrefix.drop_back())) {
+            errs() << "Untyped accesses to global rs_allocations are not "
+                      "supported.\n";
+            return false;
+          }
+        }
+      }
+
+      // TODO: Consider using set-like container to reduce computational
+      // complexity.
+      for (auto *NewU : U->users())
+        if (std::find(WorkList.begin(), WorkList.end(), NewU) == WorkList.end())
+          WorkList.push_back(NewU);
+    }
+  }
+
+  std::unordered_map<GlobalVariable *, std::string> GVAccessTypes;
+
+  for (auto &Access : Calls) {
+    auto AccessElemTyIt = GVAccessTypes.find(Access.RSAlloc.GlobalVar);
+    if (AccessElemTyIt != GVAccessTypes.end() &&
+        AccessElemTyIt->second != Access.RSElementTy) {
+      errs() << "Could not infere element type for: ";
+      Access.RSAlloc.GlobalVar->print(errs());
+      errs() << '\n';
+      return false;
+    } else if (AccessElemTyIt == GVAccessTypes.end()) {
+      GVAccessTypes.emplace(Access.RSAlloc.GlobalVar, Access.RSElementTy);
+      Access.RSAlloc.RSElementType = Access.RSElementTy;
+    }
+  }
+
+  DEBUG(dbgs() << "\n\n~~~~~~~~~~~~~~~~~~~~~\n\n");
+  return true;
+}
+
+bool solidifyRSAllocAccess(Module &M, RSAllocationCallInfo CallInfo) {
+  DEBUG(dbgs() << "\tsolidifyRSAllocAccess " << CallInfo.RSAlloc.VarName
+               << '\n');
+  auto *FCall = CallInfo.FCall;
+  auto *Fun = FCall->getCalledFunction();
+  assert(Fun);
+
+  const auto FName = Fun->getName();
+
+  StringRef GVName = CallInfo.RSAlloc.VarName;
+  std::ostringstream OSS;
+  OSS << "RS_" << GVName.drop_front().str() << FName.str();
+
+  auto *NewF = Function::Create(Fun->getFunctionType(),
+                                Function::ExternalLinkage, OSS.str(), &M);
+  FCall->setCalledFunction(NewF);
+  NewF->setAttributes(Fun->getAttributes());
+
+  DEBUG(M.dump());
+
+  return true;
+}
+
+} // rs2spirv
diff --git a/rsov/compiler/RSAllocationUtils.h b/rsov/compiler/RSAllocationUtils.h
new file mode 100644
index 0000000..5b5b818
--- /dev/null
+++ b/rsov/compiler/RSAllocationUtils.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS2SPIRV_RS_ALLOCATION_UTILS_H
+#define RS2SPIRV_RS_ALLOCATION_UTILS_H
+
+#include "llvm/ADT/Optional.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace llvm {
+class CallInst;
+class GlobalVariable;
+class Module;
+class Type;
+}
+
+namespace rs2spirv {
+
+struct RSAllocationInfo {
+  std::string VarName;
+  llvm::Optional<std::string> RSElementType;
+  llvm::GlobalVariable *GlobalVar;
+};
+
+enum class RSAllocAccessKind { GEA, SEA };
+
+struct RSAllocationCallInfo {
+  RSAllocationInfo &RSAlloc;
+  llvm::CallInst *FCall;
+  RSAllocAccessKind Kind;
+  std::string RSElementTy;
+};
+
+bool isRSAllocation(const llvm::GlobalVariable &GV);
+llvm::Optional<llvm::StringRef> getRSTypeName(const llvm::GlobalVariable &GV);
+bool getRSAllocationInfo(llvm::Module &M,
+                         llvm::SmallVectorImpl<RSAllocationInfo> &Allocs);
+bool getRSAllocAccesses(llvm::SmallVectorImpl<RSAllocationInfo> &Allocs,
+                        llvm::SmallVectorImpl<RSAllocationCallInfo> &Calls);
+bool solidifyRSAllocAccess(llvm::Module &M, RSAllocationCallInfo CallInfo);
+
+} // namespace rs2spirv
+
+#endif
diff --git a/rsov/compiler/RSSPIRVWriter.cpp b/rsov/compiler/RSSPIRVWriter.cpp
new file mode 100644
index 0000000..39632c9
--- /dev/null
+++ b/rsov/compiler/RSSPIRVWriter.cpp
@@ -0,0 +1,603 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RSSPIRVWriter.h"
+
+#include "SPIRVModule.h"
+#include "bcinfo/MetadataExtractor.h"
+
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/Triple.h"
+#include "llvm/IR/LegacyPassManager.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/SPIRV.h"
+#include "llvm/Support/raw_ostream.h"
+#include "llvm/Transforms/IPO.h"
+
+#include "GlobalMergePass.h"
+#include "LinkerModule.h"
+#include "ReflectionPass.h"
+#include "InlinePreparationPass.h"
+
+#include <fstream>
+#include <sstream>
+
+#define DEBUG_TYPE "rs2spirv-writer"
+
+using namespace llvm;
+using namespace SPIRV;
+
+namespace llvm {
+FunctionPass *createPromoteMemoryToRegisterPass();
+}
+
+namespace rs2spirv {
+
+static cl::opt<std::string> WrapperOutputFile("wo",
+                                              cl::desc("Wrapper output file"),
+                                              cl::value_desc("filename.spt"));
+
+static bool FixMain(LinkerModule &LM, MainFunBlock &MB, StringRef KernelName);
+static bool InlineFunctionCalls(LinkerModule &LM, MainFunBlock &MB);
+static bool FuseTypesAndConstants(LinkerModule &LM);
+static bool TranslateInBoundsPtrAccessToAccess(SPIRVLine &L);
+static bool FixVectorShuffles(MainFunBlock &MB);
+static void FixModuleStorageClass(LinkerModule &M);
+
+static void HandleTargetTriple(Module &M) {
+  Triple TT(M.getTargetTriple());
+  auto Arch = TT.getArch();
+
+  StringRef NewTriple;
+  switch (Arch) {
+  default:
+    llvm_unreachable("Unrecognized architecture");
+    break;
+  case Triple::arm:
+    NewTriple = "spir-unknown-unknown";
+    break;
+  case Triple::aarch64:
+    NewTriple = "spir64-unknown-unknown";
+    break;
+  case Triple::spir:
+  case Triple::spir64:
+    DEBUG(dbgs() << "!!! Already a spir triple !!!\n");
+  }
+
+  DEBUG(dbgs() << "New triple:\t" << NewTriple << "\n");
+  M.setTargetTriple(NewTriple);
+}
+
+void addPassesForRS2SPIRV(llvm::legacy::PassManager &PassMgr) {
+  PassMgr.add(createGlobalMergePass());
+  PassMgr.add(createPromoteMemoryToRegisterPass());
+  PassMgr.add(createTransOCLMD());
+  // TODO: investigate removal of OCLTypeToSPIRV pass.
+  PassMgr.add(createOCLTypeToSPIRV());
+  PassMgr.add(createSPIRVRegularizeLLVM());
+  PassMgr.add(createSPIRVLowerConstExpr());
+  PassMgr.add(createSPIRVLowerBool());
+  PassMgr.add(createAlwaysInlinerPass());
+}
+
+bool WriteSPIRV(Module *M, llvm::raw_ostream &OS, std::string &ErrMsg) {
+  std::unique_ptr<SPIRVModule> BM(SPIRVModule::createSPIRVModule());
+
+  HandleTargetTriple(*M);
+
+  bcinfo::MetadataExtractor ME(M);
+  if (!ME.extract()) {
+    errs() << "Could not extract metadata\n";
+    return false;
+  }
+  DEBUG(dbgs() << "Metadata extracted\n");
+
+  llvm::legacy::PassManager PassMgr;
+  PassMgr.add(createInlinePreparationPass(ME));
+  addPassesForRS2SPIRV(PassMgr);
+
+  std::ofstream WrapperF;
+  if (!WrapperOutputFile.empty()) {
+    WrapperF.open(WrapperOutputFile, std::ios::trunc);
+    if (!WrapperF.good()) {
+      errs() << "Could not create/open file:\t" << WrapperOutputFile << "\n";
+      return false;
+    }
+    DEBUG(dbgs() << "Wrapper output:\t" << WrapperOutputFile << "\n");
+    PassMgr.add(createReflectionPass(WrapperF, ME));
+  }
+
+  PassMgr.add(createLLVMToSPIRV(BM.get()));
+  PassMgr.run(*M);
+  DEBUG(M->dump());
+
+  if (BM->getError(ErrMsg) != SPIRVEC_Success)
+    return false;
+
+  OS << *BM;
+
+  return true;
+}
+
+bool Link(llvm::StringRef KernelFilename, llvm::StringRef WrapperFilename,
+          llvm::StringRef OutputFilename) {
+  DEBUG(dbgs() << "Linking...\n");
+
+  std::ifstream WrapperF(WrapperFilename);
+  if (!WrapperF.good()) {
+    errs() << "Cannot open file: " << WrapperFilename << "\n";
+  }
+  std::ifstream KernelF(KernelFilename);
+  if (!KernelF.good()) {
+    errs() << "Cannot open file: " << KernelFilename << "\n";
+  }
+
+  LinkerModule WrapperM(WrapperF);
+  LinkerModule KernelM(KernelF);
+
+  WrapperF.close();
+  KernelF.close();
+
+  DEBUG(dbgs() << "WrapperF:\n");
+  DEBUG(WrapperM.dump());
+  DEBUG(dbgs() << "\n~~~~~~~~~~~~~~~~~~~~~~\n\nKernelF:\n");
+  DEBUG(KernelM.dump());
+  DEBUG(dbgs() << "\n======================\n\n");
+
+  const char *const Prefix = "%rs_linker_";
+
+  for (auto *LPtr : KernelM.lines()) {
+    assert(LPtr);
+    auto &L = *LPtr;
+    size_t Pos = 0;
+    while ((Pos = L.str().find("%", Pos)) != std::string::npos) {
+      L.str().replace(Pos, 1, Prefix);
+      Pos += strlen(Prefix);
+    }
+  }
+
+  FixModuleStorageClass(KernelM);
+  DEBUG(KernelM.dump());
+
+  auto WBlocks = WrapperM.blocks();
+  auto WIt = WBlocks.begin();
+  const auto WEnd = WBlocks.end();
+
+  auto KBlocks = KernelM.blocks();
+  auto KIt = KBlocks.begin();
+  const auto KEnd = KBlocks.end();
+
+  LinkerModule OutM;
+
+  if (WIt == WEnd || KIt == KEnd)
+    return false;
+
+  const auto *HeaderB = dyn_cast<HeaderBlock>(WIt->get());
+  if (!HeaderB || !isa<HeaderBlock>(KIt->get()))
+    return false;
+
+  SmallVector<StringRef, 2> KernelNames;
+  const bool KernelsFound = HeaderB->getRSKernelNames(KernelNames);
+
+  if (!KernelsFound) {
+    errs() << "RS kernel names not found in wrapper\n";
+    return false;
+  }
+
+  // TODO: Support more than one kernel.
+  if (KernelNames.size() != 1) {
+    errs() << "Unsupported number of kernels: " << KernelNames.size() << '\n';
+    return false;
+  }
+
+  const std::string KernelName =
+      Prefix + KernelNames.front().drop_front().str();
+  DEBUG(dbgs() << "Kernel name: " << KernelName << '\n');
+
+  // Kernel's HeaderBlock is skipped - it has OpenCL-specific code that
+  // is replaced here with compute shader code.
+
+  OutM.addBlock<HeaderBlock>(*HeaderB);
+
+  if (++WIt == WEnd || ++KIt == KEnd)
+    return false;
+
+  const auto *DecorBW = dyn_cast<DecorBlock>(WIt->get());
+  if (!DecorBW || !isa<DecorBlock>(KIt->get()))
+    return false;
+
+  // Kernel's DecorBlock is skipped, because it contains OpenCL-specific code
+  // that is not needed (eg. linkage type information).
+
+  OutM.addBlock<DecorBlock>(*DecorBW);
+
+  if (++WIt == WEnd || ++KIt == KEnd)
+    return false;
+
+  const auto *TypeAndConstBW = dyn_cast<TypeAndConstBlock>(WIt->get());
+  auto *TypeAndConstBK = dyn_cast<TypeAndConstBlock>(KIt->get());
+  if (!TypeAndConstBW || !TypeAndConstBK)
+    return false;
+
+  OutM.addBlock<TypeAndConstBlock>(*TypeAndConstBW);
+  OutM.addBlock<TypeAndConstBlock>(*TypeAndConstBK);
+
+  if (++WIt == WEnd || ++KIt == KEnd)
+    return false;
+
+  const auto *VarBW = dyn_cast<VarBlock>(WIt->get());
+  auto *VarBK = dyn_cast<VarBlock>(KIt->get());
+  if (!VarBW)
+    return false;
+
+  OutM.addBlock<VarBlock>(*VarBW);
+
+  if (VarBK)
+    OutM.addBlock<VarBlock>(*VarBK);
+  else
+    --KIt;
+
+  MainFunBlock *MainB = nullptr;
+
+  while (++WIt != WEnd) {
+    auto *FunB = dyn_cast<FunctionBlock>(WIt->get());
+    if (!FunB)
+      return false;
+
+    if (auto *MB = dyn_cast<MainFunBlock>(WIt->get())) {
+      if (MainB) {
+        errs() << "More than one main function found in wrapper module\n";
+        return false;
+      }
+
+      MainB = &OutM.addBlock<MainFunBlock>(*MB);
+    } else {
+      OutM.addBlock<FunctionBlock>(*FunB);
+    }
+  }
+
+  if (!MainB) {
+    errs() << "Wrapper module has no main function\n";
+    return false;
+  }
+
+  while (++KIt != KEnd) {
+    // TODO: Check if FunDecl is a known runtime function.
+    if (isa<FunDeclBlock>(KIt->get()))
+      continue;
+
+    auto *FunB = dyn_cast<FunctionBlock>(KIt->get());
+    if (!FunB)
+      return false;
+
+    // TODO: Detect also indirect recurion.
+    if (FunB->isDirectlyRecursive()) {
+      errs() << "Function: " << FunB->getFunctionName().str()
+             << " is recursive\n";
+      return false;
+    }
+
+    OutM.addBlock<FunctionBlock>(*FunB);
+  }
+
+  OutM.fixBlockOrder();
+  if (!FixMain(OutM, *MainB, KernelName))
+    return false;
+
+  if (!FixVectorShuffles(*MainB))
+    return false;
+
+  OutM.removeUnusedFunctions();
+
+  DEBUG(dbgs() << ">>>>>>>>>>>>  Output module after prelink:\n\n");
+  DEBUG(OutM.dump());
+
+  if (!FuseTypesAndConstants(OutM)) {
+    errs() << "Type fusion failed\n";
+    return false;
+  }
+
+  DEBUG(dbgs() << ">>>>>>>>>>>>  Output module after value fusion:\n\n");
+  DEBUG(OutM.dump());
+
+  if (!OutM.saveToFile(OutputFilename)) {
+    errs() << "Could not save to file: " << OutputFilename << "\n";
+    return false;
+  }
+
+  return true;
+}
+
+bool FixMain(LinkerModule &LM, MainFunBlock &MainB, StringRef KernelName) {
+  MainB.replaceAllIds("%RS_SPIRV_DUMMY_", KernelName);
+
+  while (MainB.hasFunctionCalls())
+    if (!InlineFunctionCalls(LM, MainB)) {
+      errs() << "Could not inline function calls in main\n";
+      return false;
+    }
+
+  for (auto &L : MainB.lines()) {
+    if (!L.contains("OpInBoundsPtrAccessChain"))
+      continue;
+
+    if (!TranslateInBoundsPtrAccessToAccess(L))
+      return false;
+  }
+
+  return true;
+}
+
+struct FunctionCallInfo {
+  StringRef RetValName;
+  StringRef RetTy;
+  StringRef FName;
+  SmallVector<StringRef, 4> ArgNames;
+};
+
+static FunctionCallInfo GetFunctionCallInfo(const SPIRVLine &L) {
+  assert(L.contains("OpFunctionCall"));
+
+  const Optional<StringRef> Ret = L.getLHSIdentifier();
+  assert(Ret);
+
+  SmallVector<StringRef, 6> Ids;
+  L.getRHSIdentifiers(Ids);
+  assert(Ids.size() >= 2 && "No return type and function name");
+
+  const StringRef RetTy = Ids[0];
+  const StringRef FName = Ids[1];
+  SmallVector<StringRef, 4> Args(Ids.begin() + 2, Ids.end());
+
+  return {*Ret, RetTy, FName, std::move(Args)};
+}
+
+bool InlineFunctionCalls(LinkerModule &LM, MainFunBlock &MB) {
+  DEBUG(dbgs() << "InlineFunctionCalls\n");
+  MainFunBlock NewMB;
+
+  auto MLines = MB.lines();
+  auto MIt = MLines.begin();
+  const auto MEnd = MLines.end();
+  using iter_ty = decltype(MIt);
+
+  auto SkipToFunctionCall = [&MEnd, &NewMB](iter_ty &It) {
+    while (++It != MEnd && !It->contains("OpFunctionCall"))
+      NewMB.addLine(*It);
+
+    return It != MEnd;
+  };
+
+  NewMB.addLine(*MIt);
+
+  std::vector<std::pair<std::string, std::string>> NameMapping;
+
+  while (SkipToFunctionCall(MIt)) {
+    assert(MIt->contains("OpFunctionCall"));
+    const auto FInfo = GetFunctionCallInfo(*MIt);
+    DEBUG(dbgs() << "Found function call:\t" << MIt->str() << '\n');
+
+    SmallVector<Block *, 1> Callee;
+    LM.getBlocksIf(Callee, [&FInfo](Block &B) {
+      auto *FB = dyn_cast<FunctionBlock>(&B);
+      if (!FB)
+        return false;
+
+      return FB->getFunctionName() == FInfo.FName;
+    });
+
+    if (Callee.size() != 1) {
+      errs() << "Callee not found\n";
+      return false;
+    }
+
+    auto *FB = cast<FunctionBlock>(Callee.front());
+
+    if (FB->getArity() != FInfo.ArgNames.size()) {
+      errs() << "Arity mismatch (caller: " << FInfo.ArgNames.size()
+             << ", callee: " << FB->getArity() << ")\n";
+      return false;
+    }
+
+    Optional<StringRef> RetValName = FB->getRetValName();
+    if (!RetValName && !FB->isReturnTypeVoid()) {
+      errs() << "Return value not found for a function with non-void "
+                "return type.\n";
+      return false;
+    }
+
+    SmallVector<StringRef, 4> Params;
+    FB->getArgNames(Params);
+
+    if (Params.size() != FInfo.ArgNames.size()) {
+      errs() << "Params size mismatch\n";
+      return false;
+    }
+
+    for (size_t i = 0, e = FInfo.ArgNames.size(); i < e; ++i) {
+      DEBUG(dbgs() << "New param mapping: " << Params[i] << " -> "
+                   << FInfo.ArgNames[i] << "\n");
+      NameMapping.emplace_back(Params[i].str(), FInfo.ArgNames[i].str());
+    }
+
+    if (RetValName) {
+      DEBUG(dbgs() << "New ret-val mapping: " << FInfo.RetValName << " -> "
+                   << *RetValName << "\n");
+      NameMapping.emplace_back(FInfo.RetValName.str(), RetValName->str());
+    }
+
+    const auto Body = FB->body();
+    for (const auto &L : Body)
+      NewMB.addLine(L);
+  }
+
+  while (MIt != MEnd) {
+    NewMB.addLine(*MIt);
+    ++MIt;
+  }
+
+  std::reverse(NameMapping.begin(), NameMapping.end());
+  for (const auto &P : NameMapping) {
+    DEBUG(dbgs() << "Replace " << P.first << " with " << P.second << "\n");
+    NewMB.replaceAllIds(P.first, P.second);
+  }
+
+  MB = NewMB;
+
+  return true;
+}
+
+bool FuseTypesAndConstants(LinkerModule &LM) {
+  StringMap<std::string> TypesAndConstDefs;
+  StringMap<std::string> NameReps;
+
+  for (auto *LPtr : LM.lines()) {
+    assert(LPtr);
+    auto &L = *LPtr;
+    if (!L.contains("="))
+      continue;
+
+    SmallVector<StringRef, 4> IdsRefs;
+    L.getRHSIdentifiers(IdsRefs);
+
+    SmallVector<std::string, 4> Ids;
+    Ids.reserve(IdsRefs.size());
+    for (const auto &I : IdsRefs)
+      Ids.push_back(I.str());
+
+    for (auto &I : Ids)
+      if (NameReps.count(I) != 0) {
+        const bool Res = L.replaceId(I, NameReps[I]);
+        (void)Res;
+        assert(Res);
+      }
+
+    if (L.contains("OpType") || L.contains("OpConstant")) {
+      const auto LHS = L.getLHSIdentifier();
+      const auto RHS = L.getRHS();
+      assert(LHS);
+      assert(RHS);
+
+      if (TypesAndConstDefs.count(*RHS) != 0) {
+        NameReps.insert(
+            std::make_pair(LHS->str(), TypesAndConstDefs[RHS->str()]));
+        DEBUG(dbgs() << "New mapping: [" << LHS->str() << ", "
+                     << TypesAndConstDefs[RHS->str()] << "]\n");
+        L.markAsEmpty();
+      } else {
+        TypesAndConstDefs.insert(std::make_pair(RHS->str(), LHS->str()));
+        DEBUG(dbgs() << "New val:\t" << RHS->str() << " : " << LHS->str()
+                     << '\n');
+      }
+    };
+  }
+
+  LM.removeNonCode();
+
+  return true;
+}
+
+bool TranslateInBoundsPtrAccessToAccess(SPIRVLine &L) {
+  assert(L.contains(" OpInBoundsPtrAccessChain "));
+
+  SmallVector<StringRef, 4> Ids;
+  L.getRHSIdentifiers(Ids);
+
+  if (Ids.size() < 4) {
+    errs() << "OpInBoundsPtrAccessChain has not enough parameters:\n\t"
+           << L.str();
+    return false;
+  }
+
+  std::istringstream SS(L.str());
+  std::string LHS, Eq, Op;
+  SS >> LHS >> Eq >> Op;
+
+  if (LHS.empty() || Eq != "=" || Op != "OpInBoundsPtrAccessChain") {
+    errs() << "Could not decompose OpInBoundsPtrAccessChain:\n\t" << L.str();
+    return false;
+  }
+
+  constexpr size_t ElementArgPosition = 2;
+
+  std::ostringstream NewLine;
+  NewLine << LHS << " " << Eq << " OpAccessChain ";
+  for (size_t i = 0, e = Ids.size(); i != e; ++i)
+    if (i != ElementArgPosition)
+      NewLine << Ids[i].str() << " ";
+
+  L.str() = NewLine.str();
+  L.trim();
+
+  return true;
+}
+
+// Replaces UndefValues in VectorShuffles with zeros, which is always
+// safe, as the result for components marked as Undef is unused.
+// Ex. 1)    OpVectorShuffle %v4uchar %a %b 0 1 2 4294967295 -->
+//           OpVectorShuffle %v4uchar %a %b 0 1 2 0.
+//
+// Ex. 2)    OpVectorShuffle %v4uchar %a %b 0 4294967295 3 4294967295 -->
+//           OpVectorShuffle %v4uchar %a %b 0 0 3 0.
+//
+// Fix needed for the current Vulkan driver, which crashed during
+// backend compilation when case is not handled.
+bool FixVectorShuffles(MainFunBlock &MB) {
+  const StringRef UndefStr = " 4294967295 ";
+
+  for (auto &L : MB.lines()) {
+    if (!L.contains("OpVectorShuffle"))
+      continue;
+
+    L.str().push_back(' ');
+    while (L.contains(UndefStr))
+      L.replaceStr(UndefStr, " 0 ");
+
+    L.trim();
+  }
+
+  return true;
+}
+
+// This function changes all Function StorageClass use into Uniform.
+// It's needed, because llvm-spirv converter emits wrong StorageClass
+// for globals.
+// The transfromation, however, breaks legitimate uses of Function StorageClass
+// inside functions.
+//
+//  Ex. 1. %ptr_Function_uint = OpTypePointer Function %uint
+//     --> %ptr_Uniform_uint = OpTypePointer Uniform %uint
+//
+//  Ex. 2. %gep = OpAccessChain %ptr_Function_uchar %G %uint_zero
+//     --> %gep = OpAccessChain %ptr_Uniform_uchar %G %uint_zero
+//
+// TODO: Consider a better way of fixing this.
+void FixModuleStorageClass(LinkerModule &M) {
+  for (auto *LPtr : M.lines()) {
+    assert(LPtr);
+    auto &L = *LPtr;
+
+    while (L.contains(" Function"))
+      L.replaceStr(" Function", " Uniform");
+
+    while (L.contains("_Function_"))
+      L.replaceStr("_Function_", "_Uniform_");
+  }
+}
+
+} // namespace rs2spirv
diff --git a/rsov/compiler/RSSPIRVWriter.h b/rsov/compiler/RSSPIRVWriter.h
new file mode 100644
index 0000000..199d60b
--- /dev/null
+++ b/rsov/compiler/RSSPIRVWriter.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS_SPIRV_WRITER_H
+#define RS_SPIRV_WRITER_H
+
+#include "llvm/ADT/StringRef.h"
+
+namespace llvm {
+class Module;
+class raw_ostream;
+}
+
+namespace rs2spirv {
+
+bool WriteSPIRV(llvm::Module *M, llvm::raw_ostream &OS, std::string &ErrMsg);
+bool Link(llvm::StringRef KrrnelFilename, llvm::StringRef WrapperFilename,
+          llvm::StringRef OutputFilename);
+
+} // namespace rs2spirv
+
+#endif
diff --git a/rsov/compiler/ReflectionPass.cpp b/rsov/compiler/ReflectionPass.cpp
new file mode 100644
index 0000000..a909241
--- /dev/null
+++ b/rsov/compiler/ReflectionPass.cpp
@@ -0,0 +1,1005 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ReflectionPass.h"
+
+#include "RSAllocationUtils.h"
+#include "bcinfo/MetadataExtractor.h"
+#include "llvm/ADT/StringSwitch.h"
+#include "llvm/IR/Module.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/PassManager.h"
+#include "llvm/Pass.h"
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/SPIRV.h"
+
+#include <map>
+#include <sstream>
+#include <string>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#define DEBUG_TYPE "rs2spirv-reflection"
+
+using namespace llvm;
+
+namespace rs2spirv {
+
+namespace {
+
+// Numeric value corresponds to the number of components.
+enum class Coords : size_t { None = 0, X, XY, XYZ, Last = XYZ };
+static const StringRef CoordsNames[] = {"x", "y", "z"};
+
+struct KernelSignature {
+  std::string returnType;
+  std::string name;
+  std::string argumentType;
+  Coords coordsKind;
+
+  void dump() const {
+    dbgs() << returnType << ' ' << name << '(' << argumentType;
+    const auto CoordsNum = size_t(coordsKind);
+    for (size_t i = 0; i != CoordsNum; ++i)
+      dbgs() << ", " << CoordsNames[i];
+
+    dbgs() << ")\n";
+  }
+};
+
+std::string TypeToString(const Type *Ty) {
+  assert(Ty);
+  if (Ty->isVoidTy())
+    return "void";
+
+  if (auto *IT = dyn_cast<IntegerType>(Ty)) {
+    if (IT->getBitWidth() == 32)
+      return "int";
+    else if (IT->getBitWidth() == 8)
+      return "uchar";
+    assert(false && "Unknown integer type");
+  }
+  if (Ty->isFloatTy())
+    return "float";
+
+  if (auto *VT = dyn_cast<VectorType>(Ty)) {
+    auto *ET = VT->getElementType();
+    if (auto *IT = dyn_cast<IntegerType>(ET)) {
+      if (IT->getBitWidth() == 32)
+        return "int4";
+      else if (IT->getBitWidth() == 8)
+        return "uchar4";
+
+      assert(false && "Unknown integer vector type");
+    }
+    if (ET->isFloatTy())
+      return "float4";
+
+    llvm_unreachable("Unknown vector type");
+  }
+
+  llvm_unreachable("Unknown type");
+}
+
+enum class RSType {
+  rs_void,
+  rs_uchar,
+  rs_int,
+  rs_float,
+  rs_uchar4,
+  rs_int4,
+  rs_float4
+};
+
+RSType StrToRsTy(StringRef S) {
+  RSType Ty = StringSwitch<RSType>(S)
+                  .Case("void", RSType::rs_void)
+                  .Case("uchar", RSType::rs_uchar)
+                  .Case("int", RSType::rs_int)
+                  .Case("float", RSType::rs_float)
+                  .Case("uchar4", RSType::rs_uchar4)
+                  .Case("int4", RSType::rs_int4)
+                  .Case("float4", RSType::rs_float4);
+  return Ty;
+}
+
+struct TypeMapping {
+  RSType RSTy;
+  bool isVectorTy;
+  // Scalar types are accessed (loaded/stored) using wider (vector) types.
+  // 'vecLen' corresponds to width of such vector type.
+  // As for vector types, 'vectorWidth' is just width of such type.
+  size_t vectorWidth;
+  std::string SPIRVTy;
+  std::string SPIRVScalarTy;
+  std::string SPIRVImageFormat;
+  // TODO: Handle different image formats for read and write.
+  std::string SPIRVImageReadType;
+
+  TypeMapping(RSType RSTy, bool IsVectorTy, size_t VectorLen,
+              StringRef SPIRVScalarTy, StringRef SPIRVImageFormat)
+      : RSTy(RSTy), isVectorTy(IsVectorTy), vectorWidth(VectorLen),
+        SPIRVScalarTy(SPIRVScalarTy), SPIRVImageFormat(SPIRVImageFormat) {
+    assert(vectorWidth != 0);
+
+    if (isVectorTy) {
+      std::ostringstream OSS;
+      OSS << "%v" << vectorWidth << SPIRVScalarTy.drop_front().str();
+      SPIRVTy = OSS.str();
+      SPIRVImageReadType = SPIRVTy;
+      return;
+    }
+
+    SPIRVTy = SPIRVScalarTy;
+    std::ostringstream OSS;
+    OSS << "%v" << vectorWidth << SPIRVScalarTy.drop_front().str();
+    SPIRVImageReadType = OSS.str();
+  }
+};
+
+class ReflectionPass : public ModulePass {
+  std::ostream &OS;
+  bcinfo::MetadataExtractor &ME;
+
+  static const std::map<RSType, TypeMapping> TypeMappings;
+
+  static const TypeMapping *getMapping(RSType RsTy) {
+    auto it = TypeMappings.find(RsTy);
+    if (it != TypeMappings.end())
+      return &it->second;
+
+    return nullptr;
+  };
+
+  static const TypeMapping *getMapping(StringRef Str) {
+    auto Ty = StrToRsTy(Str);
+    return getMapping(Ty);
+  }
+
+  static const TypeMapping *getMappingOrPrintError(StringRef Str) {
+    const auto *TM = ReflectionPass::getMapping(Str);
+    if (!TM)
+      errs() << "LLVM to SPIRV type mapping for type:\t" << Str
+             << " not found\n";
+
+    return TM;
+  }
+
+  bool emitHeader(const Module &M);
+  bool emitDecorations(const Module &M,
+                       const SmallVectorImpl<RSAllocationInfo> &RSAllocs);
+  void emitCommonTypes();
+  bool extractKernelSignatures(const Module &M,
+                               SmallVectorImpl<KernelSignature> &Out);
+  bool emitKernelTypes(const KernelSignature &Kernel);
+  bool emitInputImage(const KernelSignature &Kernel);
+  void emitGLGlobalInput();
+  bool emitOutputImage(const KernelSignature &Kernel);
+  bool emitRSAllocImages(const SmallVectorImpl<RSAllocationInfo> &RSAllocs);
+  bool emitConstants(const KernelSignature &Kernel);
+  void emitRTFunctions();
+  bool emitRSAllocFunctions(
+      Module &M, const SmallVectorImpl<RSAllocationInfo> &RSAllocs,
+      const SmallVectorImpl<RSAllocationCallInfo> &RSAllocAccesses);
+  bool emitMain(const KernelSignature &Kernel,
+                const SmallVectorImpl<RSAllocationInfo> &RSAllocs);
+
+public:
+  static char ID;
+  explicit ReflectionPass(std::ostream &OS, bcinfo::MetadataExtractor &ME)
+      : ModulePass(ID), OS(OS), ME(ME) {}
+
+  const char *getPassName() const override { return "ReflectionPass"; }
+
+  bool runOnModule(Module &M) override {
+    DEBUG(dbgs() << "ReflectionPass\n");
+
+    if (!emitHeader(M)) {
+      errs() << "Emiting header failed\n";
+      return false;
+    }
+
+    SmallVector<RSAllocationInfo, 2> RSAllocs;
+    if (!getRSAllocationInfo(M, RSAllocs)) {
+      errs() << "Extracting rs_allocation info failed\n";
+      return false;
+    }
+
+    SmallVector<RSAllocationCallInfo, 4> RSAllocAccesses;
+    if (!getRSAllocAccesses(RSAllocs, RSAllocAccesses)) {
+      errs() << "Extracting rsGEA/rsSEA info failed\n";
+      return false;
+    }
+
+    if (!emitDecorations(M, RSAllocs)) {
+      errs() << "Emiting decorations failed\n";
+      return false;
+    }
+
+    emitCommonTypes();
+
+    SmallVector<KernelSignature, 4> Kernels;
+    if (!extractKernelSignatures(M, Kernels)) {
+      errs() << "Extraction of kernels failed\n";
+      return false;
+    }
+
+    if (Kernels.size() != 1) {
+      errs() << "Non single-kernel modules are not supported\n";
+      return false;
+    }
+    const auto &Kernel = Kernels.front();
+
+    if (!emitKernelTypes(Kernel)) {
+      errs() << "Emitting kernel types failed\n";
+      return false;
+    }
+
+    if (!emitInputImage(Kernel)) {
+      errs() << "Emitting input image failed\n";
+      return false;
+    }
+
+    emitGLGlobalInput();
+
+    if (!emitOutputImage(Kernel)) {
+      errs() << "Emitting output image failed\n";
+      return false;
+    }
+
+    if (!emitRSAllocImages(RSAllocs)) {
+      errs() << "Emitting rs_allocation images failed\n";
+      return false;
+    }
+
+    if (!emitConstants(Kernel)) {
+      errs() << "Emitting constants failed\n";
+      return false;
+    }
+
+    emitRTFunctions();
+
+    if (!emitRSAllocFunctions(M, RSAllocs, RSAllocAccesses)) {
+      errs() << "Emitting rs_allocation runtime functions failed\n";
+      return false;
+    }
+
+    if (!emitMain(Kernel, RSAllocs)) {
+      errs() << "Emitting main failed\n";
+      return false;
+    }
+
+    // Return false, as the module is not modified.
+    return false;
+  }
+};
+
+// TODO: Add other types: bool, double, char, uchar, long, ulong
+//  and their vector counterparts.
+// TODO: Support vector types of width different than 4. eg. float3.
+const std::map<RSType, TypeMapping> ReflectionPass::TypeMappings = {
+    {RSType::rs_void, {RSType::rs_void, false, 1, "%void", ""}},
+    {RSType::rs_uchar, {RSType::rs_uchar, false, 4, "%uchar", "R8ui"}},
+    {RSType::rs_int, {RSType::rs_void, false, 4, "%int", "R32i"}},
+    {RSType::rs_float, {RSType::rs_float, false, 4, "%float", "R32f"}},
+    {RSType::rs_uchar4, {RSType::rs_uchar4, true, 4, "%uchar", "Rgba8ui"}},
+    {RSType::rs_int4, {RSType::rs_int4, true, 4, "%int", "Rgba32i"}},
+    {RSType::rs_float4, {RSType::rs_float4, true, 4, "%float", "Rgba32f"}}};
+};
+
+char ReflectionPass::ID = 0;
+
+ModulePass *createReflectionPass(std::ostream &OS,
+                                 bcinfo::MetadataExtractor &ME) {
+  return new ReflectionPass(OS, ME);
+}
+
+bool ReflectionPass::emitHeader(const Module &M) {
+  DEBUG(dbgs() << "emitHeader\n");
+
+  OS << "; SPIR-V\n"
+        "; Version: 1.0\n"
+        "; Generator: rs2spirv;\n"
+        "; Bound: 1024\n"
+        "; Schema: 0\n"
+        "      OpCapability Shader\n"
+        "      OpCapability StorageImageWriteWithoutFormat\n"
+        "      OpCapability Addresses\n"
+        " %glsl_ext_ins = OpExtInstImport \"GLSL.std.450\"\n"
+        "      OpMemoryModel Physical32 GLSL450\n"
+        "      OpEntryPoint GLCompute %main \"main\" %global_invocation_id\n"
+        "      OpExecutionMode %main LocalSize 1 1 1\n"
+        "      OpSource GLSL 450\n"
+        "      OpSourceExtension \"GL_ARB_separate_shader_objects\"\n"
+        "      OpSourceExtension \"GL_ARB_shading_language_420pack\"\n"
+        "      OpSourceExtension \"GL_GOOGLE_cpp_style_line_directive\"\n"
+        "      OpSourceExtension \"GL_GOOGLE_include_directive\"\n";
+
+  const size_t RSKernelNum = ME.getExportForEachSignatureCount();
+
+  if (RSKernelNum == 0)
+    return false;
+
+  const char **RSKernelNames = ME.getExportForEachNameList();
+
+  OS << " %RS_KERNELS = OpString \"";
+
+  for (size_t i = 0; i < RSKernelNum; ++i)
+    if (RSKernelNames[i] != StringRef("root"))
+      OS << '%' << RSKernelNames[i] << " ";
+
+  OS << "\"\n";
+
+  return true;
+}
+
+bool ReflectionPass::emitDecorations(
+    const Module &M, const SmallVectorImpl<RSAllocationInfo> &RSAllocs) {
+  DEBUG(dbgs() << "emitDecorations\n");
+
+  OS << "\n"
+        "      OpDecorate %global_invocation_id BuiltIn GlobalInvocationId\n"
+        "      OpDecorate %input_image DescriptorSet 0\n"
+        "      OpDecorate %input_image Binding 0\n"
+        "      OpDecorate %input_image NonWritable\n"
+        "      OpDecorate %output_image DescriptorSet 0\n"
+        "      OpDecorate %output_image Binding 1\n"
+        "      OpDecorate %output_image NonReadable\n";
+
+  const auto GlobalsB = M.globals().begin();
+  const auto GlobalsE = M.globals().end();
+  const auto Found =
+      std::find_if(GlobalsB, GlobalsE, [](const GlobalVariable &GV) {
+        return GV.getName() == "__GPUBlock";
+      });
+
+  if (Found == GlobalsE)
+    return true; // GPUBlock not found - not an error by itself.
+
+  const GlobalVariable &G = *Found;
+
+  DEBUG(dbgs() << "Found GPUBlock:\t");
+  DEBUG(G.dump());
+
+  bool IsCorrectTy = false;
+  if (const auto *PtrTy = dyn_cast<PointerType>(G.getType())) {
+    if (auto *StructTy = dyn_cast<StructType>(PtrTy->getElementType())) {
+      IsCorrectTy = true;
+
+      const auto &DLayout = M.getDataLayout();
+      const auto *SLayout = DLayout.getStructLayout(StructTy);
+      assert(SLayout);
+
+      for (size_t i = 0, e = StructTy->getNumElements(); i != e; ++i)
+        OS << "      OpMemberDecorate %rs_linker_struct___GPUBuffer " << i
+           << " Offset " << SLayout->getElementOffset(i) << '\n';
+    }
+  }
+
+  if (!IsCorrectTy) {
+    errs() << "GPUBlock is not of expected type:\t";
+    G.print(errs());
+    G.getType()->print(errs());
+    return false;
+  }
+
+  OS << "      OpDecorate %rs_linker_struct___GPUBuffer BufferBlock\n";
+  OS << "      OpDecorate %rs_linker___GPUBlock DescriptorSet 0\n";
+  OS << "      OpDecorate %rs_linker___GPUBlock Binding 2\n";
+
+  size_t BindingNum = 3;
+
+  for (const auto &A : RSAllocs) {
+    OS << "      OpDecorate " << A.VarName << "_var DescriptorSet 0\n";
+    OS << "      OpDecorate " << A.VarName << "_var Binding " << BindingNum
+       << '\n';
+    ++BindingNum;
+  }
+
+  return true;
+}
+
+void ReflectionPass::emitCommonTypes() {
+  DEBUG(dbgs() << "emitCommonTypes\n");
+
+  OS << "\n\n"
+        "%void = OpTypeVoid\n"
+        "%fun_void = OpTypeFunction %void\n"
+        "%float = OpTypeFloat 32\n"
+        "%v2float = OpTypeVector %float 2\n"
+        "%v3float = OpTypeVector %float 3\n"
+        "%v4float = OpTypeVector %float 4\n"
+        "%int = OpTypeInt 32 1\n"
+        "%v2int = OpTypeVector %int 2\n"
+        "%v4int = OpTypeVector %int 4\n"
+        "%uchar = OpTypeInt 8 0\n"
+        "%v2uchar = OpTypeVector %uchar 2\n"
+        "%v3uchar = OpTypeVector %uchar 3\n"
+        "%v4uchar = OpTypeVector %uchar 4\n"
+        "%uint = OpTypeInt 32 0\n"
+        "%v2uint = OpTypeVector %uint 2\n"
+        "%v3uint = OpTypeVector %uint 3\n"
+        "%v4uint = OpTypeVector %uint 4\n"
+        "%fun_f3_uc3 = OpTypeFunction %v3float %v3uchar\n"
+        "%fun_f3_u3 = OpTypeFunction %v3float %v3uint\n"
+        "%fun_f4_uc4 = OpTypeFunction %v4float %v4uchar\n"
+        "%fun_uc3_f3 = OpTypeFunction %v3uchar %v3float\n"
+        "%fun_u3_f3 = OpTypeFunction %v3uint %v3float\n"
+        "%fun_uc4_f4 = OpTypeFunction %v4uchar %v4float\n"
+        "%fun_uc4_u4 = OpTypeFunction %v4uchar %v4uint\n"
+        "%fun_u4_uc4 = OpTypeFunction %v4uint %v4uchar\n"
+        "%fun_f_f = OpTypeFunction %float %float\n"
+        "%fun_f_ff = OpTypeFunction %float %float %float\n"
+        "%fun_f_fff = OpTypeFunction %float %float %float %float\n"
+        "%fun_f_f2f2 = OpTypeFunction %float %v2float %v2float\n"
+        "%fun_f_f3f3 = OpTypeFunction %float %v3float %v3float\n"
+        "%fun_f3_f3ff = OpTypeFunction %v3float %v3float %float %float\n"
+        "%fun_i_iii = OpTypeFunction %int %int %int %int\n"
+        "%fun_uc_uu = OpTypeFunction %uchar %uint %uint\n"
+        "%fun_u_uu = OpTypeFunction %uint %uint %uint\n"
+        "%fun_u_uuu = OpTypeFunction %uint %uint %uint %uint\n"
+        "%fun_u3_u3uu = OpTypeFunction %v3uint %v3uint %uint %uint\n";
+}
+
+static Coords GetCoordsKind(const Function &F) {
+  if (F.arg_size() <= 1)
+    return Coords::None;
+
+  DEBUG(F.getFunctionType()->dump());
+
+  SmallVector<const Argument *, 4> Args;
+  Args.reserve(F.arg_size());
+  for (const auto &Arg : F.args())
+    Args.push_back(&Arg);
+
+  auto IsInt32 = [](const Argument *Arg) {
+    assert(Arg);
+    auto *Ty = Arg->getType();
+    auto IntTy = dyn_cast<IntegerType>(Ty);
+    if (!IntTy)
+      return false;
+
+    return IntTy->getBitWidth() == 32;
+  };
+
+  size_t LastInt32Num = 0;
+  size_t XPos = -1; // npos - not found.
+  auto RIt = Args.rbegin();
+  const auto REnd = Args.rend();
+  while (RIt != REnd && IsInt32(*RIt)) {
+    if ((*RIt)->getName() == "x")
+      XPos = Args.size() - 1 - LastInt32Num;
+
+    ++LastInt32Num;
+    ++RIt;
+  }
+
+  DEBUG(dbgs() << "Original number of last i32's: " << LastInt32Num << '\n');
+  DEBUG(dbgs() << "X found at position: " << XPos << '\n');
+  if (XPos == size_t(-1) || Args.size() - XPos > size_t(Coords::Last))
+    return Coords::None;
+
+  // Check remaining coordinate names.
+  for (size_t i = 1, c = XPos + 1, e = Args.size(); c != e; ++i, ++c)
+    if (Args[c]->getName() != CoordsNames[i])
+      return Coords::None;
+
+  DEBUG(dbgs() << "Coords: not none!\n");
+
+  return Coords(Args.size() - XPos);
+}
+
+bool ReflectionPass::extractKernelSignatures(
+    const Module &M, SmallVectorImpl<KernelSignature> &Out) {
+  DEBUG(dbgs() << "extractKernelSignatures\n");
+
+  for (const auto &F : M.functions()) {
+    if (F.isDeclaration())
+      continue;
+
+    const auto CoordsKind = GetCoordsKind(F);
+    const auto CoordsNum = unsigned(CoordsKind);
+    if (F.arg_size() != CoordsNum + 1) {
+      // TODO: Handle different arrities (and lack of return value).
+      errs() << "Unsupported kernel signature.\n";
+      return false;
+    }
+
+    const auto *FT = F.getFunctionType();
+    const auto *RT = FT->getReturnType();
+    const auto *ArgT = FT->params()[0];
+    Out.push_back(
+        {TypeToString(RT), F.getName(), TypeToString(ArgT), GetCoordsKind(F)});
+    DEBUG(Out.back().dump());
+  }
+
+  if (Out.size() != 1) {
+    // TODO: recognize non-kernel functions and don't bail out here.
+    errs() << "Unsupported number of kernels\n";
+    return false;
+  }
+
+  return true;
+}
+
+bool ReflectionPass::emitKernelTypes(const KernelSignature &Kernel) {
+  DEBUG(dbgs() << "emitKernelTypes\n");
+
+  const auto *RTMapping = getMappingOrPrintError(Kernel.returnType);
+  const auto *ArgTMapping = getMappingOrPrintError(Kernel.argumentType);
+
+  if (!RTMapping || !ArgTMapping)
+    return false;
+
+  OS << '\n' << "%kernel_function_ty = OpTypeFunction " << RTMapping->SPIRVTy
+     << ' ' << ArgTMapping->SPIRVTy;
+
+  const auto CoordsNum = unsigned(Kernel.coordsKind);
+  for (size_t i = 0; i != CoordsNum; ++i)
+    OS << " %uint";
+
+  OS << '\n';
+
+  OS << "%ptr_function_ty = OpTypePointer Function " << RTMapping->SPIRVTy
+     << "\n";
+  OS << "%ptr_function_access_ty = OpTypePointer Function "
+     << RTMapping->SPIRVImageReadType << "\n\n";
+
+  return true;
+}
+
+bool ReflectionPass::emitInputImage(const KernelSignature &Kernel) {
+  DEBUG(dbgs() << "emitInputImage\n");
+
+  const auto *ArgTMapping = getMappingOrPrintError(Kernel.argumentType);
+  if (!ArgTMapping)
+    return false;
+
+  OS << "%input_image_ty = OpTypeImage " << ArgTMapping->SPIRVScalarTy
+     << " 2D 0 0 0 2 " << ArgTMapping->SPIRVImageFormat << '\n';
+
+  OS << "%input_image_ptr_ty = OpTypePointer UniformConstant "
+     << "%input_image_ty\n";
+
+  OS << "%input_image = OpVariable %input_image_ptr_ty UniformConstant\n";
+
+  return true;
+}
+
+void ReflectionPass::emitGLGlobalInput() {
+  DEBUG(dbgs() << "emitGLGlobalInput\n");
+
+  OS << '\n' << "%global_input_ptr_ty = OpTypePointer Input %v3uint\n"
+     << "%global_invocation_id = OpVariable %global_input_ptr_ty Input\n";
+}
+
+bool ReflectionPass::emitOutputImage(const KernelSignature &Kernel) {
+  DEBUG(dbgs() << "emitOutputImage\n");
+
+  const auto *RTMapping = getMappingOrPrintError(Kernel.returnType);
+  if (!RTMapping)
+    return false;
+
+  OS << '\n';
+  OS << "%output_image_ty = OpTypeImage " << RTMapping->SPIRVScalarTy
+     << " 2D 0 0 0 2 " << RTMapping->SPIRVImageFormat << '\n'
+     << "%output_image_ptr_ty = OpTypePointer UniformConstant "
+     << "%output_image_ty\n";
+
+  OS << "%output_image = OpVariable %output_image_ptr_ty Image\n";
+
+  return true;
+}
+
+bool ReflectionPass::emitRSAllocImages(
+    const SmallVectorImpl<RSAllocationInfo> &RSAllocs) {
+  DEBUG(dbgs() << "emitRSAllocImages\n");
+
+  for (const auto &A : RSAllocs) {
+    if (!A.RSElementType) {
+      errs() << "Type of variable " << A.VarName << " not infered.\n";
+      return false;
+    }
+
+    const auto *AMapping = getMappingOrPrintError(*A.RSElementType);
+    if (!AMapping)
+      return false;
+
+    OS << '\n' << A.VarName << "_image_ty"
+       << " = OpTypeImage " << AMapping->SPIRVScalarTy << " 2D 0 0 0 2 "
+       << AMapping->SPIRVImageFormat << '\n' << A.VarName << "_image_ptr_ty"
+       << " = OpTypePointer UniformConstant " << A.VarName << "_image_ty\n";
+
+    OS << A.VarName << "_var = OpVariable " << A.VarName
+       << "_image_ptr_ty Image\n";
+  }
+
+  return true;
+}
+
+bool ReflectionPass::emitConstants(const KernelSignature &Kernel) {
+  DEBUG(dbgs() << "emitConstants\n");
+
+  OS << "\n"
+        "%uint_zero = OpConstant %uint 0\n"
+        "%float_zero = OpConstant %float 0\n";
+
+  return true;
+}
+
+static std::string GenerateConversionFun(const char *Name, const char *FType,
+                                         const char *From, const char *To,
+                                         const char *ConversionOp) {
+  std::ostringstream OS;
+
+  OS << "\n"
+     << "%rs_linker_" << Name << " = OpFunction " << To << " Pure " << FType
+     << "\n"
+     << "%param" << Name << " = OpFunctionParameter " << From << "\n"
+     << "%label" << Name << " = OpLabel\n"
+     << "%res" << Name << " = " << ConversionOp << " " << To << " %param"
+     << Name << "\n"
+     << "      OpReturnValue %res" << Name << "\n"
+     << "      OpFunctionEnd\n";
+
+  return OS.str();
+}
+
+static std::string GenerateEISFun(const char *Name, const char *FType,
+                                  const char *RType,
+                                  const SmallVector<const char *, 4> &ArgTypes,
+                                  const char *InstName) {
+  std::ostringstream OS;
+
+  OS << '\n' << "%rs_linker_" << Name << " = OpFunction " << RType << " Pure "
+     << FType << '\n';
+
+  for (size_t i = 0, e = ArgTypes.size(); i < e; ++i)
+    OS << "%param" << Name << i << " = OpFunctionParameter " << ArgTypes[i]
+       << "\n";
+
+  OS << "%label" << Name << " = OpLabel\n"
+     << "%res" << Name << " = "
+     << "OpExtInst " << RType << " %glsl_ext_ins " << InstName;
+
+  for (size_t i = 0, e = ArgTypes.size(); i < e; ++i)
+    OS << " %param" << Name << i;
+
+  OS << '\n' << "      OpReturnValue %res" << Name << "\n"
+     << "      OpFunctionEnd\n";
+
+  return OS.str();
+}
+
+// This SPIRV function generator relies heavily on future inlining.
+// Currently, the inliner doesn't perform any type checking - it blindly
+// maps function parameters to supplied parameters at call site.
+// It's non-trivial to generate correct SPIRV function signature based only
+// on the LLVM one, and the current design doesn't allow lazy type generation.
+//
+// TODO: Consider less horrible generator design that doesn't rely on lack of
+// type checking in the inliner.
+static std::string GenerateRSGEA(const char *Name, const char *RType,
+                                 StringRef LoadName, Coords CoordsKind) {
+  assert(CoordsKind != Coords::None);
+  std::ostringstream OS;
+
+  OS << "\n"
+     << "%rs_linker_" << Name << " = OpFunction " << RType
+     << " None %rs_inliner_placeholder_ty\n";
+
+  // Since the inliner doesn't perform type checking, function and parameter
+  // types can be anything. %rs_inliner_placeholder_ty is just a placeholder
+  // name that will disappear after inlining.
+
+  OS << "%rs_drop_param_" << Name << " = OpFunctionParameter "
+     << "%rs_inliner_placeholder_ty\n";
+
+  for (size_t i = 0, e = size_t(CoordsKind); i != e; ++i)
+    OS << "%param" << Name << '_' << CoordsNames[i].str()
+       << " = OpFunctionParameter %uint\n";
+
+  OS << "%label" << Name << " = OpLabel\n";
+  OS << "%arg" << Name << " = OpCompositeConstruct %v" << size_t(CoordsKind)
+     << "uint ";
+
+  for (size_t i = 0, e = size_t(CoordsKind); i != e; ++i)
+    OS << "%param" << Name << '_' << CoordsNames[i].str() << ' ';
+
+  OS << '\n';
+
+  OS << "%read" << Name << " = OpImageRead " << RType << ' ' << LoadName.str()
+     << " %arg" << Name << '\n';
+  OS << "      OpReturnValue %read" << Name << '\n';
+  OS << "      OpFunctionEnd\n";
+
+  return OS.str();
+}
+
+// The same remarks as to GenerateRSGEA apply to SEA function generator.
+static std::string GenerateRSSEA(const char *Name, StringRef LoadName,
+                                 Coords CoordsKind) {
+  assert(CoordsKind != Coords::None);
+  std::ostringstream OS;
+
+  // %rs_inliner_placeholder_ty will disappear after inlining.
+  OS << "\n"
+     << "%rs_linker_" << Name << " = OpFunction %void None "
+     << "%rs_inliner_placeholder_ty\n";
+
+  OS << "%rs_placeholder_param_" << Name << " = OpFunctionParameter "
+     << "%rs_inliner_placeholder_ty\n";
+  OS << "%param" << Name << "_new_val = OpFunctionParameter "
+     << "%rs_inliner_placeholder_ty\n";
+
+  for (size_t i = 0, e = size_t(CoordsKind); i != e; ++i)
+    OS << "%param" << Name << '_' << CoordsNames[i].str()
+       << " = OpFunctionParameter %uint\n";
+
+  OS << "%label" << Name << " = OpLabel\n";
+  OS << "%arg" << Name << " = OpCompositeConstruct %v" << size_t(CoordsKind)
+     << "uint ";
+
+  for (size_t i = 0, e = size_t(CoordsKind); i != e; ++i)
+    OS << "%param" << Name << '_' << CoordsNames[i].str() << ' ';
+
+  OS << '\n';
+
+  OS << "OpImageWrite " << LoadName.str() << " %arg" << Name << " %param"
+     << Name << "_new_val\n";
+  OS << "      OpReturn\n";
+  OS << "      OpFunctionEnd\n";
+
+  return OS.str();
+}
+
+void ReflectionPass::emitRTFunctions() {
+  DEBUG(dbgs() << "emitRTFunctions\n");
+
+  // TODO: Emit other runtime functions.
+  // TODO: Generate libary file instead of generating functions below
+  // every compilation.
+
+  // Use uints as Khronos' SPIRV converter turns LLVM's i32s into uints.
+
+  OS << GenerateConversionFun("_Z14convert_float4Dv4_h", "%fun_f4_uc4",
+                              "%v4uchar", "%v4float", "OpConvertUToF");
+
+  OS << GenerateConversionFun("_Z14convert_uchar4Dv4_f", "%fun_uc4_f4",
+                              "%v4float", "%v4uchar", "OpConvertFToU");
+
+  OS << GenerateConversionFun("_Z14convert_float3Dv3_h", "%fun_f3_uc3",
+                              "%v3uchar", "%v3float", "OpConvertUToF");
+
+  OS << GenerateConversionFun("_Z14convert_uchar3Dv3_f", "%fun_uc3_f3",
+                              "%v3float", "%v3uchar", "OpConvertFToU");
+
+  OS << GenerateConversionFun("_Z12convert_int3Dv3_f", "%fun_u3_f3", "%v3float",
+                              "%v3uint", "OpConvertFToU");
+
+  OS << GenerateConversionFun("_Z14convert_uchar3Dv3_i", "%fun_uc3_u3",
+                              "%v3uint", "%v3uchar", "OpUConvert");
+
+  OS << GenerateConversionFun("_Z14convert_uchar4Dv4_j", "%fun_uc4_u4",
+                              "%v4uint", "%v4uchar", "OpUConvert");
+
+  OS << GenerateConversionFun("_Z13convert_uint4Dv4_h", "%fun_u4_uc4",
+                              "%v4uchar", "%v4uint", "OpUConvert");
+
+  OS << GenerateEISFun("_Z3sinf", "%fun_f_f", "%float", {"%float"}, "Sin");
+  OS << GenerateEISFun("_Z4sqrtf", "%fun_f_f", "%float", {"%float"}, "Sqrt");
+  OS << GenerateEISFun("_Z10native_expf", "%fun_f_f", "%float", {"%float"},
+                       "Exp");
+  OS << GenerateEISFun("_Z3maxii", "%fun_u_uu", "%uint", {"%uint", "%uint"},
+                       "SMax");
+  OS << GenerateEISFun("_Z3minii", "%fun_u_uu", "%uint", {"%uint", "%uint"},
+                       "SMin");
+  OS << GenerateEISFun("_Z3maxff", "%fun_f_ff", "%float", {"%float", "%float"},
+                       "FMax");
+  OS << GenerateEISFun("_Z3minff", "%fun_f_ff", "%float", {"%float", "%float"},
+                       "FMin");
+  OS << GenerateEISFun("_Z5clampfff", "%fun_f_fff", "%float",
+                       {"%float", "%float", "%float"}, "FClamp");
+  OS << GenerateEISFun("_Z5clampiii", "%fun_u_uuu", "%uint",
+                       {"%uint", "%uint", "%uint"}, "SClamp");
+
+  OS << R"(
+%rs_linker__Z3dotDv2_fS_ = OpFunction %float Pure %fun_f_f2f2
+%param_Z3dotDv2_fS_0 = OpFunctionParameter %v2float
+%param_Z3dotDv2_fS_1 = OpFunctionParameter %v2float
+%label_Z3dotDv2_fS = OpLabel
+%res_Z3dotDv2_fS = OpDot %float %param_Z3dotDv2_fS_0 %param_Z3dotDv2_fS_1
+      OpReturnValue %res_Z3dotDv2_fS
+      OpFunctionEnd
+)";
+
+  OS << R"(
+%rs_linker__Z3dotDv3_fS_ = OpFunction %float Pure %fun_f_f3f3
+%param_Z3dotDv3_fS_0 = OpFunctionParameter %v3float
+%param_Z3dotDv3_fS_1 = OpFunctionParameter %v3float
+%label_Z3dotDv3_fS = OpLabel
+%res_Z3dotDv3_fS = OpDot %float %param_Z3dotDv3_fS_0 %param_Z3dotDv3_fS_1
+      OpReturnValue %res_Z3dotDv3_fS
+      OpFunctionEnd
+)";
+
+  OS << R"(
+%rs_linker_rsUnpackColor8888 = OpFunction %v4float Pure %fun_f4_uc4
+%paramrsUnpackColor88880 = OpFunctionParameter %v4uchar
+%labelrsUnpackColor8888 = OpLabel
+%castedUnpackColor8888 = OpBitcast %uint %paramrsUnpackColor88880
+%resrsUnpackColor8888 = OpExtInst %v4float %glsl_ext_ins UnpackUnorm4x8 %castedUnpackColor8888
+      OpReturnValue %resrsUnpackColor8888
+      OpFunctionEnd
+)";
+
+  OS << R"(
+%rs_linker__Z17rsPackColorTo8888Dv4_f = OpFunction %v4uchar Pure %fun_uc4_f4
+%param_Z17rsPackColorTo8888Dv4_f0 = OpFunctionParameter %v4float
+%label_Z17rsPackColorTo8888Dv4_f = OpLabel
+%res_Z17rsPackColorTo8888Dv4_f = OpExtInst %uint %glsl_ext_ins PackUnorm4x8 %param_Z17rsPackColorTo8888Dv4_f0
+%casted_Z17rsPackColorTo8888Dv4_f = OpBitcast %v4uchar %res_Z17rsPackColorTo8888Dv4_f
+      OpReturnValue %casted_Z17rsPackColorTo8888Dv4_f
+      OpFunctionEnd
+)";
+
+  OS << R"(
+%rs_linker__Z5clampDv3_fff = OpFunction %v3float Pure %fun_f3_f3ff
+%param_Z5clampDv3_fff0 = OpFunctionParameter %v3float
+%param_Z5clampDv3_fff1 = OpFunctionParameter %float
+%param_Z5clampDv3_fff2 = OpFunctionParameter %float
+%label_Z5clampDv3_fff = OpLabel
+%arg1_Z5clampDv3_fff = OpCompositeConstruct %v3float %param_Z5clampDv3_fff1 %param_Z5clampDv3_fff1 %param_Z5clampDv3_fff1
+%arg2_Z5clampDv3_fff = OpCompositeConstruct %v3float %param_Z5clampDv3_fff2 %param_Z5clampDv3_fff2 %param_Z5clampDv3_fff2
+%res_Z5clampDv3_fff = OpExtInst %v3float %glsl_ext_ins FClamp %param_Z5clampDv3_fff0 %arg1_Z5clampDv3_fff %arg2_Z5clampDv3_fff
+      OpReturnValue %res_Z5clampDv3_fff
+      OpFunctionEnd
+)";
+
+  OS << R"(
+%rs_linker__Z5clampDv3_iii = OpFunction %v3uint Pure %fun_u3_u3uu
+%param_Z5clampDv3_iii0 = OpFunctionParameter %v3uint
+%param_Z5clampDv3_iii1 = OpFunctionParameter %uint
+%param_Z5clampDv3_iii2 = OpFunctionParameter %uint
+%label_Z5clampDv3_iii = OpLabel
+%arg1_Z5clampDv3_iii = OpCompositeConstruct %v3uint %param_Z5clampDv3_iii1 %param_Z5clampDv3_iii1 %param_Z5clampDv3_iii1
+%arg2_Z5clampDv3_iii = OpCompositeConstruct %v3uint %param_Z5clampDv3_iii2 %param_Z5clampDv3_iii2 %param_Z5clampDv3_iii2
+%res_Z5clampDv3_iii = OpExtInst %v3uint %glsl_ext_ins UClamp %param_Z5clampDv3_iii0 %arg1_Z5clampDv3_iii %arg2_Z5clampDv3_iii
+      OpReturnValue %res_Z5clampDv3_iii
+      OpFunctionEnd
+)";
+}
+
+bool ReflectionPass::emitRSAllocFunctions(
+    Module &M, const SmallVectorImpl<RSAllocationInfo> &RSAllocs,
+    const SmallVectorImpl<RSAllocationCallInfo> &RSAllocAccesses) {
+  DEBUG(dbgs() << "emitRSAllocFunctions\n");
+
+  for (const auto &Access : RSAllocAccesses) {
+    solidifyRSAllocAccess(M, Access);
+
+    auto *Fun = Access.FCall->getCalledFunction();
+    if (!Fun)
+      return false;
+
+    const auto FName = Fun->getName();
+    auto *ETMapping = getMappingOrPrintError(Access.RSElementTy);
+    if (!ETMapping)
+      return false;
+
+    const auto ElementTy = ETMapping->SPIRVTy;
+    const std::string LoadName = Access.RSAlloc.VarName + "_load";
+
+    if (Access.Kind == RSAllocAccessKind::GEA)
+      OS << GenerateRSGEA(FName.str().c_str(), ElementTy.c_str(),
+                          LoadName.c_str(), Coords::XY);
+    else
+      OS << GenerateRSSEA(FName.str().c_str(), LoadName.c_str(), Coords::XY);
+  }
+
+  return true;
+}
+
+bool ReflectionPass::emitMain(
+    const KernelSignature &Kernel,
+    const SmallVectorImpl<RSAllocationInfo> &RSAllocs) {
+  DEBUG(dbgs() << "emitMain\n");
+
+  const auto *RTMapping = getMappingOrPrintError(Kernel.returnType);
+  const auto *ArgTMapping = getMappingOrPrintError(Kernel.argumentType);
+
+  if (!RTMapping || !ArgTMapping)
+    return false;
+
+  OS << '\n';
+  OS << "       %main = OpFunction %void None %fun_void\n"
+        "%lablel_main = OpLabel\n"
+        "%input_pixel = OpVariable %ptr_function_access_ty Function\n"
+        "        %res = OpVariable %ptr_function_ty Function\n"
+        " %image_load = OpLoad %input_image_ty %input_image\n"
+        "%coords_load = OpLoad %v3uint %global_invocation_id\n"
+        "   %coords_x = OpCompositeExtract %uint %coords_load 0\n"
+        "   %coords_y = OpCompositeExtract %uint %coords_load 1\n"
+        "   %coords_z = OpCompositeExtract %uint %coords_load 2\n"
+        "   %shuffled = OpVectorShuffle %v2uint %coords_load %coords_load 0 1\n"
+        "  %bitcasted = OpBitcast %v2int %shuffled\n";
+
+  OS << " %image_read = OpImageRead " << ArgTMapping->SPIRVImageReadType
+     << " %image_load %bitcasted\n"
+        "               OpStore %input_pixel %image_read\n";
+
+  // TODO: Handle vector types of width different than 4.
+  if (RTMapping->isVectorTy) {
+    OS << " %input_load = OpLoad " << ArgTMapping->SPIRVTy << " %input_pixel\n";
+  } else {
+    OS << "%input_access_chain = OpAccessChain %ptr_function_ty "
+          "%input_pixel %uint_zero\n"
+       << " %input_load = OpLoad " << ArgTMapping->SPIRVTy
+       << " %input_access_chain\n";
+  }
+
+  for (const auto &A : RSAllocs)
+    OS << A.VarName << "_load = OpLoad " << A.VarName << "_image_ty "
+       << A.VarName << "_var\n";
+
+  OS << "%kernel_call = OpFunctionCall " << ArgTMapping->SPIRVTy
+     << " %RS_SPIRV_DUMMY_ %input_load";
+
+  const auto CoordsNum = size_t(Kernel.coordsKind);
+  for (size_t i = 0; i != CoordsNum; ++i)
+    OS << " %coords_" << CoordsNames[i].str();
+
+  OS << '\n';
+
+  OS << "               OpStore %res %kernel_call\n"
+        "%output_load = OpLoad %output_image_ty %output_image\n";
+  OS << "   %res_load = OpLoad " << RTMapping->SPIRVTy << " %res\n";
+
+  if (!RTMapping->isVectorTy) {
+    OS << "%composite_constructed = OpCompositeConstruct "
+       << RTMapping->SPIRVImageReadType;
+    for (size_t i = 0; i < RTMapping->vectorWidth; ++i)
+      OS << " %res_load";
+
+    OS << "\n"
+          "               OpImageWrite %output_load %bitcasted "
+          "%composite_constructed\n";
+
+  } else {
+    OS << "               OpImageWrite %output_load %bitcasted %res_load\n";
+  }
+
+  OS << "               OpReturn\n"
+        "               OpFunctionEnd\n";
+
+  OS << "%RS_SPIRV_DUMMY_ = OpFunction " << RTMapping->SPIRVTy
+     << " None %kernel_function_ty\n";
+
+  OS << "          %p = OpFunctionParameter " << ArgTMapping->SPIRVTy << '\n';
+
+  for (size_t i = 0; i != CoordsNum; ++i)
+    OS << "          %coords_param_" << CoordsNames[i].str()
+       << " = OpFunctionParameter %uint\n";
+
+  OS << "         %11 = OpLabel\n"
+        "               OpReturnValue %p\n"
+        "               OpFunctionEnd\n";
+
+  return true;
+}
+
+} // namespace rs2spirv
diff --git a/rsov/compiler/ReflectionPass.h b/rsov/compiler/ReflectionPass.h
new file mode 100644
index 0000000..6770eaf
--- /dev/null
+++ b/rsov/compiler/ReflectionPass.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS_SPIRV_REFLECTION_PASS_H
+#define RS_SPIRV_REFLECTION_PASS_H
+
+#include "llvm/ADT/StringRef.h"
+
+#include <iosfwd>
+
+namespace llvm {
+class ModulePass;
+}
+
+namespace bcinfo {
+class MetadataExtractor;
+}
+
+namespace rs2spirv {
+
+llvm::ModulePass *createReflectionPass(std::ostream &OS,
+                                       bcinfo::MetadataExtractor &ME);
+
+} // namespace rs2spirv
+
+#endif
\ No newline at end of file
diff --git a/rsov/compiler/rs2spirv.cpp b/rsov/compiler/rs2spirv.cpp
new file mode 100644
index 0000000..ca3d956
--- /dev/null
+++ b/rsov/compiler/rs2spirv.cpp
@@ -0,0 +1,190 @@
+/*

+ * Copyright 2016, The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *     http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+#include "RSSPIRVWriter.h"

+#include "llvm/Bitcode/ReaderWriter.h"

+#include "llvm/IR/LLVMContext.h"

+#include "llvm/IR/Module.h"

+#include "llvm/Support/CommandLine.h"

+#include "llvm/Support/DataStream.h"

+#include "llvm/Support/Debug.h"

+#include "llvm/Support/FileSystem.h"

+#include "llvm/Support/PrettyStackTrace.h"

+#include "llvm/Support/SPIRV.h"

+#include "llvm/Support/Signals.h"

+#include "llvm/Support/ToolOutputFile.h"

+#include "llvm/Support/raw_ostream.h"

+#include "unit_tests/TestRunner.h"

+

+#include <fstream>

+#include <iterator>

+

+#define DEBUG_TYPE "rs2spirv"

+

+namespace kExt {

+const char SPIRVBinary[] = ".spv";

+}

+

+using namespace llvm;

+

+static cl::opt<std::string> InputFile(cl::Positional, cl::desc("<input file>"),

+                                      cl::init("-"));

+

+static cl::opt<std::string> OutputFile("o",

+                                       cl::desc("Override output filename"),

+                                       cl::value_desc("filename"));

+

+static cl::opt<std::string>

+    KernelFile("lk", cl::desc("File with a compute shader kernel"),

+               cl::value_desc("kernel.spt"));

+

+static cl::opt<std::string> WrapperFile(

+    "lw",

+    cl::desc("Generated wrapper file"

+             "(with entrypoint function and input/output images or buffers)"),

+    cl::value_desc("wrapper.spt"));

+

+static cl::opt<bool> IsPrintAsWords(

+    "print-as-words",

+    cl::desc("Print an input .spv file as a brace-init-list of words"),

+    cl::init(false));

+

+static cl::opt<bool>

+    IsRegularization("s",

+                     cl::desc("Regularize LLVM to be representable by SPIR-V"));

+

+#ifdef RS2SPIRV_DEBUG

+static cl::opt<bool> RunTests("run-tests", cl::desc("Run unit tests"),

+                              cl::init(false));

+#endif

+

+namespace SPIRV {

+extern bool SPIRVUseTextFormat;

+}

+

+static std::string removeExt(const std::string &FileName) {

+  size_t Pos = FileName.find_last_of(".");

+  if (Pos != std::string::npos)

+    return FileName.substr(0, Pos);

+  return FileName;

+}

+

+static int convertLLVMToSPIRV() {

+  if (!KernelFile.empty() && !WrapperFile.empty()) {

+    DEBUG(dbgs() << "Link " << KernelFile << " into " << WrapperFile << "\n");

+    if (!rs2spirv::Link(KernelFile, WrapperFile, OutputFile)) {

+      errs() << "Linking failed!\n\n";

+      return -1;

+    }

+    return 0;

+  }

+

+  LLVMContext Context;

+

+  std::string Err;

+  auto DS = getDataFileStreamer(InputFile, &Err);

+  if (!DS) {

+    errs() << "Fails to open input file: " << Err;

+    return -1;

+  }

+

+  ErrorOr<std::unique_ptr<Module>> MOrErr =

+      getStreamedBitcodeModule(InputFile, std::move(DS), Context);

+

+  if (std::error_code EC = MOrErr.getError()) {

+    errs() << "Fails to load bitcode: " << EC.message();

+    return -1;

+  }

+

+  std::unique_ptr<Module> M = std::move(*MOrErr);

+

+  if (std::error_code EC = M->materializeAll()) {

+    errs() << "Fails to materialize: " << EC.message();

+    return -1;

+  }

+

+  if (OutputFile.empty()) {

+    if (InputFile == "-")

+      OutputFile = "-";

+    else

+      OutputFile = removeExt(InputFile) + kExt::SPIRVBinary;

+  }

+

+  llvm::StringRef outFile(OutputFile);

+  std::error_code EC;

+  llvm::raw_fd_ostream OFS(outFile, EC, llvm::sys::fs::F_None);

+  if (!rs2spirv::WriteSPIRV(M.get(), OFS, Err)) {

+    errs() << "Fails to save LLVM as SPIRV: " << Err << '\n';

+    return -1;

+  }

+

+  return 0;

+}

+

+static int printAsWords() {

+  std::ifstream IFS(InputFile, std::ios::binary);

+  if (!IFS.good()) {

+    errs() << "Could not open input file\n";

+    return -1;

+  }

+

+  uint64_t FSize;

+  const auto EC = llvm::sys::fs::file_size(InputFile, FSize);

+  if (EC) {

+    errs() << "Fails to open input file: " << EC.message() << '\n';

+    return -1;

+  }

+

+  if (FSize % 4 != 0) {

+    errs() << "Input file is not a stream of words. Size mismatch.\n";

+    return -1;

+  }

+

+  std::istreambuf_iterator<char> It(IFS);

+  const std::istreambuf_iterator<char> End;

+

+  outs() << '{';

+

+  while (It != End) {

+    uint32_t val = 0;

+    // Mask the sign-extended values to prevent higher bits pollution.

+    val += uint32_t(*(It++)) & 0x000000FF;

+    val += (uint32_t(*(It++)) << 8) & 0x0000FF00;

+    val += (uint32_t(*(It++)) << 16) & 0x00FF0000;

+    val += (uint32_t(*(It++)) << 24) & 0xFF000000;

+    outs() << val << (It != End ? ", " : "};\n");

+  }

+

+  return 0;

+}

+

+int main(int ac, char **av) {

+  EnablePrettyStackTrace();

+  sys::PrintStackTraceOnErrorSignal(av[0]);

+  PrettyStackTraceProgram X(ac, av);

+

+  cl::ParseCommandLineOptions(ac, av, "RenderScript to SPIRV translator");

+

+#ifdef RS2SPIRV_DEBUG

+  if (RunTests)

+    return rs2spirv::TestRunnerContext::runTests();

+#endif

+

+  if (IsPrintAsWords)

+    return printAsWords();

+

+  return convertLLVMToSPIRV();

+}

diff --git a/rsov/compiler/rs2spirv_driver.sh b/rsov/compiler/rs2spirv_driver.sh
new file mode 100755
index 0000000..085812e
--- /dev/null
+++ b/rsov/compiler/rs2spirv_driver.sh
@@ -0,0 +1,48 @@
+# Copyright 2016, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#!/bin/bash
+
+if [ $# -lt 3 ]; then
+  echo 1>&2 "$0: not enough arguments"
+  echo 1>&2 $#
+  exit 2
+fi
+
+AND_HOME=$ANDROID_BUILD_TOP
+SPIRV_TOOLS_PATH=$1
+
+script_name="$2"
+script=${2%.*} # Remove extension.
+
+output_folder="$3"
+mkdir -p $output_folder
+
+eval llvm-rs-cc -o "$output_folder" -S -emit-llvm -Wall -Werror -target-api 24 \
+  -I "$AND_HOME/external/clang/lib/Headers" -I "$AND_HOME/frameworks/rs/scriptc" \
+  "$script_name"
+eval llvm-as "$output_folder/bc32/$script.ll" -o "$output_folder/$script.bc"
+eval rs2spirv "$output_folder/$script.bc" -o "$output_folder/$script.rs.spv" \
+              -wo "$output_folder/$script.w.spt" -debug
+eval "$SPIRV_TOOLS_PATH/spirv-dis" "$output_folder/$script.rs.spv" \
+              --no-color > "$output_folder/$script.rs.spt"
+eval rs2spirv -o "$output_folder/$script.spt" -lk "$output_folder/$script.rs.spt" \
+              -lw "$output_folder/$script.w.spt" -debug
+eval "$SPIRV_TOOLS_PATH/spirv-as" "$output_folder/$script.spt" \
+              -o "$output_folder/$script.spv"
+echo
+eval rs2spirv "$output_folder/$script.spv" -print-as-words
+echo
+eval "$SPIRV_TOOLS_PATH/spirv-val" "$output_folder/$script.spv"
+echo
diff --git a/rsov/compiler/tests/globals/mul.ll b/rsov/compiler/tests/globals/mul.ll
new file mode 100644
index 0000000..339f2bf
--- /dev/null
+++ b/rsov/compiler/tests/globals/mul.ll
@@ -0,0 +1,55 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: OpMemberDecorate %rs_linker_struct___GPUBuffer 0 Offset 0
+; CHECK: OpDecorate %rs_linker_struct___GPUBuffer BufferBlock
+; CHECK: OpDecorate %rs_linker___GPUBlock DescriptorSet 0
+; CHECK: OpDecorate %rs_linker___GPUBlock Binding 2
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %float
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpInBoundsPtrAccessChain
+
+@c1 = common global float 0.000000e+00, align 4
+
+; Function Attrs: norecurse nounwind readonly
+define <4 x float> @k1(<4 x float> %in) #0 {
+entry:
+  %0 = load float, float* @c1, align 4, !tbaa !11
+  %splat.splatinsert = insertelement <4 x float> undef, float %0, i32 0
+  %splat.splat = shufflevector <4 x float> %splat.splatinsert, <4 x float> undef, <4 x i32> zeroinitializer
+  %mul = fmul <4 x float> %splat.splat, %in
+  ret <4 x float> %mul
+}
+
+attributes #0 = { norecurse nounwind readonly "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6}
+!\23rs_object_slots = !{}
+!\23rs_export_foreach_name = !{!7, !8}
+!\23rs_export_foreach = !{!9, !10}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"1"}
+!7 = !{!"root"}
+!8 = !{!"k1"}
+!9 = !{!"0"}
+!10 = !{!"35"}
+!11 = !{!12, !12, i64 0}
+!12 = !{!"float", !13, i64 0}
+!13 = !{!"omnipotent char", !14, i64 0}
+!14 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/globals/mul.rs b/rsov/compiler/tests/globals/mul.rs
new file mode 100644
index 0000000..77c5c52
--- /dev/null
+++ b/rsov/compiler/tests/globals/mul.rs
@@ -0,0 +1,23 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+float c1;
+
+float4 __attribute__((kernel)) k1(float4 in) {
+  return c1 * in;
+}
diff --git a/rsov/compiler/tests/globals/mul2.ll b/rsov/compiler/tests/globals/mul2.ll
new file mode 100644
index 0000000..dcdd9d4
--- /dev/null
+++ b/rsov/compiler/tests/globals/mul2.ll
@@ -0,0 +1,67 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: OpMemberDecorate %rs_linker_struct___GPUBuffer 0 Offset 0
+; CHECK: OpMemberDecorate %rs_linker_struct___GPUBuffer 1 Offset 8
+; CHECK: OpMemberDecorate %rs_linker_struct___GPUBuffer 2 Offset 24
+; CHECK: OpMemberDecorate %rs_linker_struct___GPUBuffer 3 Offset 28
+; CHECK: OpDecorate %rs_linker_struct___GPUBuffer BufferBlock
+; CHECK: OpDecorate %rs_linker___GPUBlock DescriptorSet 0
+; CHECK: OpDecorate %rs_linker___GPUBlock Binding 2
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %float %v4float %uchar %uint
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpInBoundsPtrAccessChain
+
+@c1 = common global float 0.000000e+00, align 4
+@c2 = common global <4 x float> zeroinitializer, align 16
+@d = common global i8 0, align 1
+@i = common global i32 0, align 4
+
+; Function Attrs: norecurse nounwind readonly
+define <4 x float> @k1(<4 x float> %in) #0 {
+entry:
+  %0 = load float, float* @c1, align 4, !tbaa !14
+  %splat.splatinsert = insertelement <4 x float> undef, float %0, i32 0
+  %splat.splat = shufflevector <4 x float> %splat.splatinsert, <4 x float> undef, <4 x i32> zeroinitializer
+  %mul = fmul <4 x float> %splat.splat, %in
+  %1 = load <4 x float>, <4 x float>* @c2, align 16, !tbaa !18
+  %mul1 = fmul <4 x float> %1, %mul
+  ret <4 x float> %mul1
+}
+
+attributes #0 = { norecurse nounwind readonly "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6, !7, !8, !9}
+!\23rs_object_slots = !{}
+!\23rs_export_foreach_name = !{!10, !11}
+!\23rs_export_foreach = !{!12, !13}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"1"}
+!7 = !{!"d", !"7"}
+!8 = !{!"c2", !"float4"}
+!9 = !{!"i", !"5"}
+!10 = !{!"root"}
+!11 = !{!"k1"}
+!12 = !{!"0"}
+!13 = !{!"35"}
+!14 = !{!15, !15, i64 0}
+!15 = !{!"float", !16, i64 0}
+!16 = !{!"omnipotent char", !17, i64 0}
+!17 = !{!"Simple C/C++ TBAA"}
+!18 = !{!16, !16, i64 0}
diff --git a/rsov/compiler/tests/globals/mul2.rs b/rsov/compiler/tests/globals/mul2.rs
new file mode 100644
index 0000000..78e67ac
--- /dev/null
+++ b/rsov/compiler/tests/globals/mul2.rs
@@ -0,0 +1,26 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+float c1;
+uchar d;
+float4 c2;
+int i;
+
+float4 __attribute__((kernel)) k1(float4 in) {
+  return c1 * in * c2;
+}
diff --git a/rsov/compiler/tests/image/blend.ll b/rsov/compiler/tests/image/blend.ll
new file mode 100644
index 0000000..e9ac0cb
--- /dev/null
+++ b/rsov/compiler/tests/image/blend.ll
@@ -0,0 +1,74 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: OpMemberDecorate %rs_linker_struct___GPUBuffer 0 Offset 0
+; CHECK: OpDecorate %rs_linker_struct___GPUBuffer BufferBlock
+; CHECK: OpDecorate %rs_linker___GPUBlock DescriptorSet 0
+; CHECK: OpDecorate %rs_linker___GPUBlock Binding 2
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %uchar
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK-NOT: %rs_linker__Z14convert_uchar4Dv4_j = OpFunction %v4uchar
+; CHECK-NOT: %rs_linker__Z13convert_uint4Dv4_h = OpFunction %v4uint
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpUConvert %v4uint
+; CHECK: OpUConvert %v4uchar
+
+@alpha = global i8 0, align 1
+
+; Function Attrs: nounwind readonly
+define <4 x i8> @setImageAlpha(<4 x i8> %in, i32 %x, i32 %y) #0 {
+entry:
+  %call = tail call <4 x i32> @_Z13convert_uint4Dv4_h(<4 x i8> %in) #2
+  %0 = load i8, i8* @alpha, align 1, !tbaa !11
+  %conv = zext i8 %0 to i32
+  %splat.splatinsert = insertelement <4 x i32> undef, i32 %conv, i32 0
+  %splat.splat = shufflevector <4 x i32> %splat.splatinsert, <4 x i32> undef, <4 x i32> zeroinitializer
+  %mul = mul <4 x i32> %splat.splat, %call
+  %shr = lshr <4 x i32> %mul, <i32 8, i32 8, i32 8, i32 8>
+  %call1 = tail call <4 x i8> @_Z14convert_uchar4Dv4_j(<4 x i32> %shr) #2
+  %1 = insertelement <4 x i8> %call1, i8 %0, i32 3
+  ret <4 x i8> %1
+}
+
+; Function Attrs: nounwind readnone
+declare <4 x i8> @_Z14convert_uchar4Dv4_j(<4 x i32>) #1
+
+; Function Attrs: nounwind readnone
+declare <4 x i32> @_Z13convert_uint4Dv4_h(<4 x i8>) #1
+
+attributes #0 = { nounwind readonly "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind readnone }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6}
+!\23rs_object_slots = !{}
+!\23rs_export_foreach_name = !{!7, !8}
+!\23rs_export_foreach = !{!9, !10}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"alpha", !"7"}
+!7 = !{!"root"}
+!8 = !{!"setImageAlpha"}
+!9 = !{!"0"}
+!10 = !{!"59"}
+!11 = !{!12, !12, i64 0}
+!12 = !{!"omnipotent char", !13, i64 0}
+!13 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/image/blend.rs b/rsov/compiler/tests/image/blend.rs
new file mode 100644
index 0000000..ea1d323
--- /dev/null
+++ b/rsov/compiler/tests/image/blend.rs
@@ -0,0 +1,26 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar alpha = 0x0;
+
+uchar4 __attribute__((kernel)) setImageAlpha(uchar4 in, uint32_t x, uint32_t y) {
+    uchar4 out;
+    out.rgba = convert_uchar4((convert_uint4(in.rgba) * alpha) >> (uint4)8);
+    out.a = alpha;
+    return out;
+}
diff --git a/rsov/compiler/tests/image/contrast.ll b/rsov/compiler/tests/image/contrast.ll
new file mode 100644
index 0000000..1e77c5d
--- /dev/null
+++ b/rsov/compiler/tests/image/contrast.ll
@@ -0,0 +1,109 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %float %float
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK-NOT: %rs_linker__Z14convert_float3Dv3_h = OpFunction %v3float
+; CHECK-NOT: %rs_linker__Z14convert_uchar3Dv3_f = OpFunction %v3uchar
+; CHECK-NOT: %rs_linker__Z5clampDv3_fff = OpFunction %v3float
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpConvertUToF %v3float
+; CHECK: OpExtInst %v3float %glsl_ext_ins FClamp
+; CHECK: OpConvertFToU %v3uchar
+
+; Undef value representation.
+; CHECK-NOT: 4294967295
+
+@brightM = internal unnamed_addr global float 0.000000e+00, align 4
+@brightC = internal unnamed_addr global float 0.000000e+00, align 4
+
+; Function Attrs: nounwind
+define void @setBright(float %v) #0 {
+entry:
+  %div = fdiv float %v, 1.000000e+02
+  %call = tail call float @_Z3powff(float 2.000000e+00, float %div) #4
+  store float %call, float* @brightM, align 4, !tbaa !11
+  %mul = fmul float %call, 1.270000e+02
+  %sub = fsub float 1.270000e+02, %mul
+  store float %sub, float* @brightC, align 4, !tbaa !11
+  ret void
+}
+
+; Function Attrs: nounwind readnone
+declare float @_Z3powff(float, float) #1
+
+; Function Attrs: nounwind readonly
+define <4 x i8> @contrast(<4 x i8> %in) #2 {
+entry:
+  %0 = shufflevector <4 x i8> %in, <4 x i8> undef, <3 x i32> <i32 0, i32 1, i32 2>
+  %call = tail call <3 x float> @_Z14convert_float3Dv3_h(<3 x i8> %0) #4
+  %1 = load float, float* @brightM, align 4, !tbaa !11
+  %splat.splatinsert = insertelement <3 x float> undef, float %1, i32 0
+  %splat.splat = shufflevector <3 x float> %splat.splatinsert, <3 x float> undef, <3 x i32> zeroinitializer
+  %mul = fmul <3 x float> %call, %splat.splat
+  %2 = load float, float* @brightC, align 4, !tbaa !11
+  %splat.splatinsert1 = insertelement <3 x float> undef, float %2, i32 0
+  %splat.splat2 = shufflevector <3 x float> %splat.splatinsert1, <3 x float> undef, <3 x i32> zeroinitializer
+  %add = fadd <3 x float> %mul, %splat.splat2
+  %call4 = tail call <3 x float> @_Z5clampDv3_fff(<3 x float> %add, float 0.000000e+00, float 2.550000e+02) #4
+  %call5 = tail call <3 x i8> @_Z14convert_uchar3Dv3_f(<3 x float> %call4) #4
+  %3 = shufflevector <3 x i8> %call5, <3 x i8> undef, <4 x i32> <i32 0, i32 1, i32 2, i32 undef>
+  %4 = insertelement <4 x i8> %3, i8 -1, i32 3
+  ret <4 x i8> %4
+}
+
+; Function Attrs: nounwind readnone
+declare <3 x float> @_Z14convert_float3Dv3_h(<3 x i8>) #1
+
+; Function Attrs: nounwind readnone
+declare <3 x i8> @_Z14convert_uchar3Dv3_f(<3 x float>) #1
+
+; Function Attrs: nounwind readnone
+declare <3 x float> @_Z5clampDv3_fff(<3 x float>, float, float) #1
+
+; Function Attrs: noinline nounwind
+define void @.helper_setBright({ float }* nocapture) #3 {
+entry:
+  %1 = getelementptr inbounds { float }, { float }* %0, i32 0, i32 0
+  %2 = load float, float* %1, align 4
+  tail call void @setBright(float %2)
+  ret void
+}
+
+attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind readonly "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #3 = { noinline nounwind }
+attributes #4 = { nounwind readnone }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_func = !{!6}
+!\23rs_export_foreach_name = !{!7, !8}
+!\23rs_export_foreach = !{!9, !10}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!".helper_setBright"}
+!7 = !{!"root"}
+!8 = !{!"contrast"}
+!9 = !{!"0"}
+!10 = !{!"35"}
+!11 = !{!12, !12, i64 0}
+!12 = !{!"float", !13, i64 0}
+!13 = !{!"omnipotent char", !14, i64 0}
+!14 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/image/contrast.rs b/rsov/compiler/tests/image/contrast.rs
new file mode 100644
index 0000000..dd95ad3
--- /dev/null
+++ b/rsov/compiler/tests/image/contrast.rs
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+static float brightM = 0.f;
+static float brightC = 0.f;
+
+void setBright(float v) {
+    brightM = pow(2.f, v / 100.f);
+    brightC = 127.f - brightM * 127.f;
+}
+
+uchar4 __attribute__((kernel)) contrast(uchar4 in) {
+    float3 v = convert_float3(in.rgb) * brightM + brightC;
+    uchar4 o;
+    o.rgb = convert_uchar3(clamp(v, 0.f, 255.f));
+    o.a = 0xff;
+    return o;
+}
\ No newline at end of file
diff --git a/rsov/compiler/tests/image/copy.ll b/rsov/compiler/tests/image/copy.ll
new file mode 100644
index 0000000..ea5226f
--- /dev/null
+++ b/rsov/compiler/tests/image/copy.ll
@@ -0,0 +1,31 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %main = OpFunction
+
+; Function Attrs: norecurse nounwind readnone
+define <4 x i8> @copy(<4 x i8> %v_in) #0 {
+entry:
+  ret <4 x i8> %v_in
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_foreach_name = !{!6, !7}
+!\23rs_export_foreach = !{!8, !9}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"root"}
+!7 = !{!"copy"}
+!8 = !{!"0"}
+!9 = !{!"35"}
diff --git a/rsov/compiler/tests/image/copy.rs b/rsov/compiler/tests/image/copy.rs
new file mode 100644
index 0000000..1f3ae2e
--- /dev/null
+++ b/rsov/compiler/tests/image/copy.rs
@@ -0,0 +1,7 @@
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar4 __attribute__((kernel)) copy(uchar4 v_in) {
+    return v_in;
+}
diff --git a/rsov/compiler/tests/lit.cfg b/rsov/compiler/tests/lit.cfg
new file mode 100644
index 0000000..d9ab794
--- /dev/null
+++ b/rsov/compiler/tests/lit.cfg
@@ -0,0 +1,56 @@
+# -*- Python -*-
+
+# Configuration file for the 'lit' test runner.
+
+import re
+
+# name: The name of this test suite.
+config.name = 'rs2spirv'
+
+# suffixes: A list of file extensions to treat as test files.
+config.suffixes = ['.ll']
+
+# testFormat: The test format to use to interpret tests.
+import lit.formats
+config.test_format = lit.formats.ShTest()
+
+ANDROID_HOST_OUT = os.getenv("ANDROID_HOST_OUT")
+ANDROID_PRODUCT_OUT = os.getenv("ANDROID_PRODUCT_OUT")
+
+if not ANDROID_HOST_OUT or not ANDROID_PRODUCT_OUT:
+    import sys
+    sys.exit(1)
+
+# test_source_root: The path where tests are located (default is the test suite
+# root).
+config.test_source_root = None
+config.test_exec_root = os.path.join(ANDROID_HOST_OUT, 'tests', 'rs2spirv')
+
+tools_dir = os.pathsep.join([os.path.join(ANDROID_HOST_OUT, 'bin'),
+                             os.path.join(ANDROID_HOST_OUT, 'lib64'),
+                             os.path.join(ANDROID_PRODUCT_OUT, 'system/lib')])
+
+# Based on LLVM's lit.cfg: "For each occurrence of an llvm tool name
+# as its own word, replace it with the full path to the build directory
+# holding that tool."
+for pattern in [r"\bFileCheck\b",
+                r"\bllvm-as\b",
+                r"\bllvm-dis\b",
+                r"\bllvm-spirv\b",
+                r"\brs2spirv\b",
+                r"\bspirv-as\b",
+                r"\bspirv-dis\b",
+                r"\bspirv-val\b",
+                r"\brs2spirv\b",
+                r"\brs2spirv_lit_driver.sh\b",
+                r"\bopt\b"]:
+    tool_match = re.match(r"^(\\)?((\| )?)\W+b([\.0-9A-Za-z-_]+)\\b\W*$",
+                          pattern)
+    tool_pipe = tool_match.group(2)
+    tool_name = tool_match.group(4)
+    import lit.util
+    tool_path = lit.util.which(tool_name, tools_dir)
+    if not tool_path:
+        lit_config.note("Did not find " + tool_name + " in " + tools_dir)
+        tool_path = os.path.join(tools_dir, tool_name)
+    config.substitutions.append((pattern, tool_pipe + tool_path))
diff --git a/rsov/compiler/tests/llvm-lit b/rsov/compiler/tests/llvm-lit
new file mode 100755
index 0000000..cc6f58e
--- /dev/null
+++ b/rsov/compiler/tests/llvm-lit
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+import os
+import sys
+
+# In the Android tree, use the environment variables set by envsetup.sh
+# to determine correct path for the root of the source tree.
+# TODO: To run clang tests, @LLVM_BINARY_DIR@ must be substituted also.
+android_source_root = os.getenv('ANDROID_BUILD_TOP', ".")
+llvm_source_root = os.path.join(android_source_root, 'external', 'llvm')
+
+# Make sure we can find the lit package.
+sys.path.append(os.path.join(llvm_source_root, 'utils', 'lit'))
+
+# Set up some builtin parameters, so that by default the LLVM test suite
+# configuration file knows how to find the object tree.
+builtin_parameters = {
+    'llvm_site_config' : os.path.join('./', 'lit.site.cfg')
+}
+
+if __name__=='__main__':
+    import lit
+    lit.main(builtin_parameters)
diff --git a/rsov/compiler/tests/multi_function/blend_mf.ll b/rsov/compiler/tests/multi_function/blend_mf.ll
new file mode 100644
index 0000000..fe65043
--- /dev/null
+++ b/rsov/compiler/tests/multi_function/blend_mf.ll
@@ -0,0 +1,70 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK-NOT: %rs_linker__Z14convert_uchar4Dv4_j = OpFunction %v4uchar
+; CHECK-NOT: %rs_linker__Z13convert_uint4Dv4_h = OpFunction %v4uint
+; CHECK-NOT: OpFunction %v4uchar
+; CHECK-NOT: OpFunction %uchar
+
+; CHECK: %main = OpFunction
+
+; CHECK: OpUConvert %v4uint
+; CHECK: OpUConvert %v4uchar
+
+; CHECK-NOT: OpFunctionCall
+
+; Function Attrs: norecurse nounwind readnone
+define void @NOP() #0 {
+entry:
+  ret void
+}
+
+; Function Attrs: nounwind readnone
+define <4 x i8> @setImageAlpha(<4 x i8> %in) #1 {
+entry:
+  %call = tail call <4 x i32> @_Z13convert_uint4Dv4_h(<4 x i8> %in) #2
+  %mul = mul <4 x i32> %call, <i32 37, i32 37, i32 37, i32 37>
+  %shr = lshr <4 x i32> %mul, <i32 8, i32 8, i32 8, i32 8>
+  %call1 = tail call <4 x i8> @_Z14convert_uchar4Dv4_j(<4 x i32> %shr) #2
+  %call2 = tail call fastcc <4 x i8> @twice(<4 x i8> %call1)
+  %0 = insertelement <4 x i8> %call2, i8 37, i32 3
+  ret <4 x i8> %0
+}
+
+; Function Attrs: norecurse nounwind readnone
+define internal fastcc <4 x i8> @twice(<4 x i8> %in) #0 {
+entry:
+  %mul = shl <4 x i8> %in, <i8 1, i8 1, i8 1, i8 1>
+  ret <4 x i8> %mul
+}
+
+; Function Attrs: nounwind readnone
+declare <4 x i8> @_Z14convert_uchar4Dv4_j(<4 x i32>) #1
+
+; Function Attrs: nounwind readnone
+declare <4 x i32> @_Z13convert_uint4Dv4_h(<4 x i8>) #1
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind readnone }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_func = !{!6}
+!\23rs_export_foreach_name = !{!7, !8}
+!\23rs_export_foreach = !{!9, !10}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"NOP"}
+!7 = !{!"root"}
+!8 = !{!"setImageAlpha"}
+!9 = !{!"0"}
+!10 = !{!"35"}
diff --git a/rsov/compiler/tests/multi_function/blend_mf.rs b/rsov/compiler/tests/multi_function/blend_mf.rs
new file mode 100644
index 0000000..1769208
--- /dev/null
+++ b/rsov/compiler/tests/multi_function/blend_mf.rs
@@ -0,0 +1,38 @@
+// Copyright (C) 2011 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+static uchar4 twice(uchar4 in) {
+  return in * 2;
+}
+
+static uchar getConstant() {
+  return 37;
+}
+
+void NOP() {
+  uchar3 x = {1, 2, 3};
+  (void) convert_float3(x);
+}
+
+uchar4 __attribute__((kernel)) setImageAlpha(uchar4 in) {
+  uchar4 out;
+  out.rgba = twice(convert_uchar4((convert_uint4(in.rgba) * 37) >> (uint4)8));
+  out.a = getConstant();
+  NOP();
+  return out;
+}
diff --git a/rsov/compiler/tests/rs2spirv_lit_driver.sh b/rsov/compiler/tests/rs2spirv_lit_driver.sh
new file mode 100755
index 0000000..d084c90
--- /dev/null
+++ b/rsov/compiler/tests/rs2spirv_lit_driver.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+
+# Copyright 2016, The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# TODO: Consider rewriting as a native binary instead of shell script.
+
+if [ $# -lt 1 ]; then
+  echo 1>&2 "$0: not enough arguments"
+  echo 1>&2 $#
+  exit 2
+fi
+
+script_path="$1"
+script_name=$(basename $script_path)
+script=${script_name%.*} # Remove extension.
+
+output_folder="driver_out"
+mkdir -p $output_folder
+
+eval llvm-as "$script_path" -o "$output_folder/$script.bc"
+eval rs2spirv "$output_folder/$script.bc" -o "$output_folder/$script.rs.spv" \
+              -wo "$output_folder/$script.w.spt"
+eval spirv-dis "$output_folder/$script.rs.spv" \
+              --no-color > "$output_folder/$script.rs.spt"
+eval rs2spirv -o "$output_folder/$script.spt" -lk "$output_folder/$script.rs.spt" \
+              -lw "$output_folder/$script.w.spt"
+eval spirv-as "$output_folder/$script.spt" \
+              -o "$output_folder/$script.spv"
+
+eval spirv-val "$output_folder/$script.spv"
+eval cat "$output_folder/$script.spt"
+
+eval rm "$output_folder/$script.*"
diff --git a/rsov/compiler/tests/rs_allocation/access_same.ll b/rsov/compiler/tests/rs_allocation/access_same.ll
new file mode 100644
index 0000000..7b2b25f
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/access_same.ll
@@ -0,0 +1,93 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %uchar
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageRead %v4uchar %[[RS_ALLOC:.*]] %{{.*}}
+; CHECK: OpImageWrite %{{.*}}[[RS_ALLOC]] %{{.*}} %{{.*}}
+; CHECK: OpImageWrite
+
+
+%struct.rs_allocation = type { i32* }
+
+@a1 = common global %struct.rs_allocation zeroinitializer, align 4
+@c1 = common global i8 0, align 1
+
+; Function Attrs: nounwind
+define <4 x i8> @k1(<4 x i8> %in) #0 {
+entry:
+  %0 = extractelement <4 x i8> %in, i32 0
+  %conv = zext i8 %0 to i32
+  %1 = extractelement <4 x i8> %in, i32 1
+  %conv1 = zext i8 %1 to i32
+  %.unpack = load i32, i32* bitcast (%struct.rs_allocation* @a1 to i32*), align 4
+  %2 = insertvalue [1 x i32] undef, i32 %.unpack, 0
+  %call = tail call <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32] %2, i32 %conv, i32 %conv1) #2
+  %3 = extractelement <4 x i8> %in, i32 2
+  %conv2 = zext i8 %3 to i32
+  %4 = extractelement <4 x i8> %in, i32 3
+  %conv3 = zext i8 %4 to i32
+  %.unpack14 = load i32, i32* bitcast (%struct.rs_allocation* @a1 to i32*), align 4
+  %5 = insertvalue [1 x i32] undef, i32 %.unpack14, 0
+  tail call void @_Z21rsSetElementAt_uchar413rs_allocationDv4_hjj([1 x i32] %5, <4 x i8> %call, i32 %conv2, i32 %conv3) #2
+  %6 = load i8, i8* @c1, align 1, !tbaa !13
+  %conv4 = zext i8 %6 to i32
+  %7 = extractelement <4 x i8> %call, i32 2
+  %conv5 = zext i8 %7 to i32
+  %add = add nuw nsw i32 %conv4, %conv5
+  %conv6 = trunc i32 %add to i8
+  %8 = insertelement <4 x i8> %call, i8 %conv6, i32 2
+  ret <4 x i8> %8
+}
+
+declare <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32], i32, i32) #1
+
+declare void @_Z21rsSetElementAt_uchar413rs_allocationDv4_hjj([1 x i32], <4 x i8>, i32, i32) #1
+
+; Function Attrs: nounwind
+define void @.rs.dtor() #0 {
+entry:
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @a1) #2
+  ret void
+}
+
+declare void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation*) #1
+
+attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6, !7}
+!\23rs_object_slots = !{!8}
+!\23rs_export_foreach_name = !{!9, !10}
+!\23rs_export_foreach = !{!11, !12}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"7"}
+!7 = !{!"a1", !"20"}
+!8 = !{!"1"}
+!9 = !{!"root"}
+!10 = !{!"k1"}
+!11 = !{!"0"}
+!12 = !{!"35"}
+!13 = !{!14, !14, i64 0}
+!14 = !{!"omnipotent char", !15, i64 0}
+!15 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/rs_allocation/access_same.rs b/rsov/compiler/tests/rs_allocation/access_same.rs
new file mode 100644
index 0000000..7fce3bb
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/access_same.rs
@@ -0,0 +1,28 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar c1;
+rs_allocation a1;
+
+uchar4 __attribute__((kernel)) k1(uchar4 in) {
+  uchar4 res = rsGetElementAt_uchar4(a1, in.r, in.g);
+  rsSetElementAt_uchar4(a1, res, in.b, in.a);
+  res.b += c1;
+  return res;
+}
+
diff --git a/rsov/compiler/tests/rs_allocation/copy_coords.ll b/rsov/compiler/tests/rs_allocation/copy_coords.ll
new file mode 100644
index 0000000..bed18d5
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/copy_coords.ll
@@ -0,0 +1,40 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: %coords_load = OpLoad %v3uint %global_invocation_id
+; CHECK: %coords_x = OpCompositeExtract %uint %coords_load 0
+; CHECK: %coords_y = OpCompositeExtract %uint %coords_load 1
+
+; Function Attrs: norecurse nounwind readnone
+define <4 x i8> @copy_coords(<4 x i8> %in, i32 %x, i32 %y) #0 {
+entry:
+  %conv = trunc i32 %x to i8
+  %0 = insertelement <4 x i8> %in, i8 %conv, i32 0
+  %conv1 = trunc i32 %y to i8
+  %1 = insertelement <4 x i8> %0, i8 %conv1, i32 1
+  ret <4 x i8> %1
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_foreach_name = !{!6, !7}
+!\23rs_export_foreach = !{!8, !9}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"root"}
+!7 = !{!"copy_coords"}
+!8 = !{!"0"}
+!9 = !{!"59"}
diff --git a/rsov/compiler/tests/rs_allocation/copy_coords.rs b/rsov/compiler/tests/rs_allocation/copy_coords.rs
new file mode 100644
index 0000000..2b36ee7
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/copy_coords.rs
@@ -0,0 +1,24 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar4 __attribute__((kernel)) copy_coords(uchar4 in, uint32_t x, uint32_t y) {
+  uchar4 res = in;
+  res.r = (uchar) x;
+  res.g = (uchar) y;
+  return res;
+}
diff --git a/rsov/compiler/tests/rs_allocation/multi_read.ll b/rsov/compiler/tests/rs_allocation/multi_read.ll
new file mode 100644
index 0000000..a405a63
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/multi_read.ll
@@ -0,0 +1,95 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %uchar
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageWrite
+
+%struct.rs_allocation = type { i32* }
+
+@a1 = common global %struct.rs_allocation zeroinitializer, align 4
+@a2 = common global %struct.rs_allocation zeroinitializer, align 4
+@c1 = common global i8 0, align 1
+
+; Function Attrs: nounwind
+define <4 x i8> @k1(<4 x i8> %in) #0 {
+entry:
+  %0 = extractelement <4 x i8> %in, i32 0
+  %conv = zext i8 %0 to i32
+  %1 = extractelement <4 x i8> %in, i32 1
+  %conv1 = zext i8 %1 to i32
+  %.unpack = load i32, i32* bitcast (%struct.rs_allocation* @a1 to i32*), align 4
+  %2 = insertvalue [1 x i32] undef, i32 %.unpack, 0
+  %call = tail call <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32] %2, i32 %conv, i32 %conv1) #2
+  %3 = extractelement <4 x i8> %in, i32 2
+  %conv2 = zext i8 %3 to i32
+  %4 = extractelement <4 x i8> %in, i32 3
+  %conv3 = zext i8 %4 to i32
+  %.unpack16 = load i32, i32* bitcast (%struct.rs_allocation* @a2 to i32*), align 4
+  %5 = insertvalue [1 x i32] undef, i32 %.unpack16, 0
+  %call4 = tail call <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32] %5, i32 %conv2, i32 %conv3) #2
+  %add = add <4 x i8> %call4, %call
+  %6 = load i8, i8* @c1, align 1, !tbaa !15
+  %conv5 = zext i8 %6 to i32
+  %7 = extractelement <4 x i8> %add, i32 2
+  %conv6 = zext i8 %7 to i32
+  %add7 = add nuw nsw i32 %conv6, %conv5
+  %conv8 = trunc i32 %add7 to i8
+  %8 = insertelement <4 x i8> %add, i8 %conv8, i32 2
+  ret <4 x i8> %8
+}
+
+declare <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32], i32, i32) #1
+
+; Function Attrs: nounwind
+define void @.rs.dtor() #0 {
+entry:
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @a1) #2
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @a2) #2
+  ret void
+}
+
+declare void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation*) #1
+
+attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6, !7, !8}
+!\23rs_object_slots = !{!9, !10}
+!\23rs_export_foreach_name = !{!11, !12}
+!\23rs_export_foreach = !{!13, !14}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"7"}
+!7 = !{!"a1", !"20"}
+!8 = !{!"a2", !"20"}
+!9 = !{!"1"}
+!10 = !{!"2"}
+!11 = !{!"root"}
+!12 = !{!"k1"}
+!13 = !{!"0"}
+!14 = !{!"35"}
+!15 = !{!16, !16, i64 0}
+!16 = !{!"omnipotent char", !17, i64 0}
+!17 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/rs_allocation/multi_read.rs b/rsov/compiler/tests/rs_allocation/multi_read.rs
new file mode 100644
index 0000000..bebb0f9
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/multi_read.rs
@@ -0,0 +1,29 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar c1;
+rs_allocation a1;
+rs_allocation a2;
+
+uchar4 __attribute__((kernel)) k1(uchar4 in) {
+  uchar4 res = rsGetElementAt_uchar4(a1, in.r, in.g);
+  res += rsGetElementAt_uchar4(a2, in.b, in.a);
+  res.b += c1;
+  return res;
+}
+
diff --git a/rsov/compiler/tests/rs_allocation/read.ll b/rsov/compiler/tests/rs_allocation/read.ll
new file mode 100644
index 0000000..623b981
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/read.ll
@@ -0,0 +1,82 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %uchar
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageWrite
+
+%struct.rs_allocation = type { i32* }
+
+@alloc = common global %struct.rs_allocation zeroinitializer, align 4
+@c1 = common global i8 0, align 1
+
+; Function Attrs: nounwind
+define <4 x i8> @k1(<4 x i8> %in) #0 {
+entry:
+  %0 = extractelement <4 x i8> %in, i32 0
+  %conv = zext i8 %0 to i32
+  %1 = extractelement <4 x i8> %in, i32 1
+  %conv1 = zext i8 %1 to i32
+  %.unpack = load i32, i32* bitcast (%struct.rs_allocation* @alloc to i32*), align 4
+  %2 = insertvalue [1 x i32] undef, i32 %.unpack, 0
+  %call = tail call <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32] %2, i32 %conv, i32 %conv1) #2
+  %3 = load i8, i8* @c1, align 1, !tbaa !13
+  %conv2 = zext i8 %3 to i32
+  %4 = extractelement <4 x i8> %call, i32 2
+  %conv3 = zext i8 %4 to i32
+  %add = add nuw nsw i32 %conv3, %conv2
+  %conv4 = trunc i32 %add to i8
+  %5 = insertelement <4 x i8> %call, i8 %conv4, i32 2
+  ret <4 x i8> %5
+}
+
+declare <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32], i32, i32) #1
+
+; Function Attrs: nounwind
+define void @.rs.dtor() #0 {
+entry:
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @alloc) #2
+  ret void
+}
+
+declare void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation*) #1
+
+attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6, !7}
+!\23rs_object_slots = !{!8}
+!\23rs_export_foreach_name = !{!9, !10}
+!\23rs_export_foreach = !{!11, !12}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"7"}
+!7 = !{!"alloc", !"20"}
+!8 = !{!"1"}
+!9 = !{!"root"}
+!10 = !{!"k1"}
+!11 = !{!"0"}
+!12 = !{!"35"}
+!13 = !{!14, !14, i64 0}
+!14 = !{!"omnipotent char", !15, i64 0}
+!15 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/rs_allocation/read.rs b/rsov/compiler/tests/rs_allocation/read.rs
new file mode 100644
index 0000000..0cde99f
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/read.rs
@@ -0,0 +1,26 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar c1;
+rs_allocation alloc;
+
+uchar4 __attribute__((kernel)) k1(uchar4 in) {
+  uchar4 res = rsGetElementAt_uchar4(alloc, in.r, in.g);
+  res.b += c1;
+  return res;
+}
diff --git a/rsov/compiler/tests/rs_allocation/read_write.ll b/rsov/compiler/tests/rs_allocation/read_write.ll
new file mode 100644
index 0000000..2ea6e46
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/read_write.ll
@@ -0,0 +1,85 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %uchar
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageWrite
+
+%struct.rs_allocation = type { i32* }
+
+@r = common global %struct.rs_allocation zeroinitializer, align 4
+@w = common global %struct.rs_allocation zeroinitializer, align 4
+@c1 = common global i8 0, align 1
+
+; Function Attrs: nounwind
+define <4 x i8> @k1(<4 x i8> %in, i32 %x, i32 %y) #0 {
+entry:
+  %.unpack = load i32, i32* bitcast (%struct.rs_allocation* @r to i32*), align 4
+  %0 = insertvalue [1 x i32] undef, i32 %.unpack, 0
+  %call = tail call <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32] %0, i32 %x, i32 %y) #2
+  %1 = load i8, i8* @c1, align 1, !tbaa !15
+  %splat.splatinsert = insertelement <4 x i8> undef, i8 %1, i32 0
+  %splat.splat = shufflevector <4 x i8> %splat.splatinsert, <4 x i8> undef, <4 x i32> zeroinitializer
+  %add = add <4 x i8> %call, %in
+  %add1 = add <4 x i8> %add, %splat.splat
+  %.unpack6 = load i32, i32* bitcast (%struct.rs_allocation* @w to i32*), align 4
+  %2 = insertvalue [1 x i32] undef, i32 %.unpack6, 0
+  tail call void @_Z21rsSetElementAt_uchar413rs_allocationDv4_hjj([1 x i32] %2, <4 x i8> %add1, i32 %x, i32 %y) #2
+  ret <4 x i8> %in
+}
+
+declare <4 x i8> @_Z21rsGetElementAt_uchar413rs_allocationjj([1 x i32], i32, i32) #1
+
+declare void @_Z21rsSetElementAt_uchar413rs_allocationDv4_hjj([1 x i32], <4 x i8>, i32, i32) #1
+
+; Function Attrs: nounwind
+define void @.rs.dtor() #0 {
+entry:
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @r) #2
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @w) #2
+  ret void
+}
+
+declare void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation*) #1
+
+attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6, !7, !8}
+!\23rs_object_slots = !{!9, !10}
+!\23rs_export_foreach_name = !{!11, !12}
+!\23rs_export_foreach = !{!13, !14}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"7"}
+!7 = !{!"r", !"20"}
+!8 = !{!"w", !"20"}
+!9 = !{!"1"}
+!10 = !{!"2"}
+!11 = !{!"root"}
+!12 = !{!"k1"}
+!13 = !{!"0"}
+!14 = !{!"59"}
+!15 = !{!16, !16, i64 0}
+!16 = !{!"omnipotent char", !17, i64 0}
+!17 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/rs_allocation/read_write.rs b/rsov/compiler/tests/rs_allocation/read_write.rs
new file mode 100644
index 0000000..8852c82
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/read_write.rs
@@ -0,0 +1,27 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar c1;
+rs_allocation r;
+rs_allocation w;
+
+uchar4 __attribute__((kernel)) k1(uchar4 in, uint32_t x, uint32_t y) {
+  uchar4 read = rsGetElementAt_uchar4(r, x, y);
+  rsSetElementAt_uchar4(w, in + c1 + read, x, y);
+  return in;
+}
diff --git a/rsov/compiler/tests/rs_allocation/write.ll b/rsov/compiler/tests/rs_allocation/write.ll
new file mode 100644
index 0000000..0315c45
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/write.ll
@@ -0,0 +1,75 @@
+; RUN: rs2spirv_lit_driver.sh %s | FileCheck %s
+
+target datalayout = "e-p:32:32-i64:64-v128:64:128-n32-S64"
+target triple = "armv7-none-linux-gnueabi"
+
+; CHECK: %rs_linker_struct___GPUBuffer = OpTypeStruct %uchar
+; CHECK: OpTypePointer Uniform %rs_linker_struct___GPUBuffer
+
+; CHECK: %rs_linker___GPUBlock = OpVariable %{{.+}} Uniform
+
+; CHECK: %main = OpFunction
+
+; CHECK-NOT: OpFunctionCall
+
+; CHECK: OpImageRead %v4uchar
+; CHECK: OpImageWrite
+; CHECK: OpImageWrite
+
+%struct.rs_allocation = type { i32* }
+
+@alloc = common global %struct.rs_allocation zeroinitializer, align 4
+@c1 = common global i8 0, align 1
+
+; Function Attrs: nounwind
+define <4 x i8> @k1(<4 x i8> %in, i32 %x, i32 %y) #0 {
+entry:
+  %0 = load i8, i8* @c1, align 1, !tbaa !13
+  %splat.splatinsert = insertelement <4 x i8> undef, i8 %0, i32 0
+  %splat.splat = shufflevector <4 x i8> %splat.splatinsert, <4 x i8> undef, <4 x i32> zeroinitializer
+  %add = add <4 x i8> %splat.splat, %in
+  %.unpack = load i32, i32* bitcast (%struct.rs_allocation* @alloc to i32*), align 4
+  %1 = insertvalue [1 x i32] undef, i32 %.unpack, 0
+  tail call void @_Z21rsSetElementAt_uchar413rs_allocationDv4_hjj([1 x i32] %1, <4 x i8> %add, i32 %x, i32 %y) #2
+  ret <4 x i8> %in
+}
+
+declare void @_Z21rsSetElementAt_uchar413rs_allocationDv4_hjj([1 x i32], <4 x i8>, i32, i32) #1
+
+; Function Attrs: nounwind
+define void @.rs.dtor() #0 {
+entry:
+  tail call void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation* nonnull @alloc) #2
+  ret void
+}
+
+declare void @_Z13rsClearObjectP13rs_allocation(%struct.rs_allocation*) #1
+
+attributes #0 = { nounwind "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "target-features"="+long64" "unsafe-fp-math"="false" "use-soft-float"="false" }
+attributes #2 = { nounwind }
+
+!llvm.module.flags = !{!0, !1}
+!llvm.ident = !{!2}
+!\23pragma = !{!3, !4, !5}
+!\23rs_export_var = !{!6, !7}
+!\23rs_object_slots = !{!8}
+!\23rs_export_foreach_name = !{!9, !10}
+!\23rs_export_foreach = !{!11, !12}
+
+!0 = !{i32 1, !"wchar_size", i32 4}
+!1 = !{i32 1, !"min_enum_size", i32 4}
+!2 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!3 = !{!"version", !"1"}
+!4 = !{!"java_package_name", !"rs2spirv"}
+!5 = !{!"rs_fp_relaxed", !""}
+!6 = !{!"c1", !"7"}
+!7 = !{!"alloc", !"20"}
+!8 = !{!"1"}
+!9 = !{!"root"}
+!10 = !{!"k1"}
+!11 = !{!"0"}
+!12 = !{!"59"}
+!13 = !{!14, !14, i64 0}
+!14 = !{!"omnipotent char", !15, i64 0}
+!15 = !{!"Simple C/C++ TBAA"}
diff --git a/rsov/compiler/tests/rs_allocation/write.rs b/rsov/compiler/tests/rs_allocation/write.rs
new file mode 100644
index 0000000..ef87c7a
--- /dev/null
+++ b/rsov/compiler/tests/rs_allocation/write.rs
@@ -0,0 +1,25 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+uchar c1;
+rs_allocation alloc;
+
+uchar4 __attribute__((kernel)) k1(uchar4 in, uint32_t x, uint32_t y) {
+  rsSetElementAt_uchar4(alloc, in + c1, x, y);
+  return in;
+}
diff --git a/rsov/compiler/tests/run-lit-tests.sh b/rsov/compiler/tests/run-lit-tests.sh
new file mode 100755
index 0000000..bafbb45
--- /dev/null
+++ b/rsov/compiler/tests/run-lit-tests.sh
@@ -0,0 +1,7 @@
+#!/bin/bash -e
+
+current_dir=$(pwd)
+LIT_PATH=$current_dir/llvm-lit
+LIBSPIRV_TESTS=$current_dir
+
+$LIT_PATH $LIBSPIRV_TESTS $@
diff --git a/rsov/compiler/tests/single_kernel/duff.ll b/rsov/compiler/tests/single_kernel/duff.ll
new file mode 100644
index 0000000..3bb6a53
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/duff.ll
@@ -0,0 +1,100 @@
+; RUN: llvm-as < %s | rs2spirv -spirv-text -o %t
+; RUN: FileCheck < %t %s
+
+; TODO: Complete the test.
+
+target datalayout = "e-m:e-i64:64-i128:128-n32:64-S128"
+target triple = "aarch64-none-linux-gnueabi"
+
+; Function Attrs: norecurse nounwind readnone
+; CHECK: Name [[FooIdx:[0-9]+]] "duff"
+define i32 @duff(i32 %count) #0 {
+entry:
+  %add = add nsw i32 %count, 7
+  %div = sdiv i32 %add, 8
+  %rem = srem i32 %count, 8
+  switch i32 %rem, label %sw.epilog [
+    i32 0, label %do.body
+    i32 7, label %sw.bb1
+    i32 6, label %sw.bb2
+    i32 5, label %sw.bb3
+    i32 4, label %sw.bb6
+    i32 3, label %sw.bb8
+    i32 2, label %sw.bb12
+    i32 1, label %sw.bb14
+  ]
+
+do.body:                                          ; preds = %entry, %sw.bb14
+  %n.0 = phi i32 [ %dec15, %sw.bb14 ], [ %div, %entry ]
+  %x.0 = phi i32 [ %shl, %sw.bb14 ], [ 321, %entry ]
+  %mul = mul nsw i32 %x.0, 5
+  br label %sw.bb1
+
+sw.bb1:                                           ; preds = %entry, %do.body
+  %n.1 = phi i32 [ %n.0, %do.body ], [ %div, %entry ]
+  %x.1 = phi i32 [ %mul, %do.body ], [ 321, %entry ]
+  %dec = add nsw i32 %x.1, -1
+  br label %sw.bb2
+
+sw.bb2:                                           ; preds = %entry, %sw.bb1
+  %n.2 = phi i32 [ %n.1, %sw.bb1 ], [ %div, %entry ]
+  %x.2 = phi i32 [ %dec, %sw.bb1 ], [ 321, %entry ]
+  %xor = xor i32 %x.2, 27
+  br label %sw.bb3
+
+sw.bb3:                                           ; preds = %entry, %sw.bb2
+  %n.3 = phi i32 [ %n.2, %sw.bb2 ], [ %div, %entry ]
+  %x.3 = phi i32 [ %xor, %sw.bb2 ], [ 321, %entry ]
+  %mul4 = mul nsw i32 %x.3, %x.3
+  %sub.neg = add i32 %x.3, 12
+  %sub5 = sub i32 %sub.neg, %mul4
+  br label %sw.bb6
+
+sw.bb6:                                           ; preds = %entry, %sw.bb3
+  %n.4 = phi i32 [ %n.3, %sw.bb3 ], [ %div, %entry ]
+  %x.4 = phi i32 [ %sub5, %sw.bb3 ], [ 321, %entry ]
+  %add7 = add nsw i32 %x.4, 2
+  br label %sw.bb8
+
+sw.bb8:                                           ; preds = %entry, %sw.bb6
+  %n.5 = phi i32 [ %n.4, %sw.bb6 ], [ %div, %entry ]
+  %x.5 = phi i32 [ %add7, %sw.bb6 ], [ 321, %entry ]
+  %rem9 = srem i32 %x.5, 32
+  %mul10 = mul nsw i32 %x.5, %x.5
+  %add11 = add nsw i32 %rem9, %mul10
+  br label %sw.bb12
+
+sw.bb12:                                          ; preds = %entry, %sw.bb8
+  %n.6 = phi i32 [ %n.5, %sw.bb8 ], [ %div, %entry ]
+  %x.6 = phi i32 [ %add11, %sw.bb8 ], [ 321, %entry ]
+  %sub13 = add nsw i32 %x.6, -2
+  br label %sw.bb14
+
+sw.bb14:                                          ; preds = %entry, %sw.bb12
+  %n.7 = phi i32 [ %div, %entry ], [ %n.6, %sw.bb12 ]
+  %x.7 = phi i32 [ 321, %entry ], [ %sub13, %sw.bb12 ]
+  %shl = shl i32 %x.7, 3
+  %dec15 = add nsw i32 %n.7, -1
+  %cmp = icmp sgt i32 %n.7, 1
+  br i1 %cmp, label %do.body, label %sw.epilog
+
+sw.epilog:                                        ; preds = %sw.bb14, %entry
+  %x.8 = phi i32 [ 321, %entry ], [ %shl, %sw.bb14 ]
+  ret i32 %x.8
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.ident = !{!0}
+!\23pragma = !{!1, !2, !3}
+!\23rs_export_foreach_name = !{!4, !5}
+!\23rs_export_foreach = !{!6, !7}
+
+!0 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!1 = !{!"version", !"1"}
+!2 = !{!"java_package_name", !"rs2spirv"}
+!3 = !{!"rs_fp_relaxed", !""}
+!4 = !{!"root"}
+!5 = !{!"duff"}
+!6 = !{!"0"}
+!7 = !{!"35"}
diff --git a/rsov/compiler/tests/single_kernel/fib.ll b/rsov/compiler/tests/single_kernel/fib.ll
new file mode 100644
index 0000000..916cfc0
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/fib.ll
@@ -0,0 +1,48 @@
+; RUN: llvm-as < %s | rs2spirv -spirv-text -o %t
+; RUN: FileCheck < %t %s
+
+; TODO: Complete the test.
+
+
+target datalayout = "e-m:e-i64:64-i128:128-n32:64-S128"
+target triple = "aarch64-none-linux-gnueabi"
+
+; Function Attrs: nounwind readnone
+; CHECK: Name [[FooIdx:[0-9]+]] "fib"
+define i32 @fib(i32 %n) #0 {
+entry:
+  %n.off8 = add i32 %n, -1
+  %0 = icmp ult i32 %n.off8, 2
+  br i1 %0, label %return, label %if.end
+
+if.end:                                           ; preds = %entry, %if.end
+  %n.tr10 = phi i32 [ %sub2, %if.end ], [ %n, %entry ]
+  %accumulator.tr9 = phi i32 [ %add, %if.end ], [ 1, %entry ]
+  %sub = add nsw i32 %n.tr10, -1
+  %call = tail call i32 @fib(i32 %sub)
+  %sub2 = add nsw i32 %n.tr10, -2
+  %add = add nsw i32 %call, %accumulator.tr9
+  %n.off = add i32 %n.tr10, -3
+  %1 = icmp ult i32 %n.off, 2
+  br i1 %1, label %return, label %if.end
+
+return:                                           ; preds = %if.end, %entry
+  %accumulator.tr.lcssa = phi i32 [ 1, %entry ], [ %add, %if.end ]
+  ret i32 %accumulator.tr.lcssa
+}
+
+attributes #0 = { nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.ident = !{!0}
+!\23pragma = !{!1, !2, !3}
+!\23rs_export_foreach_name = !{!4, !5}
+!\23rs_export_foreach = !{!6, !7}
+
+!0 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!1 = !{!"version", !"1"}
+!2 = !{!"java_package_name", !"rs2spirv"}
+!3 = !{!"rs_fp_relaxed", !""}
+!4 = !{!"root"}
+!5 = !{!"fib"}
+!6 = !{!"0"}
+!7 = !{!"35"}
diff --git a/rsov/compiler/tests/single_kernel/identity.ll b/rsov/compiler/tests/single_kernel/identity.ll
new file mode 100644
index 0000000..a4765a2
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/identity.ll
@@ -0,0 +1,29 @@
+; RUN: llvm-as < %s | rs2spirv -spirv-text -o %t
+; RUN: FileCheck < %t %s
+target datalayout = "e-m:e-i64:64-i128:128-n32:64-S128"
+target triple = "aarch64-none-linux-gnueabi"
+
+; Function Attrs: norecurse nounwind readnone
+; CHECK: Name [[FooIdx:[0-9]+]] "foo"
+; CHECK: Name [[InIdx:[0-9]+]] "in"
+; CHECK: Decorate {{.*}}[[FooIdx]] LinkageAttributes "foo" Export
+define i32 @foo(i32 %in) #0 {
+; CHECK: ReturnValue {{.*}}[[InIdx]]
+  ret i32 %in
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.ident = !{!0}
+!\23pragma = !{!1, !2, !3}
+!\23rs_export_foreach_name = !{!4, !5}
+!\23rs_export_foreach = !{!6, !7}
+
+!0 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!1 = !{!"version", !"1"}
+!2 = !{!"java_package_name", !"rs2spirv"}
+!3 = !{!"rs_fp_relaxed", !""}
+!4 = !{!"root"}
+!5 = !{!"foo"}
+!6 = !{!"0"}
+!7 = !{!"35"}
diff --git a/rsov/compiler/tests/single_kernel/identity.rs b/rsov/compiler/tests/single_kernel/identity.rs
new file mode 100644
index 0000000..a3bdb6e
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/identity.rs
@@ -0,0 +1,9 @@
+// TODO: Complete the test.
+
+#pragma version(1)
+#pragma rs java_package_name(rs2srpiv)
+#pragma rs_fp_relaxed
+
+int __attribute__((kernel)) foo(int in) {
+  return in;
+}
diff --git a/rsov/compiler/tests/single_kernel/invert.ll b/rsov/compiler/tests/single_kernel/invert.ll
new file mode 100644
index 0000000..23c458b
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/invert.ll
@@ -0,0 +1,42 @@
+; RUN: llvm-as < %s | rs2spirv -spirv-text -o %t
+; RUN: FileCheck < %t %s
+
+; TODO: Complete the test.
+
+target datalayout = "e-m:e-i64:64-i128:128-n32:64-S128"
+target triple = "aarch64-none-linux-gnueabi"
+
+; Function Attrs: norecurse nounwind readnone
+; CHECK: Name [[FooIdx:[0-9]+]] "invert"
+define <4 x float> @invert(<4 x float> %in) #0 {
+entry:
+  %0 = extractelement <4 x float> %in, i64 0
+  %sub = fsub float 1.000000e+00, %0
+  %1 = insertelement <4 x float> undef, float %sub, i64 0
+  %2 = extractelement <4 x float> %in, i64 1
+  %sub1 = fsub float 1.000000e+00, %2
+  %3 = insertelement <4 x float> %1, float %sub1, i64 1
+  %4 = extractelement <4 x float> %in, i64 2
+  %sub2 = fsub float 1.000000e+00, %4
+  %5 = insertelement <4 x float> %3, float %sub2, i64 2
+  %6 = extractelement <4 x float> %in, i64 3
+  %sub3 = fsub float 1.000000e+00, %6
+  %7 = insertelement <4 x float> %5, float %sub3, i64 3
+  ret <4 x float> %7
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.ident = !{!0}
+!\23pragma = !{!1, !2, !3}
+!\23rs_export_foreach_name = !{!4, !5}
+!\23rs_export_foreach = !{!6, !7}
+
+!0 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!1 = !{!"version", !"1"}
+!2 = !{!"java_package_name", !"rs2spirv"}
+!3 = !{!"rs_fp_relaxed", !""}
+!4 = !{!"root"}
+!5 = !{!"invert"}
+!6 = !{!"0"}
+!7 = !{!"35"}
diff --git a/rsov/compiler/tests/single_kernel/kernel.ll b/rsov/compiler/tests/single_kernel/kernel.ll
new file mode 100644
index 0000000..43f817c
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/kernel.ll
@@ -0,0 +1,27 @@
+; RUN: llvm-as < %s | rs2spirv -spirv-text -o %t
+; RUN: FileCheck < %t %s
+target datalayout = "e-m:e-i64:64-i128:128-n32:64-S128"
+target triple = "aarch64-none-linux-gnueabi"
+
+; Function Attrs: norecurse nounwind readnone
+; CHECK: Name [[FooIdx:[0-9]+]] "foo"
+; CHECK: Decorate {{.*}}[[FooIdx]] LinkageAttributes "foo" Export
+define void @foo() #0 {
+  ret void
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.ident = !{!0}
+!\23pragma = !{!1, !2, !3}
+!\23rs_export_foreach_name = !{!4, !5}
+!\23rs_export_foreach = !{!6, !7}
+
+!0 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!1 = !{!"version", !"1"}
+!2 = !{!"java_package_name", !"rs2srpiv"}
+!3 = !{!"rs_fp_relaxed", !""}
+!4 = !{!"root"}
+!5 = !{!"foo"}
+!6 = !{!"0"}
+!7 = !{!"32"}
diff --git a/rsov/compiler/tests/single_kernel/kernel.rs b/rsov/compiler/tests/single_kernel/kernel.rs
new file mode 100644
index 0000000..2e38a13
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/kernel.rs
@@ -0,0 +1,6 @@
+#pragma version(1)
+#pragma rs java_package_name(rs2spirv)
+#pragma rs_fp_relaxed
+
+void __attribute__((kernel)) foo() {
+}
diff --git a/rsov/compiler/tests/single_kernel/times.ll b/rsov/compiler/tests/single_kernel/times.ll
new file mode 100644
index 0000000..40ee914
--- /dev/null
+++ b/rsov/compiler/tests/single_kernel/times.ll
@@ -0,0 +1,31 @@
+; RUN: llvm-as < %s | rs2spirv -spirv-text -o %t
+; RUN: FileCheck < %t %s
+
+; TODO: Complete the test.
+
+target datalayout = "e-m:e-i64:64-i128:128-n32:64-S128"
+target triple = "aarch64-none-linux-gnueabi"
+
+; Function Attrs: norecurse nounwind readnone
+; CHECK: Name [[FooIdx:[0-9]+]] "times"
+define i32 @times(i32 %x) #0 {
+entry:
+  %mul = shl i32 %x, 1
+  ret i32 %mul
+}
+
+attributes #0 = { norecurse nounwind readnone "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="false" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="0" "stackrealign" "unsafe-fp-math"="false" "use-soft-float"="false" }
+
+!llvm.ident = !{!0}
+!\23pragma = !{!1, !2, !3}
+!\23rs_export_foreach_name = !{!4, !5}
+!\23rs_export_foreach = !{!6, !7}
+
+!0 = !{!"Android clang version 3.8.256229  (based on LLVM 3.8.256229)"}
+!1 = !{!"version", !"1"}
+!2 = !{!"java_package_name", !"rs2spirv"}
+!3 = !{!"rs_fp_relaxed", !""}
+!4 = !{!"root"}
+!5 = !{!"times"}
+!6 = !{!"0"}
+!7 = !{!"42"}
diff --git a/rsov/compiler/unit_tests/LinkerModuleTests.cpp b/rsov/compiler/unit_tests/LinkerModuleTests.cpp
new file mode 100644
index 0000000..ef4ab80
--- /dev/null
+++ b/rsov/compiler/unit_tests/LinkerModuleTests.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "unit_tests/TestRunner.h"
+
+#include "LinkerModule.h"
+
+using namespace llvm;
+using namespace rs2spirv;
+
+TEST_CASE("SPIRVLine::hasCode_negative") {
+  SPIRVLine L("");
+  CHECK(!L.hasCode());
+
+  L.str() = ";";
+  CHECK(!L.hasCode());
+
+  L.str() = " ;";
+  CHECK(!L.hasCode());
+
+  L.str() = "; OpReturn";
+  CHECK(!L.hasCode());
+
+  L.str() = "   ";
+  CHECK(!L.hasCode());
+}
+
+TEST_CASE("SPIRVLine::hasCode_positive") {
+  SPIRVLine L("OpReturn");
+  CHECK(L.hasCode());
+
+  L.str() = " OpReturn ";
+  CHECK(L.hasCode());
+
+  L.str() = "OpReturn;";
+  CHECK(L.hasCode());
+
+  L.str() = "OpReturn ;";
+  CHECK(L.hasCode());
+}
+
+TEST_CASE("SPIRVLine::getIdentifiers") {
+  using Vector = SmallVector<StringRef, 4>;
+  Vector Ids;
+
+  SPIRVLine L("OpReturn");
+  L.getIdentifiers(Ids);
+  CHECK((Ids == Vector{}));
+  Ids.clear();
+
+  L.str() = "%uint = OpTypeInt 32 0";
+  L.getIdentifiers(Ids);
+  CHECK((Ids == Vector{"%uint"}));
+  Ids.clear();
+
+  L.str() = "%x = OpTypeStruct %float";
+  L.getIdentifiers(Ids);
+  CHECK((Ids == Vector{"%x", "%float"}));
+  Ids.clear();
+}
+
+TEST_CASE("SPIRVLine::getLHSIdentifier") {
+  SPIRVLine L("OpReturn");
+  CHECK(!L.getLHSIdentifier());
+
+  L.str() = "%uint = OpTypeInt 32 0";
+  auto Id = L.getLHSIdentifier();
+  CHECK(Id);
+  CHECK(*Id == "%uint");
+
+  L.str() = "%12 = OpConstant %uint 0";
+  Id = L.getLHSIdentifier();
+  CHECK(Id);
+  CHECK(*Id == "%12");
+}
+
+TEST_CASE("SPIRVLine::getRHSIdentifiers") {
+  using Vector = SmallVector<StringRef, 4>;
+  Vector Ids;
+
+  SPIRVLine L("OpReturn");
+  L.getRHSIdentifiers(Ids);
+  CHECK((Ids == Vector{}));
+  Ids.clear();
+
+  L.str() = "%uint = OpTypeInt 32 0";
+  L.getRHSIdentifiers(Ids);
+  CHECK((Ids == Vector{}));
+  Ids.clear();
+
+  L.str() = "%x = OpTypeStruct %float";
+  L.getRHSIdentifiers(Ids);
+  CHECK((Ids == Vector{"%float"}));
+  Ids.clear();
+
+  L.str() = "%x = OpTypeStruct %float %uint";
+  L.getRHSIdentifiers(Ids);
+  CHECK((Ids == Vector{"%float", "%uint"}));
+  Ids.clear();
+}
+
+TEST_CASE("SPIRVLine::getRHS") {
+  SPIRVLine L("OpReturn");
+  auto Res = L.getRHS();
+  CHECK(!Res);
+
+  L.str() = "%float = OpTypeFloat 32";
+  Res = L.getRHS();
+  CHECK(Res);
+  CHECK(*Res == "OpTypeFloat 32");
+}
+
+TEST_CASE("SPIRVLine::replaceId") {
+  SPIRVLine L("OpReturn");
+  bool Res = L.replaceId("%uint", "%void");
+  CHECK(!Res);
+  CHECK(L.str() == "OpReturn");
+
+  L.str() = "%entry = OpLabel";
+  Res = L.replaceId("%wtw", "%twt");
+  CHECK(!Res);
+  CHECK(L.str() == "%entry = OpLabel");
+
+  Res = L.replaceId("%entry", "%x");
+  CHECK(Res);
+  CHECK(L.str() == "%x = OpLabel");
+
+  L.str() = "%7 = OpTypeFunction %v4float %v4float";
+  Res = L.replaceId("%7", "%8");
+  CHECK(Res);
+  CHECK(L.str() == "%8 = OpTypeFunction %v4float %v4float");
+  Res = L.replaceId("%v4float", "%void");
+  CHECK(Res);
+  CHECK(L.str() == "%8 = OpTypeFunction %void %v4float");
+  Res = L.replaceId("%v4float", "%void");
+  CHECK(Res);
+  CHECK(L.str() == "%8 = OpTypeFunction %void %void");
+}
+
+TEST_CASE("SPIRVLine::replaceStr") {
+  SPIRVLine L("OpReturn");
+  bool Res = L.replaceStr("OpLoad", "OpStore");
+  CHECK(!Res);
+  CHECK(L.str() == "OpReturn");
+  Res = L.replaceStr("OpReturn", "OpFunctionEnd");
+  CHECK(Res);
+  CHECK(L.str() == "OpFunctionEnd");
+
+  L.str() = "%16 = OpUndef %v4float";
+  Res = L.replaceStr("OpUndef", "OpDef");
+  CHECK(Res);
+  CHECK(L.str() == "%16 = OpDef %v4float");
+}
diff --git a/rsov/compiler/unit_tests/TestRunner.h b/rsov/compiler/unit_tests/TestRunner.h
new file mode 100644
index 0000000..4e8f57a
--- /dev/null
+++ b/rsov/compiler/unit_tests/TestRunner.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RS2SPIRV_TEST_RUNNER
+#define RS2SPIRV_TEST_RUNNER
+
+#include "llvm/Support/Debug.h"
+#include "llvm/Support/raw_ostream.h"
+
+#include <cassert>
+#include <vector>
+
+namespace rs2spirv {
+
+struct TestCase {
+  void (*testPtr)(void);
+  const char *const description;
+};
+
+class TestRunnerContext {
+public:
+  static TestRunnerContext &getInstance() {
+    static TestRunnerContext ctx;
+    return ctx;
+  }
+
+  static void addTest(TestCase TC) { getInstance().tests.push_back(TC); }
+  static size_t &getCheckSuccessNum() { return getInstance().checkSuccessNum; }
+  static size_t &getTotalCheckNum() { return getInstance().totalCheckNum; }
+
+  static int runTests() {
+    bool Failed = false;
+    for (auto &TC : getInstance().tests) {
+      getCheckSuccessNum() = getTotalCheckNum() = 0;
+      llvm::outs() << "Test(" << TC.description << ") {\n";
+      TC.testPtr();
+      llvm::outs() << "\n} (" << TC.description << ") [" << getCheckSuccessNum()
+                   << "/" << getTotalCheckNum() << "]\n\n";
+      Failed |= getCheckSuccessNum() != getTotalCheckNum();
+    }
+
+    return static_cast<int>(Failed);
+  }
+
+private:
+  TestRunnerContext() = default;
+  std::vector<TestCase> tests;
+  size_t checkSuccessNum;
+  size_t totalCheckNum;
+};
+
+struct TestAdder {
+  TestAdder(TestCase TC) { TestRunnerContext::addTest(TC); }
+};
+
+#define RS2SPIRV_CONCAT_IMPL(S1, S2) S1##S2
+#define RS2SPIRV_CONCAT(S1, S2) RS2SPIRV_CONCAT_IMPL(S1, S2)
+#define RS2SPIRV_ANONYMOUS(X) RS2SPIRV_CONCAT(X, __COUNTER__)
+
+#if RS2SPIRV_DEBUG
+#define RS2SPIRV_TEST_CASE_ADD_IMPL(FNAME, VNAME, DESCRIPTION)                 \
+  static void FNAME();                                                         \
+  static rs2spirv::TestAdder VNAME({FNAME, DESCRIPTION});                      \
+  inline void FNAME()
+#elif defined(__GNUC__) || defined(__clang__)
+#define RS2SPIRV_TEST_CASE_ADD_IMPL(FNAME, VNAME, DESCRIPTION)                 \
+  static inline void __attribute__((unused)) FNAME()
+#else
+#define RS2SPIRV_TEST_CASE_ADD_IMPL(FNAME, VNAME, DESCRIPTION)                 \
+  static inline void FNAME()
+#endif
+
+#define RS2SPIRV_TEST_CASE_ADD(NAME, DESCRIPTION)                              \
+  RS2SPIRV_TEST_CASE_ADD_IMPL(RS2SPIRV_ANONYMOUS(NAME),                        \
+                              RS2SPIRV_ANONYMOUS(NAME), DESCRIPTION)
+
+#define TEST_CASE(DESCRIPTION) RS2SPIRV_TEST_CASE_ADD(TC, DESCRIPTION)
+
+#define CHECK(CONDITION)                                                       \
+  ++rs2spirv::TestRunnerContext::getTotalCheckNum();                           \
+  if (!(CONDITION))                                                            \
+    llvm::errs() << "\nCHECK <(  " #CONDITION "  )> failed!\n";                \
+  else                                                                         \
+    ++rs2spirv::TestRunnerContext::getCheckSuccessNum();                       \
+  (void)0
+
+} // namespace rs2spirv
+
+#endif