Repository: angr/pypcode (language: python)

Issue:
Verify loaded library version
### Description

In some cases, an older csleigh version may be accidentally loaded (eg working with multiple python revs). Add version enforcement.

### Alternatives

_No response_

### Additional context

_No response_
Reduce context dependencies
### Description

We can reduce amount of references to context. Helpful also for creating p-code manually.

### Alternatives

_No response_

### Additional context

_No response_


Discussion / hints:

Fix patch:
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..2ebbf98c
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,61 @@
+# https://clang.llvm.org/docs/ClangFormat.html
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+---
+Language:        Cpp
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: true
+AlignOperands:   true
+AlignTrailingComments: false # churn
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: false
+BinPackParameters: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowAllArgumentsOnNextLine: true
+BraceWrapping:
+  AfterControlStatement: false
+  AfterEnum:       false
+  AfterFunction:   true
+  AfterStruct:     false
+  AfterUnion:      false
+  BeforeElse:      false
+  IndentBraces:    false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: false
+BreakStringLiterals: true
+ColumnLimit:     120
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerAlignment: false
+DisableFormat:   false
+IncludeIsMainRegex: '$'
+IndentCaseLabels: false
+IndentWidth:     4
+AccessModifierOffset: -4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ?
+MacroBlockEnd:   '.*_END$'
+MaxEmptyLinesToKeep: 2
+PointerAlignment: Right
+ReflowComments:  true
+SortIncludes:    true
+SpaceAfterCStyleCast: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInContainerLiterals: true
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard:        Auto
+UseTab:          Never
+...
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a4644a02..c32eaa7e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -45,20 +45,32 @@ jobs:
     strategy:
       matrix:
         include:
-          - { os: macos-latest,   wheel: cp38-macosx_x86_64 }
-          - { os: macos-latest,   wheel: pp38-macosx_x86_64 }
-          - { os: macos-latest,   wheel: pp39-macosx_x86_64 }
-          - { os: macos-latest,   wheel: cp38-macosx_arm64 }
-          - { os: windows-latest, wheel: cp38-win_amd64 }
-          - { os: windows-latest, wheel: pp38-win_amd64 }
-          - { os: windows-latest, wheel: pp39-win_amd64 }
-          - { os: ubuntu-latest,  wheel: cp38-manylinux_x86_64 }
-          - { os: ubuntu-latest,  wheel: pp38-manylinux_x86_64 }
-          - { os: ubuntu-latest,  wheel: pp39-manylinux_x86_64 }
+          - { wheel: cp38-macosx_arm64,      os: macos-latest   }
+          - { wheel: cp38-macosx_x86_64,     os: macos-latest   }
+          - { wheel: cp38-manylinux_x86_64,  os: ubuntu-latest  }
+          - { wheel: cp38-win_amd64,         os: windows-latest }
+          - { wheel: cp39-macosx_arm64,      os: macos-latest   }
+          - { wheel: cp39-macosx_x86_64,     os: macos-latest   }
+          - { wheel: cp39-manylinux_x86_64,  os: ubuntu-latest  }
+          - { wheel: cp39-win_amd64,         os: windows-latest }
+          - { wheel: cp310-macosx_arm64,     os: macos-latest   }
+          - { wheel: cp310-macosx_x86_64,    os: macos-latest   }
+          - { wheel: cp310-manylinux_x86_64, os: ubuntu-latest  }
+          - { wheel: cp310-win_amd64,        os: windows-latest }
+          - { wheel: cp311-macosx_arm64,     os: macos-latest   }
+          - { wheel: cp311-macosx_x86_64,    os: macos-latest   }
+          - { wheel: cp311-manylinux_x86_64, os: ubuntu-latest  }
+          - { wheel: cp311-win_amd64,        os: windows-latest }
+
+          # Currently broken on pypy3.8
+          # - { wheel: pp38-macosx_x86_64,     os: macos-latest   }
+          # - { wheel: pp38-manylinux_x86_64,  os: ubuntu-latest  }
+          # - { wheel: pp38-win_amd64,         os: windows-latest }
+
+          - { wheel: pp39-macosx_x86_64,     os: macos-latest   }
+          - { wheel: pp39-manylinux_x86_64,  os: ubuntu-latest  }
+          - { wheel: pp39-win_amd64,         os: windows-latest }
     steps:
-      - uses: actions/setup-python@v4
-        with:
-          python-version: '3.8'
       - name: Download source distribution
         uses: actions/download-artifact@v3
         with:
@@ -67,59 +79,20 @@ jobs:
         shell: bash
         run: tar --strip-components 1 -xvf *.tar.gz
       - name: Build wheel
-        uses: pypa/cibuildwheel@v2.11.2
+        uses: pypa/cibuildwheel@v2.12.1
         with:
           output-dir: wheelhouse
         env:
           CIBW_ARCHS_MACOS: x86_64 arm64
           CIBW_BUILD: ${{ matrix.wheel }}
           CIBW_TEST_COMMAND: python -m unittest discover -v -s {package}/tests
-          CIBW_TEST_SKIP: "cp* *_arm64"
+          CIBW_TEST_SKIP: "*_arm64"
           CIBW_BUILD_VERBOSITY: 1
       - uses: actions/upload-artifact@v3
         with:
           name: wheels
           path: ./wheelhouse/*.whl
 
-  # CPython wheels are abi3 compatible and should work on 3.*,>=3.8
-  # Note: PyPy wheels are tested above with CIBW
-  test_wheels:
-    needs: build_wheels
-    name: Test with CPython ${{ matrix.py }} on ${{ matrix.os }}
-    runs-on: ${{ matrix.os }}
-    strategy:
-      matrix:
-        include:
-          - { os: macos-latest,   py: '3.8' }
-          - { os: macos-latest,   py: '3.11' }
-          - { os: windows-latest, py: '3.8' }
-          - { os: windows-latest, py: '3.11' }
-          - { os: ubuntu-latest,  py: '3.8' }
-          - { os: ubuntu-latest,  py: '3.11' }
-    steps:
-      - uses: actions/download-artifact@v3
-        with:
-          name: source
-          path: dist
-      - uses: actions/download-artifact@v3
-        with:
-          name: wheels
-          path: dist
-      - uses: actions/setup-python@v4
-        with:
-          python-version: ${{ matrix.py }}
-      - name: Run tests
-        shell: bash
-        run: |
-          tar --strip-components 1 -xvf ./dist/*.tar.gz
-          ls dist
-
-          pip install cffi types-cffi
-          pip install --no-index --find-links ./dist pypcode
-
-          pushd tests
-          python -m unittest discover -v -s .
-
   build_docs:
     name: Build docs
     runs-on: ubuntu-latest
@@ -139,7 +112,7 @@ jobs:
 
   upload_pypi:
     name: Upload wheels to PyPI
-    needs: [lint, build_docs, build_sdist, build_wheels, test_wheels]
+    needs: [lint, build_docs, build_sdist, build_wheels]
     runs-on: ubuntu-latest
     # upload to PyPI on every tag starting with 'v'
     if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')
diff --git a/.gitignore b/.gitignore
index 6862d0bd..0048aa98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,10 @@
 __pycache__/
 build/
 dist/
+pypcode/bin/
+docs/_build/
+*.so
+*.sla
+*.manifest
+*.gradle
+*.java
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6f53e9f3..b8b83e84 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,7 +1,7 @@
 ci:
     skip: [pylint]
 
-exclude: ^pypcode/native|^pypcode/processors
+exclude: ^pypcode/sleigh|^pypcode/processors
 repos:
 
 #
@@ -75,6 +75,13 @@ repos:
     hooks:
     -   id: black
 
+-   repo: https://github.com/pre-commit/mirrors-clang-format
+    rev: v16.0.0
+    hooks:
+    -   id: clang-format
+        files: pypcode/pypcode_native.cpp
+
+
 #
 # Static Checks
 #
@@ -98,7 +105,7 @@ repos:
     -   id: check-docstring-first
 
 -   repo: https://github.com/charliermarsh/ruff-pre-commit
-    rev: v0.0.254
+    rev: v0.0.258
     hooks:
     -   id: ruff
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 00000000..344fa051
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,60 @@
+cmake_minimum_required(VERSION 3.18...3.22)
+project(pypcode)
+find_package(Python COMPONENTS Interpreter Development.Module REQUIRED)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_OSX_DEPLOYMENT_TARGET 10.14)
+
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
+endif()
+
+# Detect the installed nanobind package and import it into CMake
+execute_process(
+  COMMAND "${Python_EXECUTABLE}" -c "import nanobind; print(nanobind.cmake_dir())"
+  OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE NB_DIR)
+list(APPEND CMAKE_PREFIX_PATH "${NB_DIR}")
+find_package(nanobind CONFIG REQUIRED)
+
+if(MSVC)
+  add_compile_options(/O2 /D_HAS_STD_BYTE=0)
+else()
+  add_compile_options(-O3 -Wall -Wno-sign-compare -D__TERMINAL__)
+endif()
+
+set(SLEIGH_COMMON
+  pypcode/sleigh/address.cc
+  pypcode/sleigh/context.cc
+  pypcode/sleigh/float.cc
+  pypcode/sleigh/globalcontext.cc
+  pypcode/sleigh/marshal.cc
+  pypcode/sleigh/opcodes.cc
+  pypcode/sleigh/pcodecompile.cc
+  pypcode/sleigh/pcodeparse.cc
+  pypcode/sleigh/pcoderaw.cc
+  pypcode/sleigh/semantics.cc
+  pypcode/sleigh/sleigh.cc
+  pypcode/sleigh/sleighbase.cc
+  pypcode/sleigh/slghpatexpress.cc
+  pypcode/sleigh/slghpattern.cc
+  pypcode/sleigh/slghsymbol.cc
+  pypcode/sleigh/space.cc
+  pypcode/sleigh/translate.cc
+  pypcode/sleigh/xml.cc
+  )
+
+add_executable(sleigh
+  pypcode/sleigh/filemanage.cc
+  pypcode/sleigh/slgh_compile.cc
+  pypcode/sleigh/slghparse.cc
+  pypcode/sleigh/slghscan.cc
+  ${SLEIGH_COMMON}
+  )
+install(TARGETS sleigh DESTINATION bin)
+
+nanobind_add_module(pypcode_native
+  pypcode/pypcode_native.cpp
+  ${SLEIGH_COMMON}
+  )
+install(TARGETS pypcode_native DESTINATION .)
diff --git a/MANIFEST.in b/MANIFEST.in
index f38990e1..3743174f 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,9 +1,7 @@
 graft pypcode
-prune pypcode/native/build
 prune pypcode/bin
+include CMakeLists.txt
 include LICENSE.txt
-include build_cffi.py
 graft tests
 global-exclude *.so
 global-exclude *.gitignore
-
diff --git a/build_cffi.py b/build_cffi.py
deleted file mode 100644
index afb67063..00000000
--- a/build_cffi.py
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/usr/bin/env python3
-import os.path
-import struct
-import subprocess
-import platform
-
-from setuptools.command.build_ext import build_ext
-
-
-ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
-LIB_SRC_DIR = os.path.join(ROOT_DIR, "pypcode", "native")
-CMAKE_BUILD_DIR = os.path.join(LIB_SRC_DIR, "build")
-
-
-class FfiPreBuildExtension(build_ext):
-    """
-    FFI Pre-Build Extension
-    """
-
-    def pre_run(self, _, ffi):
-        try:
-            subprocess.check_output(["cmake", "--version"])
-        except OSError as exc:
-            raise RuntimeError("Please install CMake to build") from exc
-
-        install_pkg_root = os.path.abspath(os.path.join(ROOT_DIR if self.inplace else self.build_lib, "pypcode"))
-        cmake_install_prefix = install_pkg_root
-        cmake_config_args = [
-            "-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON",
-            "-DCMAKE_WINDOWS_EXPORT_ALL_SYMBOLS=TRUE",
-            f"-DCMAKE_INSTALL_PREFIX={cmake_install_prefix}",
-        ]
-        cmake_build_args = []
-        if platform.system() == "Windows":
-            is_64b = struct.calcsize("P") * 8 == 64
-            cmake_config_args += ["-A", "x64" if is_64b else "Win32"]
-            cmake_build_args += ["--config", "Release"]
-
-        # Build sleigh and csleigh library
-        subprocess.check_call(["cmake", "-S", ".", "-B", "build"] + cmake_config_args, cwd=LIB_SRC_DIR)
-        subprocess.check_call(
-            ["cmake", "--build", "build", "--parallel", "--verbose"] + cmake_build_args,
-            cwd=LIB_SRC_DIR,
-        )
-        subprocess.check_call(["cmake", "--install", "build"], cwd=LIB_SRC_DIR)
-
-        # Build sla files
-        bin_ext = ".exe" if platform.system() == "Windows" else ""
-        sleigh_bin = os.path.join(install_pkg_root, "bin", "sleigh" + bin_ext)
-        specfiles_dir = os.path.join(install_pkg_root, "processors")
-        subprocess.check_call([sleigh_bin, "-a", specfiles_dir])
-
-        with open(os.path.join(CMAKE_BUILD_DIR, "csleigh.i"), encoding="utf-8") as f:
-            ffi.cdef(f.read())
-
-
-def ffibuilder():
-    from cffi import FFI  # pylint:disable=import-outside-toplevel
-
-    ffi = FFI()
-    LIBS = {"Darwin": ["c++"], "Linux": ["stdc++"]}.get(platform.system(), [])
-    ffi.set_source(
-        "pypcode._csleigh",
-        """
-        #include "build/csleigh.i"
-        """,
-        libraries=["csleigh"] + LIBS,
-        include_dirs=[LIB_SRC_DIR],
-        library_dirs=[os.path.join(CMAKE_BUILD_DIR, "lib")],
-    )
-    return ffi
-
-
-if __name__ == "__main__":
-    ffibuilder().compile(verbose=True)
diff --git a/docs/.gitignore b/docs/.gitignore
deleted file mode 100644
index e35d8850..00000000
--- a/docs/.gitignore
+++ /dev/null
@@ -1,1 +0,0 @@
-_build
diff --git a/docs/guide.rst b/docs/guide.rst
index 561594e0..ae1f42cc 100644
--- a/docs/guide.rst
+++ b/docs/guide.rst
@@ -19,27 +19,25 @@ The very latest development version can be installed from GitHub via:
 Usage Example
 -------------
 
+Disassemble:
+
+.. ipython::
+
+   In [0]: from pypcode import Context, PcodePrettyPrinter
+      ...: ctx = Context("x86:LE:64:default")
+      ...: dx = ctx.disassemble(b"\x48\x35\x78\x56\x34\x12\xc3")
+      ...: for ins in dx.instructions:
+      ...:     print(f"{ins.addr.offset:#x}/{ins.length}: {ins.mnem} {ins.body}")
+
+Translate to P-Code:
+
 .. ipython::
 
-   In [0]: from pypcode import Arch, Context, PcodePrettyPrinter
-      ...:
-      ...: # Enumerate available languages, select target language
-      ...: langs = {lang.id: lang for arch in Arch.enumerate() for lang in arch.languages}
-      ...: lang = langs["x86:LE:64:default"]
-      ...:
-      ...: # Load machine code
-      ...: machine_code = b"\x48\x35\x78\x56\x34\x12\xc3"
-      ...: base_address = 0
-      ...:
-      ...: # Create a translation context, lift a block
-      ...: ctx = Context(lang)
-      ...: result = ctx.translate(machine_code, base_address, bb_terminating=True)
-      ...:
-      ...: # Print instruction disassembly, P-Code
-      ...: for insn in result.instructions:
-      ...:     print(f"-- {insn.address.offset:#x}: {insn.asm_mnem} {insn.asm_body}")
-      ...:     for op in insn.ops:
-      ...:         print(f"{op.seq.uniq}) {PcodePrettyPrinter.fmt_op(op)}")
+   In [0]: from pypcode import Context, PcodePrettyPrinter
+      ...: ctx = Context("x86:LE:64:default")
+      ...: tx = ctx.translate(b"\x48\x35\x78\x56\x34\x12\xc3")
+      ...: for op in tx.ops:
+      ...:     print(PcodePrettyPrinter.fmt_op(op))
 
 Command Line Usage Example
 --------------------------
diff --git a/pypcode/.gitignore b/pypcode/.gitignore
deleted file mode 100644
index b5182032..00000000
--- a/pypcode/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-*.so
-bin
diff --git a/pypcode/__init__.py b/pypcode/__init__.py
index 3d3ded1d..fd85caf5 100644
--- a/pypcode/__init__.py
+++ b/pypcode/__init__.py
@@ -1,209 +1,72 @@
 #!/usr/bin/env python
 """
-Pythonic interface to SLEIGH by way of the csleigh C API wrapper and CFFI.
+Pythonic interface to SLEIGH
 """
 
-__version__ = "1.1.3.dev0"
-
 import os.path
 import xml.etree.ElementTree as ET
-from enum import Enum
-from typing import TYPE_CHECKING, cast, Dict, Generator, Sequence, Optional, Mapping, Union, Tuple
-
-from ._csleigh import ffi  # pylint:disable=no-name-in-module
-from ._csleigh.lib import (  # pylint:disable=no-name-in-module,import-error
-    csleigh_Addr_getSpaceFromConst,
-    csleigh_Addr_isConstant,
-    csleigh_AddrSpace_getName,
-    csleigh_CPUI_BOOL_AND,
-    csleigh_CPUI_BOOL_NEGATE,
-    csleigh_CPUI_BOOL_OR,
-    csleigh_CPUI_BOOL_XOR,
-    csleigh_CPUI_BRANCH,
-    csleigh_CPUI_BRANCHIND,
-    csleigh_CPUI_CALL,
-    csleigh_CPUI_CALLIND,
-    csleigh_CPUI_CALLOTHER,
-    csleigh_CPUI_CAST,
-    csleigh_CPUI_CBRANCH,
-    csleigh_CPUI_COPY,
-    csleigh_CPUI_CPOOLREF,
-    csleigh_CPUI_EXTRACT,
-    csleigh_CPUI_FLOAT_ABS,
-    csleigh_CPUI_FLOAT_ADD,
-    csleigh_CPUI_FLOAT_CEIL,
-    csleigh_CPUI_FLOAT_DIV,
-    csleigh_CPUI_FLOAT_EQUAL,
-    csleigh_CPUI_FLOAT_FLOAT2FLOAT,
-    csleigh_CPUI_FLOAT_FLOOR,
-    csleigh_CPUI_FLOAT_INT2FLOAT,
-    csleigh_CPUI_FLOAT_LESS,
-    csleigh_CPUI_FLOAT_LESSEQUAL,
-    csleigh_CPUI_FLOAT_MULT,
-    csleigh_CPUI_FLOAT_NAN,
-    csleigh_CPUI_FLOAT_NEG,
-    csleigh_CPUI_FLOAT_NOTEQUAL,
-    csleigh_CPUI_FLOAT_ROUND,
-    csleigh_CPUI_FLOAT_SQRT,
-    csleigh_CPUI_FLOAT_SUB,
-    csleigh_CPUI_FLOAT_TRUNC,
-    csleigh_CPUI_INDIRECT,
-    csleigh_CPUI_INSERT,
-    csleigh_CPUI_INT_2COMP,
-    csleigh_CPUI_INT_ADD,
-    csleigh_CPUI_INT_AND,
-    csleigh_CPUI_INT_CARRY,
-    csleigh_CPUI_INT_DIV,
-    csleigh_CPUI_INT_EQUAL,
-    csleigh_CPUI_INT_LEFT,
-    csleigh_CPUI_INT_LESS,
-    csleigh_CPUI_INT_LESSEQUAL,
-    csleigh_CPUI_INT_MULT,
-    csleigh_CPUI_INT_NEGATE,
-    csleigh_CPUI_INT_NOTEQUAL,
-    csleigh_CPUI_INT_OR,
-    csleigh_CPUI_INT_REM,
-    csleigh_CPUI_INT_RIGHT,
-    csleigh_CPUI_INT_SBORROW,
-    csleigh_CPUI_INT_SCARRY,
-    csleigh_CPUI_INT_SDIV,
-    csleigh_CPUI_INT_SEXT,
-    csleigh_CPUI_INT_SLESS,
-    csleigh_CPUI_INT_SLESSEQUAL,
-    csleigh_CPUI_INT_SREM,
-    csleigh_CPUI_INT_SRIGHT,
-    csleigh_CPUI_INT_SUB,
-    csleigh_CPUI_INT_XOR,
-    csleigh_CPUI_INT_ZEXT,
-    csleigh_CPUI_LOAD,
-    csleigh_CPUI_MULTIEQUAL,
-    csleigh_CPUI_NEW,
-    csleigh_CPUI_PIECE,
-    csleigh_CPUI_POPCOUNT,
-    csleigh_CPUI_PTRADD,
-    csleigh_CPUI_PTRSUB,
-    csleigh_CPUI_RETURN,
-    csleigh_CPUI_SEGMENTOP,
-    csleigh_CPUI_STORE,
-    csleigh_CPUI_SUBPIECE,
-    csleigh_createContext,
-    csleigh_destroyContext,
-    csleigh_ERROR_TYPE_BADDATA,
-    csleigh_ERROR_TYPE_GENERIC,
-    csleigh_ERROR_TYPE_NOERROR,
-    csleigh_ERROR_TYPE_UNIMPL,
-    csleigh_freeResult,
-    csleigh_setVariableDefault,
-    csleigh_Sleigh_getAllRegisters,
-    csleigh_Sleigh_getRegisterName,
-    csleigh_translate,
+from enum import IntEnum
+from typing import cast, Dict, Generator, Sequence, Optional, Mapping, Tuple, Union
+
+from .__version__ import __version__
+
+from .pypcode_native import __version__ as __pypcode_native_version__  # pylint:disable=no-name-in-module
+
+assert __version__ == __pypcode_native_version__
+
+from .pypcode_native import (  # pylint:disable=no-name-in-module
+    Address,
+    AddrSpace,
+    BadDataError,
+    DecoderError,
+    Disassembly,
+    Instruction,
+    OpCode,
+    PcodeOp,
+    TRANSLATE_FLAGS_BB_TERMINATING,
+    Translation,
+    UnimplError,
+    Varnode,
 )
 
-if TYPE_CHECKING:
-    from ._csleigh.lib import (
-        csleigh_Address,
-        csleigh_AddrSpace,
-        csleigh_Context,
-        csleigh_Error,
-        csleigh_PcodeOp,
-        csleigh_SeqNum,
-        csleigh_Translation,
-        csleigh_TranslationResult,
-        csleigh_Varnode,
-    )
+from .pypcode_native import Context as _Context  # pylint:disable=no-name-in-module
 
 
-PKG_SRC_DIR = os.path.abspath(os.path.dirname(__file__))
-SPECFILES_DIR = os.path.join(PKG_SRC_DIR, "processors")
+__all__ = [
+    "Address",
+    "AddrSpace",
+    "Arch",
+    "ArchLanguage",
+    "BadDataError",
+    "Context",
+    "DecoderError",
+    "Disassembly",
+    "Instruction",
+    "OpCode",
+    "OpFormat",
+    "OpFormatBinary",
+    "OpFormatFunc",
+    "OpFormatSpecial",
+    "OpFormatUnary",
+    "PcodeOp",
+    "PcodePrettyPrinter",
+    "TranslateFlags",
+    "Translation",
+    "UnimplError",
+    "Varnode",
+]
 
 
-class OpCode(Enum):
+class TranslateFlags(IntEnum):
     """
-    1:1 mapping of C enumeration csleigh_CPUI_* to Enum
+    Flags that can be passed to Context::translate
     """
 
-    BOOL_AND = csleigh_CPUI_BOOL_AND
-    BOOL_NEGATE = csleigh_CPUI_BOOL_NEGATE
-    BOOL_OR = csleigh_CPUI_BOOL_OR
-    BOOL_XOR = csleigh_CPUI_BOOL_XOR
-    BRANCH = csleigh_CPUI_BRANCH
-    BRANCHIND = csleigh_CPUI_BRANCHIND
-    CALL = csleigh_CPUI_CALL
-    CALLIND = csleigh_CPUI_CALLIND
-    CALLOTHER = csleigh_CPUI_CALLOTHER
-    CAST = csleigh_CPUI_CAST
-    CBRANCH = csleigh_CPUI_CBRANCH
-    COPY = csleigh_CPUI_COPY
-    CPOOLREF = csleigh_CPUI_CPOOLREF
-    EXTRACT = csleigh_CPUI_EXTRACT
-    FLOAT_ABS = csleigh_CPUI_FLOAT_ABS
-    FLOAT_ADD = csleigh_CPUI_FLOAT_ADD
-    FLOAT_CEIL = csleigh_CPUI_FLOAT_CEIL
-    FLOAT_DIV = csleigh_CPUI_FLOAT_DIV
-    FLOAT_EQUAL = csleigh_CPUI_FLOAT_EQUAL
-    FLOAT_FLOAT2FLOAT = csleigh_CPUI_FLOAT_FLOAT2FLOAT
-    FLOAT_FLOOR = csleigh_CPUI_FLOAT_FLOOR
-    FLOAT_INT2FLOAT = csleigh_CPUI_FLOAT_INT2FLOAT
-    FLOAT_LESS = csleigh_CPUI_FLOAT_LESS
-    FLOAT_LESSEQUAL = csleigh_CPUI_FLOAT_LESSEQUAL
-    FLOAT_MULT = csleigh_CPUI_FLOAT_MULT
-    FLOAT_NAN = csleigh_CPUI_FLOAT_NAN
-    FLOAT_NEG = csleigh_CPUI_FLOAT_NEG
-    FLOAT_NOTEQUAL = csleigh_CPUI_FLOAT_NOTEQUAL
-    FLOAT_ROUND = csleigh_CPUI_FLOAT_ROUND
-    FLOAT_SQRT = csleigh_CPUI_FLOAT_SQRT
-    FLOAT_SUB = csleigh_CPUI_FLOAT_SUB
-    FLOAT_TRUNC = csleigh_CPUI_FLOAT_TRUNC
-    INDIRECT = csleigh_CPUI_INDIRECT
-    INSERT = csleigh_CPUI_INSERT
-    INT_2COMP = csleigh_CPUI_INT_2COMP
-    INT_ADD = csleigh_CPUI_INT_ADD
-    INT_AND = csleigh_CPUI_INT_AND
-    INT_CARRY = csleigh_CPUI_INT_CARRY
-    INT_DIV = csleigh_CPUI_INT_DIV
-    INT_EQUAL = csleigh_CPUI_INT_EQUAL
-    INT_LEFT = csleigh_CPUI_INT_LEFT
-    INT_LESS = csleigh_CPUI_INT_LESS
-    INT_LESSEQUAL = csleigh_CPUI_INT_LESSEQUAL
-    INT_MULT = csleigh_CPUI_INT_MULT
-    INT_NEGATE = csleigh_CPUI_INT_NEGATE
-    INT_NOTEQUAL = csleigh_CPUI_INT_NOTEQUAL
-    INT_OR = csleigh_CPUI_INT_OR
-    INT_REM = csleigh_CPUI_INT_REM
-    INT_RIGHT = csleigh_CPUI_INT_RIGHT
-    INT_SBORROW = csleigh_CPUI_INT_SBORROW
-    INT_SCARRY = csleigh_CPUI_INT_SCARRY
-    INT_SDIV = csleigh_CPUI_INT_SDIV
-    INT_SEXT = csleigh_CPUI_INT_SEXT
-    INT_SLESS = csleigh_CPUI_INT_SLESS
-    INT_SLESSEQUAL = csleigh_CPUI_INT_SLESSEQUAL
-    INT_SREM = csleigh_CPUI_INT_SREM
-    INT_SRIGHT = csleigh_CPUI_INT_SRIGHT
-    INT_SUB = csleigh_CPUI_INT_SUB
-    INT_XOR = csleigh_CPUI_INT_XOR
-    INT_ZEXT = csleigh_CPUI_INT_ZEXT
-    LOAD = csleigh_CPUI_LOAD
-    MULTIEQUAL = csleigh_CPUI_MULTIEQUAL
-    NEW = csleigh_CPUI_NEW
-    PIECE = csleigh_CPUI_PIECE
-    POPCOUNT = csleigh_CPUI_POPCOUNT
-    PTRADD = csleigh_CPUI_PTRADD
-    PTRSUB = csleigh_CPUI_PTRSUB
-    RETURN = csleigh_CPUI_RETURN
-    SEGMENTOP = csleigh_CPUI_SEGMENTOP
-    STORE = csleigh_CPUI_STORE
-    SUBPIECE = csleigh_CPUI_SUBPIECE
-
-
-class SleighErrorType(Enum):
-    """
-    1:1 mapping of C enumeration csleigh_ERROR_TYPE_* to Enum
-    """
+    BB_TERMINATING = TRANSLATE_FLAGS_BB_TERMINATING
 
-    BADDATA = csleigh_ERROR_TYPE_BADDATA
-    GENERIC = csleigh_ERROR_TYPE_GENERIC
-    NOERROR = csleigh_ERROR_TYPE_NOERROR
-    UNIMPL = csleigh_ERROR_TYPE_UNIMPL
+
+PKG_SRC_DIR = os.path.abspath(os.path.dirname(__file__))
+SPECFILES_DIR = os.path.join(PKG_SRC_DIR, "processors")
 
 
 class ArchLanguage:
@@ -263,7 +126,7 @@ def cspecs(self) -> Mapping[Tuple[str, str], ET.Element]:
                 self._cspecs[(e.attrib["id"], e.attrib["name"])] = cspec
         return self._cspecs
 
-    def init_context_from_pspec(self, ctx: "csleigh_Context") -> None:
+    def init_context_from_pspec(self, ctx: "Context") -> None:
         if self.pspec is None:
             return
         cd = self.pspec.find("context_data")
@@ -274,7 +137,7 @@ def init_context_from_pspec(self, ctx: "csleigh_Context") -> None:
             return
         for e in cs:
             assert e.tag == "set"
-            csleigh_setVariableDefault(ctx, e.attrib["name"].encode("utf-8"), int(e.attrib["val"]))
+            ctx.setVariableDefault(e.attrib["name"], int(e.attrib["val"]))
 
     @classmethod
     def from_id(cls, langid: str) -> Optional["ArchLanguage"]:
@@ -307,9 +170,15 @@ class Arch:
     ldef: ET.ElementTree
     languages: Sequence[ArchLanguage]
 
-    def __init__(self, archname: str, ldefpath: str):
+    def __init__(self, name: str, ldefpath: str):
+        """
+        Initialize the Arch.
+
+        :param name: The name of the architecture
+        :param ldefpath: Path to language definition files (.ldefs)
+        """
         self.archpath = os.path.dirname(ldefpath)
-        self.archname = archname
+        self.archname = name
         self.ldefpath = ldefpath
         self.ldef = ET.parse(ldefpath)
         self.languages = [ArchLanguage(self.archpath, e) for e in self.ldef.getroot()]
@@ -333,308 +202,36 @@ def enumerate(cls) -> Generator["Arch", None, None]:
                 yield Arch(archname, ldefpath)
 
 
-class ContextCreationError(Exception):
-    """
-    An error during context creation.
-    """
-
-
-class Context:
+class Context(_Context):
     """
     Context for translation.
     """
 
     __slots__ = (
-        "lang",
-        "ctx_c",
-        "_cached_addr_spaces",
+        "language",
         "registers",
     )
 
-    lang: ArchLanguage
-    ctx_c: "csleigh_Context"
-    _cached_addr_spaces: Dict["csleigh_AddrSpace", "AddrSpace"]
-
-    def __init__(self, lang: ArchLanguage):
-        self._cached_addr_spaces = {}
-        self.lang = lang
-        self.ctx_c = csleigh_createContext(self.lang.slafile_path.encode("utf-8"))
-        if not self.ctx_c:
-            raise ContextCreationError()
-        self.lang.init_context_from_pspec(self.ctx_c)
-
-        count_a = ffi.new("unsigned int *count")
-        regs = csleigh_Sleigh_getAllRegisters(self.ctx_c, count_a)
-
-        self.registers = {}
-        for i in range(count_a[0]):
-            reg_name = cast(bytes, ffi.string(regs[i].name)).decode("utf-8")
-            reg_vn = Varnode.from_c(self, regs[i].varnode)
-            self.registers[reg_name] = reg_vn
-
-    def __del__(self):
-        csleigh_destroyContext(self.ctx_c)
-
-    def get_cached_addr_space(self, cobj: "csleigh_AddrSpace") -> Optional["AddrSpace"]:
-        """
-        Used during translation to cache unchanging address space objects. Should
-        not be called by pypcode users.
-        """
-        return self._cached_addr_spaces.get(cobj)
-
-    def set_cached_addr_space(self, cobj: "csleigh_AddrSpace", pobj: "AddrSpace") -> None:
-        """
-        Used during translation to cache unchanging address space objects. Should
-        not be called by pypcode users.
-        """
-        self._cached_addr_spaces[cobj] = pobj
-
-    def translate(
-        self,
-        code: Union[bytes, bytearray],
-        base: int,
-        max_inst: int = 0,
-        max_bytes: int = 0,
-        bb_terminating: bool = False,
-    ) -> "TranslationResult":
-        """
-        Disassemble and translate to p-code.
-
-        :param code:           Buffer of machine code to translate
-        :param base:           Base address of the machine code
-        :param max_inst:       Maximum number of instructions to translate, or 0 for no limit
-        :param max_bytes:      Maximum number of bytes to translate, or 0 for no limit
-        :param bb_terminating: End translation at basic block boundaries
-        """
-        c_data = ffi.from_buffer(code)
-        max_bytes = min(len(code), max_bytes) if (max_bytes > 0) else len(code)
-        res_c = csleigh_translate(self.ctx_c, c_data, max_bytes, base, max_inst, bb_terminating)
-        res = TranslationResult.from_c(self, res_c)
-        csleigh_freeResult(res_c)
-        if res.error:
-            raise res.error
-        return res
-
-    def get_register_name(self, space: "AddrSpace", offset: int, size: int) -> str:
-        """
-        Call SleighBase::getRegisterName in this context.
-        """
-        return cast(bytes, ffi.string(csleigh_Sleigh_getRegisterName(self.ctx_c, space.to_c(), offset, size))).decode(
-            "utf-8"
-        )
-
-
-class ContextObj:
-    """
-    Base class for objects that are context-sensitive.
-    """
-
-    __slots__ = ("ctx",)
-
-    ctx: Context
-
-    def __init__(self, ctx: Context):
-        self.ctx = ctx
-
-
-class AddrSpace(ContextObj):
-    """
-    An address space.
-    """
-
-    __slots__ = (
-        "cobj",
-        "name",
-    )
-
-    cobj: "csleigh_AddrSpace"
-    name: str
-
-    def __init__(self, ctx: Context, cobj: "csleigh_AddrSpace"):
-        super().__init__(ctx)
-        self.cobj = cobj
-        self.name = cast(bytes, ffi.string(csleigh_AddrSpace_getName(cobj))).decode("utf-8")
-
-    @classmethod
-    def from_c_uncached(cls, ctx: Context, cobj: "csleigh_AddrSpace") -> "AddrSpace":
-        return cls(ctx, cobj)
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_AddrSpace") -> "AddrSpace":
-        obj = ctx.get_cached_addr_space(cobj)
-        if obj is None:
-            obj = cls.from_c_uncached(ctx, cobj)
-            ctx.set_cached_addr_space(cobj, obj)
-        return obj
-
-    def to_c(self) -> "csleigh_AddrSpace":
-        return self.cobj
-
-
-class Address(ContextObj):
-    """
-    Addresses a byte within some address space.
-    """
-
-    __slots__ = (
-        "space",
-        "offset",
-    )
-
-    space: AddrSpace
-    offset: int
+    language: ArchLanguage
+    registers: Dict[str, Varnode]
 
-    def __init__(self, ctx: Context, space: AddrSpace, offset: int):
-        super().__init__(ctx)
-        self.space = space
-        self.offset = offset
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_Address") -> "Address":
-        return cls(ctx, AddrSpace.from_c(ctx, cobj.space), cobj.offset)
-
-    def to_c(self) -> "csleigh_Address":
-        # XXX: The boxing in AddrSpace/Address is a little excessive, but lets us
-        # access the API as provided by SLEIGH, and should still be fast.
-        cobj = cast("csleigh_Address", ffi.new("csleigh_Address *"))
-        cobj.space = self.space.to_c()
-        cobj.offset = self.offset
-        return cobj
-
-    @property
-    def is_constant(self) -> bool:
+    def __init__(self, language: Union[ArchLanguage, str]):
         """
-        Return True if this address is in the constant space.
-        """
-        return csleigh_Addr_isConstant(self.to_c())
+        Initialize a context for translation or disassembly.
 
-    def get_space_from_const(self) -> AddrSpace:
-        """
-        For LOAD/STORE, the address space which is being accessed is encoded as
-        a pointer stored in a constant space offset. See Address::getSpaceFromConst.
+        :param language: The ``ArchLanguage`` to initialize the context with, or a language id ``str``
         """
-        return AddrSpace.from_c(self.ctx, csleigh_Addr_getSpaceFromConst(self.to_c()))
-
-
-class Varnode(ContextObj):
-    """
-    Data manipulated by p-code.
-    """
-
-    __slots__ = (
-        "space",
-        "offset",
-        "size",
-    )
-
-    space: AddrSpace
-    offset: int
-    size: int
-
-    def __init__(self, ctx: Context, space: AddrSpace, offset: int, size: int):
-        super().__init__(ctx)
-        self.space = space
-        self.offset = offset
-        self.size = size
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_Varnode") -> "Varnode":
-        return cls(ctx, AddrSpace.from_c(ctx, cobj.space), cobj.offset, cobj.size)
-
-    def __str__(self):
-        return "%s[%#x:%d]" % (self.space.name, self.offset, self.size)
-
-    def get_addr(self) -> Address:
-        return Address(self.ctx, self.space, self.offset)
-
-    def get_register_name(self) -> str:
-        """
-        Convenience function to call SleighBase::getRegisterName with current
-        Varnode attributes.
-        """
-        return self.ctx.get_register_name(self.space, self.offset, self.size)
-
-    def get_space_from_const(self) -> AddrSpace:
-        """
-        Convenience function to call Address::getSpaceFromConst with current Varnode
-        attributes.
-        """
-        return self.get_addr().get_space_from_const()
-
-
-class SeqNum(ContextObj):
-    """
-    Unique address for p-code ops translated from an instruction at `pc`.
-    """
-
-    __slots__ = (
-        "pc",
-        "uniq",
-        "order",
-    )
-
-    pc: Address
-    uniq: int
-    order: int
-
-    def __init__(self, ctx: Context, pc: Address, uniq: int, order: int):
-        super().__init__(ctx)
-        self.pc = pc
-        self.uniq = uniq
-        self.order = order
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_SeqNum") -> "SeqNum":
-        return cls(ctx, Address.from_c(ctx, cobj.pc), cobj.uniq, cobj.order)
-
-
-class PcodeOp(ContextObj):
-    """
-    A single p-code op.
-    """
-
-    __slots__ = (
-        "seq",
-        "opcode",
-        "output",
-        "inputs",
-    )
-
-    seq: SeqNum
-    opcode: OpCode
-    output: Optional[Varnode]
-    inputs: Sequence[Varnode]
-
-    def __init__(
-        self,
-        ctx: Context,
-        seq: SeqNum,
-        opcode: OpCode,
-        inputs: Sequence[Varnode],
-        output: Optional[Varnode] = None,
-    ):
-        super().__init__(ctx)
-        self.seq = seq
-        self.opcode = opcode
-        self.output = output
-        self.inputs = inputs
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_PcodeOp") -> "PcodeOp":
-        return cls(
-            ctx,
-            SeqNum.from_c(ctx, cobj.seq),
-            OpCode(cobj.opcode),
-            [Varnode.from_c(ctx, cobj.inputs[i]) for i in range(cobj.inputs_count)],
-            Varnode.from_c(ctx, cobj.output) if cobj.output else None,
-        )
-
-    def __str__(self):
-        s = ""
-        if self.output:
-            s += str(self.output) + " = "
-        s += f'{self.opcode.name} {", ".join(str(i) for i in self.inputs)}'
-        return s
+        if isinstance(language, ArchLanguage):
+            self.language = language
+        elif isinstance(language, str):
+            _l = ArchLanguage.from_id(cast(str, language))
+            assert _l is not None
+            self.language = _l
+        else:
+            assert False, "Context must be initialized with a language or language id"
+        super().__init__(self.language.slafile_path)
+        self.language.init_context_from_pspec(self)
+        self.registers = {n: v for v, n in self.getAllRegisters().items()}
 
 
 class OpFormat:
@@ -647,13 +244,13 @@ def fmt_vn(vn: Varnode) -> str:
         if vn.space.name == "const":
             return "%#x" % vn.offset
         elif vn.space.name == "register":
-            name = vn.get_register_name()
+            name = vn.getRegisterName()
             if name:
                 return name
-        return str(vn)
+        return f"{vn.space.name}[{vn.offset:x}:{vn.size:d}]"
 
     def fmt(self, op: PcodeOp) -> str:
-        return f'{op.opcode.name} {", ".join(self.fmt_vn(i) for i in op.inputs)}'
+        return f'{op.opcode.__name__} {", ".join(self.fmt_vn(i) for i in op.inputs)}'
 
 
 class OpFormatUnary(OpFormat):
@@ -722,13 +319,13 @@ def fmt_CBRANCH(self, op: PcodeOp) -> str:
         return f"if ({self.fmt_vn(op.inputs[1])}) goto {self.fmt_vn(op.inputs[0])}"
 
     def fmt_LOAD(self, op: PcodeOp) -> str:
-        return f"*[{op.inputs[0].get_space_from_const().name}]{self.fmt_vn(op.inputs[1])}"
+        return f"*[{op.inputs[0].getSpaceFromConst().name}]{self.fmt_vn(op.inputs[1])}"
 
     def fmt_RETURN(self, op: PcodeOp) -> str:
         return f"return {self.fmt_vn(op.inputs[0])}"
 
     def fmt_STORE(self, op: PcodeOp) -> str:
-        return f"*[{op.inputs[0].get_space_from_const().name}]{self.fmt_vn(op.inputs[1])} = {op.inputs[2]}"
+        return f"*[{op.inputs[0].getSpaceFromConst().name}]{self.fmt_vn(op.inputs[1])} = {op.inputs[2]}"
 
     def fmt(self, op: PcodeOp) -> str:
         return {
@@ -817,177 +414,3 @@ class PcodePrettyPrinter:
     def fmt_op(cls, op: PcodeOp) -> str:
         fmt = cls.OP_FORMATS.get(op.opcode, cls.DEFAULT_OP_FORMAT)
         return (f"{fmt.fmt_vn(op.output)} = " if op.output else "") + fmt.fmt(op)
-
-
-class Translation(ContextObj):
-    """
-    Translation of a machine instruction to p-code.
-    """
-
-    __slots__ = (
-        "address",
-        "length",
-        "asm_mnem",
-        "asm_body",
-        "ops",
-    )
-
-    address: Address
-    length: int
-    asm_mnem: str
-    asm_body: str
-    ops: Sequence[PcodeOp]
-
-    def __init__(
-        self,
-        ctx: Context,
-        address: Address,
-        length: int,
-        asm_mnem: str,
-        asm_body: str,
-        ops: Sequence[PcodeOp],
-    ):
-        super().__init__(ctx)
-        self.address = address
-        self.length = length
-        self.asm_mnem = asm_mnem
-        self.asm_body = asm_body
-        self.ops = ops
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_Translation") -> "Translation":
-        return cls(
-            ctx,
-            Address.from_c(ctx, cobj.address),
-            cobj.length,
-            cast(bytes, ffi.string(cobj.asm_mnem)).decode("utf-8"),
-            cast(bytes, ffi.string(cobj.asm_body)).decode("utf-8"),
-            [PcodeOp.from_c(ctx, cobj.ops[i]) for i in range(cobj.ops_count)],
-        )
-
-
-class SleighError(Exception):
-    """
-    Base class for translation errors.
-    """
-
-    __slots__ = ("ctx",)
-
-    ctx: Context
-
-    def __init__(self, ctx: Context, explain: str):
-        super().__init__(explain)
-        self.ctx = ctx
-
-
-class UnimplError(SleighError):
-    """
-    UnimplError exception.
-    """
-
-    __slots__ = (
-        "explain",
-        "address",
-        "instruction_length",
-    )
-
-    explain: str
-    address: Address
-    instruction_length: int
-
-    def __init__(self, ctx: Context, explain: str, address: Address, instruction_length: int):
-        super().__init__(ctx, explain)
-        self.explain = explain
-        self.address = address
-        self.instruction_length = instruction_length
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_Error") -> "UnimplError":
-        assert SleighErrorType(cobj.type) == SleighErrorType.UNIMPL
-        return cls(
-            ctx,
-            cast(bytes, ffi.string(cobj.explain)).decode("utf-8"),
-            Address.from_c(ctx, cobj.unimpl.address),
-            cobj.unimpl.instruction_length,
-        )
-
-
-class BadDataError(SleighError):
-    """
-    BadDataError exception.
-    """
-
-    __slots__ = (
-        "explain",
-        "address",
-    )
-
-    explain: str
-    address: Address
-
-    def __init__(self, ctx: Context, explain: str, address: Address):
-        super().__init__(ctx, explain)
-        self.explain = explain
-        self.address = address
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_Error") -> "BadDataError":
-        assert SleighErrorType(cobj.type) == SleighErrorType.BADDATA
-        return cls(
-            ctx,
-            cast(bytes, ffi.string(cobj.explain)).decode("utf-8"),
-            Address.from_c(ctx, cobj.unimpl.address),
-        )
-
-
-class SleighErrorFactory:
-    """
-    Generates SleighErrorType objects from csleigh errors.
-    """
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_Error") -> Union[None, SleighError, UnimplError, BadDataError]:
-        t = SleighErrorType(cobj.type)
-        if t == SleighErrorType.NOERROR:
-            return None
-
-        error_type_to_handler = {
-            SleighErrorType.UNIMPL: UnimplError.from_c,
-            SleighErrorType.BADDATA: BadDataError.from_c,
-        }
-        handler = error_type_to_handler.get(t)
-        if handler is None:
-            return SleighError(ctx, "Unknown error")
-        return handler(ctx, cobj)
-
-
-class TranslationResult(ContextObj):
-    """
-    The result of a translation.
-    """
-
-    __slots__ = (
-        "instructions",
-        "error",
-    )
-
-    instructions: Sequence[Translation]
-    error: Optional[SleighError]
-
-    def __init__(
-        self,
-        ctx: Context,
-        instructions: Sequence[Translation],
-        error: Optional[SleighError] = None,
-    ):
-        super().__init__(ctx)
-        self.instructions = instructions
-        self.error = error
-
-    @classmethod
-    def from_c(cls, ctx: Context, cobj: "csleigh_TranslationResult") -> "TranslationResult":
-        return cls(
-            ctx,
-            [Translation.from_c(ctx, cobj.instructions[i]) for i in range(cobj.instructions_count)],
-            SleighErrorFactory.from_c(ctx, cobj.error),
-        )
diff --git a/pypcode/__main__.py b/pypcode/__main__.py
index 13ff1610..61832206 100644
--- a/pypcode/__main__.py
+++ b/pypcode/__main__.py
@@ -11,7 +11,7 @@
 import sys
 from difflib import SequenceMatcher
 
-from pypcode import Arch, BadDataError, Context, PcodePrettyPrinter, UnimplError
+from pypcode import Arch, BadDataError, Context, OpCode, PcodePrettyPrinter, TranslateFlags, UnimplError
 
 log = logging.getLogger(__name__)
 logging.basicConfig(level=logging.DEBUG, format="[%(name)s:%(levelname)s] %(message)s")
@@ -40,13 +40,6 @@ def main():
         type=int,
         help="maximum number of instructions to translate",
     )
-    ap.add_argument(
-        "-r",
-        "--raw",
-        action="store_true",
-        default=False,
-        help="show raw p-code (disable pretty-printing)",
-    )
     ap.add_argument(
         "-b",
         "--basic-block",
@@ -87,19 +80,26 @@ def main():
     ctx = Context(langs[args.langid])
 
     try:
-        res = ctx.translate(code, base, args.max_instructions, bb_terminating=args.basic_block)
+        flags = TranslateFlags.BB_TERMINATING if args.basic_block else 0
+        res = ctx.translate(code, base, max_instructions=args.max_instructions, flags=flags)
+
+        last_imark_idx = 0
+        for i, op in enumerate(res.ops):
+            if op.opcode == OpCode.IMARK:
+                last_imark_idx = i
+                disas_addr = op.inputs[0].offset
+                disas_offset = disas_addr - base
+                disas_len = sum(vn.size for vn in op.inputs)
+                disas_slice = code[disas_offset : disas_offset + disas_len]
+                for insn in ctx.disassemble(disas_slice, disas_addr).instructions:
+                    print(f"{insn.addr.offset:#x}/{insn.length}: {insn.mnem} {insn.body}")
+            else:
+                print(f" {i - last_imark_idx - 1:3d}: {PcodePrettyPrinter.fmt_op(op)}")
+        print("")
     except (BadDataError, UnimplError) as e:
         print(f"An error occurred during translation: {e}")
         sys.exit(1)
 
-    for insn in res.instructions:
-        print("-" * 80)
-        print("%08x/%d: %s %s" % (insn.address.offset, insn.length, insn.asm_mnem, insn.asm_body))
-        print("-" * 80)
-        for op in insn.ops:
-            print("%3d: %s" % (op.seq.uniq, str(op) if args.raw else PcodePrettyPrinter.fmt_op(op)))
-        print("")
-
 
 if __name__ == "__main__":
     main()
diff --git a/pypcode/__version__.py b/pypcode/__version__.py
new file mode 100644
index 00000000..9bc34971
--- /dev/null
+++ b/pypcode/__version__.py
@@ -0,0 +1,1 @@
+__version__ = "1.1.3.dev0"
diff --git a/pypcode/_csleigh.pyi b/pypcode/_csleigh.pyi
deleted file mode 100644
index 44e85c32..00000000
--- a/pypcode/_csleigh.pyi
+++ /dev/null
@@ -1,3 +0,0 @@
-import cffi
-
-ffi: cffi.FFI
diff --git a/pypcode/_csleigh/lib.pyi b/pypcode/_csleigh/lib.pyi
deleted file mode 100644
index ec591a1b..00000000
--- a/pypcode/_csleigh/lib.pyi
+++ /dev/null
@@ -1,153 +0,0 @@
-from typing import Any, Optional, Sequence, TypeAlias
-
-import _cffi_backend
-
-CData: TypeAlias = _cffi_backend._CDataBase
-charp: TypeAlias = CData
-
-csleigh_CPUI_BOOL_AND: int
-csleigh_CPUI_BOOL_NEGATE: int
-csleigh_CPUI_BOOL_OR: int
-csleigh_CPUI_BOOL_XOR: int
-csleigh_CPUI_BRANCH: int
-csleigh_CPUI_BRANCHIND: int
-csleigh_CPUI_CALL: int
-csleigh_CPUI_CALLIND: int
-csleigh_CPUI_CALLOTHER: int
-csleigh_CPUI_CAST: int
-csleigh_CPUI_CBRANCH: int
-csleigh_CPUI_COPY: int
-csleigh_CPUI_CPOOLREF: int
-csleigh_CPUI_EXTRACT: int
-csleigh_CPUI_FLOAT_ABS: int
-csleigh_CPUI_FLOAT_ADD: int
-csleigh_CPUI_FLOAT_CEIL: int
-csleigh_CPUI_FLOAT_DIV: int
-csleigh_CPUI_FLOAT_EQUAL: int
-csleigh_CPUI_FLOAT_FLOAT2FLOAT: int
-csleigh_CPUI_FLOAT_FLOOR: int
-csleigh_CPUI_FLOAT_INT2FLOAT: int
-csleigh_CPUI_FLOAT_LESS: int
-csleigh_CPUI_FLOAT_LESSEQUAL: int
-csleigh_CPUI_FLOAT_MULT: int
-csleigh_CPUI_FLOAT_NAN: int
-csleigh_CPUI_FLOAT_NEG: int
-csleigh_CPUI_FLOAT_NOTEQUAL: int
-csleigh_CPUI_FLOAT_ROUND: int
-csleigh_CPUI_FLOAT_SQRT: int
-csleigh_CPUI_FLOAT_SUB: int
-csleigh_CPUI_FLOAT_TRUNC: int
-csleigh_CPUI_INDIRECT: int
-csleigh_CPUI_INSERT: int
-csleigh_CPUI_INT_2COMP: int
-csleigh_CPUI_INT_ADD: int
-csleigh_CPUI_INT_AND: int
-csleigh_CPUI_INT_CARRY: int
-csleigh_CPUI_INT_DIV: int
-csleigh_CPUI_INT_EQUAL: int
-csleigh_CPUI_INT_LEFT: int
-csleigh_CPUI_INT_LESS: int
-csleigh_CPUI_INT_LESSEQUAL: int
-csleigh_CPUI_INT_MULT: int
-csleigh_CPUI_INT_NEGATE: int
-csleigh_CPUI_INT_NOTEQUAL: int
-csleigh_CPUI_INT_OR: int
-csleigh_CPUI_INT_REM: int
-csleigh_CPUI_INT_RIGHT: int
-csleigh_CPUI_INT_SBORROW: int
-csleigh_CPUI_INT_SCARRY: int
-csleigh_CPUI_INT_SDIV: int
-csleigh_CPUI_INT_SEXT: int
-csleigh_CPUI_INT_SLESS: int
-csleigh_CPUI_INT_SLESSEQUAL: int
-csleigh_CPUI_INT_SREM: int
-csleigh_CPUI_INT_SRIGHT: int
-csleigh_CPUI_INT_SUB: int
-csleigh_CPUI_INT_XOR: int
-csleigh_CPUI_INT_ZEXT: int
-csleigh_CPUI_LOAD: int
-csleigh_CPUI_MULTIEQUAL: int
-csleigh_CPUI_NEW: int
-csleigh_CPUI_PIECE: int
-csleigh_CPUI_POPCOUNT: int
-csleigh_CPUI_PTRADD: int
-csleigh_CPUI_PTRSUB: int
-csleigh_CPUI_RETURN: int
-csleigh_CPUI_SEGMENTOP: int
-csleigh_CPUI_STORE: int
-csleigh_CPUI_SUBPIECE: int
-csleigh_ERROR_TYPE_BADDATA: int
-csleigh_ERROR_TYPE_GENERIC: int
-csleigh_ERROR_TYPE_NOERROR: int
-csleigh_ERROR_TYPE_UNIMPL: int
-
-def csleigh_AddrSpace_getName(*args, **kwargs) -> Any: ...
-def csleigh_Addr_getSpaceFromConst(*args, **kwargs) -> Any: ...
-def csleigh_Addr_isConstant(*args, **kwargs) -> Any: ...
-def csleigh_Sleigh_getAllRegisters(*args, **kwargs) -> Any: ...
-def csleigh_Sleigh_getRegisterName(*args, **kwargs) -> Any: ...
-def csleigh_createContext(*args, **kwargs) -> Any: ...
-def csleigh_destroyContext(*args, **kwargs) -> Any: ...
-def csleigh_freeResult(*args, **kwargs) -> Any: ...
-def csleigh_setVariableDefault(*args, **kwargs) -> Any: ...
-def csleigh_translate(*args, **kwargs) -> Any: ...
-
-csleigh_OpCode: TypeAlias = int
-csleigh_ErrorType: TypeAlias = int
-
-class csleigh_Context:
-    pass
-
-class csleigh_AddrSpace:
-    pass
-
-class csleigh_Address:
-    offset: int
-    space: csleigh_AddrSpace
-
-class csleigh_Varnode:
-    space: csleigh_AddrSpace
-    offset: int
-    size: int
-
-class csleigh_SeqNum:
-    pc: csleigh_Address
-    uniq: int
-    order: int
-
-class csleigh_PcodeOp:
-    seq: csleigh_SeqNum
-    opcode: csleigh_OpCode
-    output: Optional[csleigh_Varnode]
-    inputs: Sequence[csleigh_Varnode]
-    inputs_count: int
-
-class csleigh_UnimplError:
-    address: csleigh_Address
-    instruction_length: int
-
-class csleigh_BadDataError:
-    address: csleigh_Address
-
-class csleigh_Error:
-    type: csleigh_ErrorType
-    explain: charp
-    unimpl: csleigh_UnimplError
-    baddata: csleigh_BadDataError
-
-class csleigh_Translation:
-    address: csleigh_Address
-    length: int
-    asm_mnem: charp
-    asm_body: charp
-    ops: Sequence[csleigh_PcodeOp]
-    ops_count: int
-
-class csleigh_TranslationResult:
-    error: csleigh_Error
-    instructions: Sequence[csleigh_Translation]
-    instructions_count: int
-
-class csleigh_RegisterDefinition:
-    name: charp
-    varnode: csleigh_Varnode
diff --git a/pypcode/native/.gitignore b/pypcode/native/.gitignore
deleted file mode 100644
index e0bd3776..00000000
--- a/pypcode/native/.gitignore
+++ /dev/null
@@ -1,7 +0,0 @@
-*.java
-Module.manifest
-build.gradle
-certification.manifest
-bin/
-lib/
-csleigh.i
diff --git a/pypcode/native/CMakeLists.txt b/pypcode/native/CMakeLists.txt
deleted file mode 100644
index 45137489..00000000
--- a/pypcode/native/CMakeLists.txt
+++ /dev/null
@@ -1,78 +0,0 @@
-cmake_minimum_required(VERSION 3.9)
-project(csleigh)
-# set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0 -ggdb")
-
-SEPARATE_ARGUMENTS(NEW_CMAKE_C_FLAGS NATIVE_COMMAND ${CMAKE_C_FLAGS})
-
-# First for the generic no-config case (e.g. with mingw)
-set( CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin )
-set( CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
-set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib )
-# Second, for multi-config builds (e.g. msvc)
-foreach( OUTPUTCONFIG ${CMAKE_CONFIGURATION_TYPES} )
-    string( TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG )
-    set( CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/bin )
-    set( CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/lib )
-    set( CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} ${CMAKE_BINARY_DIR}/lib )
-endforeach( OUTPUTCONFIG CMAKE_CONFIGURATION_TYPES )
-
-if (MSVC)
-    add_compile_options(/O3 /std:c++14)
-else()
-    add_compile_options(-O3 -Wall -Wno-sign-compare -D__TERMINAL__ -std=c++14)
-endif()
-
-set(SLEIGH_COMMON
-	sleigh/address.cc
-	sleigh/context.cc
-	sleigh/float.cc
-	sleigh/globalcontext.cc
-	sleigh/marshal.cc
-	sleigh/opcodes.cc
-	sleigh/pcodecompile.cc
-	sleigh/pcodeparse.cc
-	sleigh/pcoderaw.cc
-	sleigh/semantics.cc
-	sleigh/sleigh.cc
-	sleigh/sleighbase.cc
-	sleigh/slghpatexpress.cc
-	sleigh/slghpattern.cc
-	sleigh/slghsymbol.cc
-	sleigh/space.cc
-	sleigh/translate.cc
-	sleigh/xml.cc
-	)
-
-add_executable(sleigh
-	sleigh/filemanage.cc
-	sleigh/slgh_compile.cc
-	sleigh/slghparse.cc
-	sleigh/slghscan.cc
-	${SLEIGH_COMMON}
-)
-
-add_library(csleigh
-	csleigh.cc
-	${SLEIGH_COMMON}
-)
-
-set_target_properties(csleigh PROPERTIES POSITION_INDEPENDENT_CODE ON)
-
-if (MSVC)
-	if (CMAKE_SIZEOF_VOID_P EQUAL 8)
-		set(PREPROCESSOR_ONLY_FLAGS /DCFFI_CDEF=1 /EP /D_WINDOWS=1 /D_WIN64)
-	else ()
-		set(PREPROCESSOR_ONLY_FLAGS /DCFFI_CDEF=1 /EP /D_WINDOWS=1 /D_WIN32)
-	endif ()
-else()
-    set(PREPROCESSOR_ONLY_FLAGS -DCFFI_CDEF=1 -E -P)
-endif()
-
-add_custom_target(
-	csleigh.i ALL
-	COMMAND ${CMAKE_C_COMPILER} ${NEW_CMAKE_C_FLAGS} ${PREPROCESSOR_ONLY_FLAGS} ${CMAKE_SOURCE_DIR}/csleigh.h > ${CMAKE_BINARY_DIR}/csleigh.i
-	BYPRODUCTS ${CMAKE_BINARY_DIR}/csleigh.i
-	VERBATIM
-)
-
-install(TARGETS sleigh DESTINATION bin)
diff --git a/pypcode/native/Makefile b/pypcode/native/Makefile
deleted file mode 100644
index 15f34a6f..00000000
--- a/pypcode/native/Makefile
+++ /dev/null
@@ -1,26 +0,0 @@
-PROCESSORS_DIR:=../processors
-SPECFILES:=$(shell find $(PROCESSORS_DIR) -name '*.slaspec')
-SLAFILES:=$(SPECFILES:.slaspec=.sla)
-SLEIGH_BIN:=./bin/sleigh
-
-V = 0
-VE_0 := @
-VE_1 :=
-VE = $(VE_$(V))
-
-ifeq ($(V),1)
-QUIET=
-else
-QUIET=>/dev/null
-endif
-
-all: $(SLAFILES)
-
-%.sla: %.slaspec $(SLEIGH_BIN)
-	@echo "sleigh  $@"
-	$(VE) $(SLEIGH_BIN) $< $@ $(QUIET)
-
-.PHONY: clean
-clean:
-	$(VE) rm -f $(SLAFILES) $(QUIET)
-
diff --git a/pypcode/native/csleigh.cc b/pypcode/native/csleigh.cc
deleted file mode 100644
index 9c95823d..00000000
--- a/pypcode/native/csleigh.cc
+++ /dev/null
@@ -1,442 +0,0 @@
-//
-// Minimal SLEIGH library C API, for disassembly and translation to P-code.
-//
-#include <stdio.h>
-#include <assert.h>
-#include <stdbool.h>
-#include <unordered_set>
-#include <memory>
-#include "sleigh/loadimage.hh"
-#include "sleigh/sleigh.hh"
-#include "csleigh.h"
-
-// #define DEBUG
-#ifdef DEBUG
-#define LOG(fmt, ...) fprintf(stderr, "csleigh: " fmt "\n", ## __VA_ARGS__);
-#else
-#define LOG(fmt, ...) do {} while (0)
-#endif
-
-// https://gist.github.com/xymopen/352cbb55ddc2a767ed7c5999cfed4d31
-template< class T, class M >
-static inline constexpr ptrdiff_t offset_of( const M T::*member ) {
-    return reinterpret_cast< ptrdiff_t >( &( reinterpret_cast< T* >( 0 )->*member ) );
-}
-template< class T, class M >
-static inline constexpr T* owner_of( M *ptr, const M T::*member ) {
-    return reinterpret_cast< T* >( reinterpret_cast< intptr_t >( ptr ) - offset_of( member ) );
-}
-
-void convertVarnodeToCType(const VarnodeData &var, LPX(Varnode) &out)
-{
-    assert(var.space != NULL);
-    out.space = (LPX(AddrSpace)*)(var.space);
-    out.offset = var.offset;
-    out.size = var.size;
-}
-
-void convertAddressToCType(const Address &in, LPX(Address) &out)
-{
-    out.space = (LPX(AddrSpace)*)in.getSpace();
-    out.offset = in.getOffset();
-}
-
-class ContextPypcode : public ContextInternal
-{
-    bool                        m_finalized;
-    std::unordered_set<string>  m_variables;
-
-public:
-    ContextPypcode()
-    : ContextInternal()
-    {
-        m_finalized = false;
-    }
-
-    void finalize() { m_finalized = true; }
-
-    virtual void registerVariable(const string &nm,int4 sbit,int4 ebit) {
-        if (!m_finalized) {
-            ContextInternal::registerVariable(nm, sbit, ebit);
-            m_variables.insert(nm);
-        }
-    }
-
-    void resetAllVariables() {
-        for (const string &nm : m_variables) {
-            auto val = ContextDatabase::getDefaultValue(nm);
-            setVariableRegion(nm, Address(Address::m_minimal), Address(), val);
-        }
-    }
-};
-
-class SimpleLoadImage : public LoadImage
-{
-    uintb                m_baseaddr;
-    int4                 m_length;
-    const unsigned char *m_data;
-
-public:
-    SimpleLoadImage()
-    : LoadImage("nofile")
-    {
-        m_baseaddr = 0;
-        m_data = NULL;
-        m_length = 0;
-    }
-
-    void setData(uintb ad, const unsigned char *ptr,int4 sz)
-    {
-        m_baseaddr = ad;
-        m_data = ptr;
-        m_length = sz;
-    }
-
-    void loadFill(uint1 *ptr, int4 size, const Address &addr)
-    {
-        LOG("Filling %d bytes at %lx", size, addr.getOffset());
-        uintb start = addr.getOffset();
-        uintb max = m_baseaddr + m_length - 1;
-
-        //
-        // When decoding an instruction, SLEIGH will attempt to pull in several
-        // bytes at a time, starting at each instruction boundary.
-        //
-        // If the start address is outside of the defined range, bail out.
-        // Otherwise, if we have some data to provide but cannot sastisfy the
-        // entire request, fill the remainder of the buffer with zero.
-        //
-        if (start > max || start < m_baseaddr) {
-            throw std::out_of_range("Attempting to lift outside buffer range");
-        }
-
-        for(int4 i = 0; i < size; i++) {
-            uintb curoff = start + i;
-            if ((curoff < m_baseaddr) || (curoff>max)) {
-                ptr[i] = 0;
-                continue;
-            }
-            uintb diff = curoff - m_baseaddr;
-            ptr[i] = m_data[(int4)diff];
-        }
-    }
-
-    virtual string getArchType(void) const { return "myload"; }
-    virtual void adjustVma(long adjust) { }
-};
-
-
-class PcodeEmitCacher : public PcodeEmit
-{
-public:
-    vector<unique_ptr<LPX(Varnode)>> m_vars;
-    vector<LPX(PcodeOp)>             m_ops;
-    uintm                            m_uniq;
-
-    PcodeEmitCacher()
-    : m_uniq(0)
-    {
-    }
-
-    void dump(const Address &addr, OpCode opc, VarnodeData *outvar,
-              VarnodeData *vars, int4 isize)
-    {
-        assert(isize > 0);
-
-        m_ops.emplace_back();
-        LPX(PcodeOp) &op = m_ops.back();
-        convertAddressToCType(addr, op.seq.pc);
-        op.seq.uniq = m_uniq++;
-        op.opcode = (LPX(OpCode))opc;
-
-        int num_varnodes = isize;
-        if (outvar) num_varnodes += 1;
-        LPX(Varnode) *vna = new LPX(Varnode)[num_varnodes];
-
-        int vni = 0;
-        if (outvar) {
-            convertVarnodeToCType(*outvar, vna[vni]);
-            op.output = &vna[vni++];
-        } else {
-            op.output = NULL;
-        }
-
-        op.inputs_count = isize;
-        op.inputs = &vna[vni];
-        for (int4 i = 0; i < isize; i++) {
-            convertVarnodeToCType(vars[i], vna[vni++]);
-        }
-
-        m_vars.emplace_back(vna);
-    }
-};
-
-
-class AssemblyEmitCacher : public AssemblyEmit
-{
-public:
-    Address  m_addr;
-    string   m_mnem;
-    string   m_body;
-
-    void dump(const Address &addr, const string &mnem, const string &body)
-    {
-        m_addr = addr;
-        m_mnem = mnem;
-        m_body = body;
-    };
-};
-
-
-class TranslationResult
-{
-public:
-    LPX(TranslationResult)     m_res;
-    string                     m_error_msg;
-    vector<LPX(Translation)>   m_insns;
-    vector<AssemblyEmitCacher> m_asms;
-    vector<PcodeEmitCacher>    m_pcodes;
-
-    TranslationResult()
-    {
-        LOG("Created result %p", this);
-        m_res.error.type = LPX(ERROR_TYPE_NOERROR);
-    }
-
-    ~TranslationResult() {
-        LOG("Destroying result %p", this);
-    }
-
-    void updateWithException(UnimplError &e, Address &addr)
-    {
-        m_res.error.type = LPX(ERROR_TYPE_UNIMPL);
-        m_error_msg = e.explain;
-        m_res.error.explain = m_error_msg.c_str();
-        convertAddressToCType(addr, m_res.error.unimpl.address);
-        m_res.error.unimpl.instruction_length = e.instruction_length;
-    }
-
-    void updateWithException(BadDataError &e, Address &addr)
-    {
-        m_res.error.type = LPX(ERROR_TYPE_BADDATA);
-        m_error_msg = e.explain;
-        m_res.error.explain = m_error_msg.c_str();
-        convertAddressToCType(addr, m_res.error.baddata.address);
-    }
-};
-
-
-class TranslationContext
-{
-public:
-    SimpleLoadImage     m_loader;
-    ContextPypcode      m_context_db;
-    DocumentStorage     m_document_storage;
-    Document           *m_document;
-    Element            *m_tags;
-    unique_ptr<Sleigh>  m_sleigh;
-    string              m_register_name_cache;
-    vector<string>      m_register_names;
-    vector<LPX(RegisterDefinition)> m_registers;
-
-    TranslationContext()
-    {
-        LOG("%p Context created", this);
-    }
-
-    ~TranslationContext()
-    {
-        LOG("%p Freeing context", this);
-    }
-
-    bool loadSlaFile(const char *path)
-    {
-        // FIXME: Globals...
-        AttributeId::initialize();
-        ElementId::initialize();
-
-        LOG("%p Loading slafile...", this);
-        try {
-            m_document = m_document_storage.openDocument(path);
-            m_tags = m_document->getRoot();
-            m_document_storage.registerTag(m_tags);
-        } catch (DecoderError &e) {
-            return false;
-        }
-
-        LOG("Setting up translator");
-        m_sleigh.reset(new Sleigh(&m_loader, &m_context_db));
-        m_sleigh->initialize(m_document_storage);
-        m_context_db.finalize();
-
-        // Vectorize the register map
-        map<VarnodeData,string> regmap;
-        m_sleigh->getAllRegisters(regmap);
-        m_registers.reserve(regmap.size());
-        for (auto const & i : regmap) {
-            m_registers.emplace_back();
-            convertVarnodeToCType(i.first, m_registers.back().varnode);
-            m_register_names.push_back(i.second);
-        }
-        for (unsigned int i = 0; i < m_registers.size(); i++) {
-            m_registers[i].name = m_register_names[i].c_str();
-        }
-
-        return true;
-    }
-
-    LPX(TranslationResult) *translate(const unsigned char *bytes,
-                                      unsigned int num_bytes,
-                                      uintb address,
-                                      unsigned int max_instructions,
-                                      bool bb_terminating)
-    {
-        LOG("%p Translating bytes=%p, num_bytes=%d, address=%lx, "
-            "max_instructions=%d",
-            this, bytes, num_bytes, address, max_instructions);
-
-        // Reset state
-        m_sleigh->reset(&m_loader, &m_context_db);
-        m_sleigh->initialize(m_document_storage);
-        m_context_db.resetAllVariables();
-        m_loader.setData(address, bytes, num_bytes);
-
-        TranslationResult *res = new TranslationResult();
-        int4 offset = 0;
-        bool end_bb = false;
-
-        // Translate instructions
-        while (!end_bb && (offset < num_bytes) && (!max_instructions ||
-               (res->m_insns.size() < max_instructions))) {
-            Address addr(m_sleigh->getDefaultCodeSpace(), address + offset);
-            try {
-                int4 ilen = m_sleigh->instructionLength(addr);
-
-                res->m_asms.emplace_back();
-                m_sleigh->printAssembly(res->m_asms.back(), addr);
-
-                res->m_pcodes.emplace_back();
-                m_sleigh->oneInstruction(res->m_pcodes.back(), addr);
-
-                res->m_insns.emplace_back();
-                LPX(Translation) &insn = res->m_insns.back();
-                convertAddressToCType(addr, insn.address);
-                insn.length = ilen;
-
-                offset += ilen;
-
-                if (bb_terminating) {
-                    for (auto op : res->m_pcodes.back().m_ops) {
-                        if (op.opcode == OP(BRANCH) ||
-                            op.opcode == OP(CBRANCH) ||
-                            op.opcode == OP(BRANCHIND) ||
-                            op.opcode == OP(RETURN) ||
-                            op.opcode == OP(CALL) ||
-                            op.opcode == OP(CALLIND)) {
-                            end_bb = true;
-                            break;
-                        }
-                    }
-                }
-            } catch (UnimplError &e) {
-                res->updateWithException(e, addr);
-                break;
-            } catch (BadDataError &e) {
-                res->updateWithException(e, addr);
-                break;
-            }
-        }
-
-        // Serialize
-        for (int i = 0; i < res->m_insns.size(); i++) {
-            res->m_insns[i].asm_mnem = res->m_asms[i].m_mnem.c_str();
-            res->m_insns[i].asm_body = res->m_asms[i].m_body.c_str();
-            res->m_insns[i].ops = &res->m_pcodes[i].m_ops[0];
-            res->m_insns[i].ops_count = res->m_pcodes[i].m_ops.size();
-        }
-
-        res->m_res.instructions_count = res->m_insns.size();
-        if (res->m_res.instructions_count) {
-            res->m_res.instructions = &res->m_insns[0];
-        }
-
-        return &res->m_res;
-    }
-
-    const char *Sleigh_getRegisterName(AddrSpace* as, uintb off, int4 size)
-    {
-        m_register_name_cache = m_sleigh->getRegisterName(as, off, size);
-        return m_register_name_cache.c_str();
-    }
-};
-
-
-//------------------------------------------------------------------------------
-// C API
-//------------------------------------------------------------------------------
-
-LPX(Context) LPX(createContext)(const char *slafile)
-{
-    TranslationContext *ctx = new TranslationContext();
-    if (!ctx->loadSlaFile(slafile)) {
-        delete ctx;
-        ctx = NULL;
-    }
-    return (LPX(Context))(ctx);
-}
-
-void LPX(destroyContext)(LPX(Context) c)
-{
-    delete (TranslationContext *)c;
-}
-
-LPX(TranslationResult) *LPX(translate)(LPX(Context) c,
-                                       const unsigned char *bytes,
-                                       unsigned int num_bytes,
-                                       uintb address,
-                                       unsigned int max_instructions,
-                                       bool bb_terminating)
-{
-    return ((TranslationContext *)c)->translate(bytes, num_bytes, address,
-                                                max_instructions,
-                                                bb_terminating);
-}
-
-void LPX(freeResult)(LPX(TranslationResult) *r)
-{
-    if (r) delete owner_of(r, &TranslationResult::m_res);
-}
-
-void LPX(setVariableDefault)(LPX(Context) c, const char *name, uintm val)
-{
-    ((TranslationContext *)c)->m_context_db.setVariableDefault(name, val);
-}
-
-int LPX(Addr_isConstant)(LPX(Address) *a)
-{
-    return Address((AddrSpace *)(a->space), a->offset).isConstant();
-}
-
-LPX(AddrSpace) LPX(Addr_getSpaceFromConst)(LPX(Address) *a)
-{
-    return (LPX(AddrSpace))(uintp)a->offset;
-}
-
-const char *LPX(AddrSpace_getName)(LPX(AddrSpace) as)
-{
-    return ((AddrSpace *)as)->getName().c_str();
-}
-
-const char *LPX(Sleigh_getRegisterName)(LPX(Context) c, LPX(AddrSpace) as,
-                                        uintb off, int4 size)
-{
-    return ((TranslationContext *)c)->Sleigh_getRegisterName(
-        (AddrSpace *)as, off, size);
-}
-
-LPX(RegisterDefinition) *LPX(Sleigh_getAllRegisters)(LPX(Context) c, unsigned int *count)
-{
-    TranslationContext *ctx = (TranslationContext *)c;
-    *count = ctx->m_registers.size();
-    return &ctx->m_registers[0];
-}
diff --git a/pypcode/native/csleigh.h b/pypcode/native/csleigh.h
deleted file mode 100644
index c23a0542..00000000
--- a/pypcode/native/csleigh.h
+++ /dev/null
@@ -1,233 +0,0 @@
-/*
- * Minimal SLEIGH library C API, for disassembly and translation to P-code.
- */
-#ifndef CSLEIGH_H
-#define CSLEIGH_H
-
-#include <stdbool.h>
-
-#include "sleigh/types.h"
-
-/* Names in this header are prefixed to avoid collisions with SLEIGH. */
-#define LPX(name) csleigh_ ## name
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define OPS_X \
-	INV_OP( 0, INVALID,           "Invalid") \
-	DEC_OP( 1, COPY,              "Copy one operand to another") \
-	DEC_OP( 2, LOAD,              "Load from a pointer into a specified address space") \
-	DEC_OP( 3, STORE,             "Store at a pointer into a specified address space") \
-	DEC_OP( 4, BRANCH,            "Always branch") \
-	DEC_OP( 5, CBRANCH,           "Conditional branch") \
-	DEC_OP( 6, BRANCHIND,         "Indirect branch (jumptable)") \
-	DEC_OP( 7, CALL,              "Call to an absolute address") \
-	DEC_OP( 8, CALLIND,           "Call through an indirect address") \
-	DEC_OP( 9, CALLOTHER,         "User-defined operation") \
-	DEC_OP(10, RETURN,            "Return from subroutine") \
-	DEC_OP(11, INT_EQUAL,         "Integer comparison, equality (==)") \
-	DEC_OP(12, INT_NOTEQUAL,      "Integer comparison, in-equality (!=)") \
-	DEC_OP(13, INT_SLESS,         "Integer comparison, signed less-than (<)") \
-	DEC_OP(14, INT_SLESSEQUAL,    "Integer comparison, signed less-than-or-equal (<=)") \
-	DEC_OP(15, INT_LESS,          "Integer comparison, unsigned less-than (<)") \
-	DEC_OP(16, INT_LESSEQUAL,     "Integer comparison, unsigned less-than-or-equal (<=)") \
-	DEC_OP(17, INT_ZEXT,          "Zero extension") \
-	DEC_OP(18, INT_SEXT,          "Sign extension") \
-	DEC_OP(19, INT_ADD,           "Addition, signed or unsigned (+)") \
-	DEC_OP(20, INT_SUB,           "Subtraction, signed or unsigned (-)") \
-	DEC_OP(21, INT_CARRY,         "Test for unsigned carry") \
-	DEC_OP(22, INT_SCARRY,        "Test for signed carry") \
-	DEC_OP(23, INT_SBORROW,       "Test for signed borrow") \
-	DEC_OP(24, INT_2COMP,         "Twos complement") \
-	DEC_OP(25, INT_NEGATE,        "Logical/bitwise negation (~)") \
-	DEC_OP(26, INT_XOR,           "Logical/bitwise exclusive-or (^)") \
-	DEC_OP(27, INT_AND,           "Logical/bitwise and (&)") \
-	DEC_OP(28, INT_OR,            "Logical/bitwise or (|)") \
-	DEC_OP(29, INT_LEFT,          "Left shift (<<)") \
-	DEC_OP(30, INT_RIGHT,         "Right shift, logical (>>)") \
-	DEC_OP(31, INT_SRIGHT,        "Right shift, arithmetic (>>)") \
-	DEC_OP(32, INT_MULT,          "Integer multiplication, signed and unsigned (*)") \
-	DEC_OP(33, INT_DIV,           "Integer division, unsigned (/)") \
-	DEC_OP(34, INT_SDIV,          "Integer division, signed (/)") \
-	DEC_OP(35, INT_REM,           "Remainder/modulo, unsigned (%)") \
-	DEC_OP(36, INT_SREM,          "Remainder/modulo, signed (%)") \
-	DEC_OP(37, BOOL_NEGATE,       "Boolean negate (!)") \
-	DEC_OP(38, BOOL_XOR,          "Boolean exclusive-or (^^)") \
-	DEC_OP(39, BOOL_AND,          "Boolean and (&&)") \
-	DEC_OP(40, BOOL_OR,           "Boolean or (||)") \
-	DEC_OP(41, FLOAT_EQUAL,       "Floating-point comparison, equality (==)") \
-	DEC_OP(42, FLOAT_NOTEQUAL,    "Floating-point comparison, in-equality (!=)") \
-	DEC_OP(43, FLOAT_LESS,        "Floating-point comparison, less-than (<)") \
-	DEC_OP(44, FLOAT_LESSEQUAL,   "Floating-point comparison, less-than-or-equal (<=)") \
-	INV_OP(45, INVALID,           "Invalid") \
-	DEC_OP(46, FLOAT_NAN,         "Not-a-number test (NaN)") \
-	DEC_OP(47, FLOAT_ADD,         "Floating-point addition (+)") \
-	DEC_OP(48, FLOAT_DIV,         "Floating-point division (/)") \
-	DEC_OP(49, FLOAT_MULT,        "Floating-point multiplication (*)") \
-	DEC_OP(50, FLOAT_SUB,         "Floating-point subtraction (-)") \
-	DEC_OP(51, FLOAT_NEG,         "Floating-point negation (-)") \
-	DEC_OP(52, FLOAT_ABS,         "Floating-point absolute value (abs)") \
-	DEC_OP(53, FLOAT_SQRT,        "Floating-point square root (sqrt)") \
-	DEC_OP(54, FLOAT_INT2FLOAT,   "Convert an integer to a floating-point") \
-	DEC_OP(55, FLOAT_FLOAT2FLOAT, "Convert between different floating-point sizes") \
-	DEC_OP(56, FLOAT_TRUNC,       "Round towards zero") \
-	DEC_OP(57, FLOAT_CEIL,        "Round towards +infinity") \
-	DEC_OP(58, FLOAT_FLOOR,       "Round towards -infinity") \
-	DEC_OP(59, FLOAT_ROUND,       "Round towards nearest") \
-	DEC_OP(60, MULTIEQUAL,        "Phi-node operator") \
-	DEC_OP(61, INDIRECT,          "Copy with an indirect effect") \
-	DEC_OP(62, PIECE,             "Concatenate") \
-	DEC_OP(63, SUBPIECE,          "Truncate") \
-	DEC_OP(64, CAST,              "Cast from one data-type to another") \
-	DEC_OP(65, PTRADD,            "Index into an array ([])") \
-	DEC_OP(66, PTRSUB,            "Drill down to a sub-field  (->)") \
-	DEC_OP(67, SEGMENTOP,         "Look-up a segmented address") \
-	DEC_OP(68, CPOOLREF,          "Recover a value from the constant pool") \
-	DEC_OP(69, NEW,               "Allocate a new object (new)") \
-	DEC_OP(70, INSERT,            "Insert a bit-range") \
-	DEC_OP(71, EXTRACT,           "Extract a bit-range") \
-	DEC_OP(72, POPCOUNT,          "Count the 1-bits") \
-
-#define OP(opname) LPX(CPUI_ ## opname)
-
-typedef enum {
-#define DEC_OP(oid, oname, odesc) OP(oname) = oid,
-#define INV_OP(oid, oname, odesc)
-OPS_X
-#undef DEC_OP
-#undef INV_OP
-} LPX(OpCode);
-
-/* Opaque types */
-typedef void *LPX(Context);
-typedef void *LPX(AddrSpace);
-
-typedef struct {
-	LPX(AddrSpace) space;
-	uintb          offset;
-} LPX(Address);
-
-typedef struct {
-	LPX(AddrSpace) space;
-	uintb          offset;
-	uint4          size;
-} LPX(Varnode);
-
-typedef struct {
-	LPX(Address) pc;
-	uintm        uniq;
-	uintm        order;
-} LPX(SeqNum);
-
-typedef struct {
-	LPX(SeqNum)   seq;
-	LPX(OpCode)   opcode;
-	LPX(Varnode) *output; /* NULL if no output */
-	LPX(Varnode) *inputs;
-	unsigned int  inputs_count;
-} LPX(PcodeOp);
-
-typedef enum {
-	LPX(ERROR_TYPE_NOERROR) = 0,
-	LPX(ERROR_TYPE_GENERIC) = 1,
-	LPX(ERROR_TYPE_UNIMPL)  = 2,
-	LPX(ERROR_TYPE_BADDATA) = 3,
-} LPX(ErrorType);
-
-typedef struct {
-	LPX(Address) address;
-	int4 instruction_length;
-} LPX(UnimplError);
-
-typedef struct {
-	LPX(Address) address;
-} LPX(BadDataError);
-
-typedef struct {
-	LPX(ErrorType)  type;
-	const char     *explain;
-	union {
-		LPX(UnimplError)  unimpl;
-		LPX(BadDataError) baddata;
-	};
-} LPX(Error);
-
-typedef struct {
-	LPX(Address)  address;
-	int4          length;
-	const char   *asm_mnem;
-	const char   *asm_body;
-	LPX(PcodeOp) *ops;
-	unsigned int  ops_count;
-} LPX(Translation);
-
-typedef struct {
-	LPX(Error)        error;
-	LPX(Translation) *instructions;
-	unsigned int      instructions_count;
-} LPX(TranslationResult);
-
-typedef struct {
-	const char   *name;
-	LPX(Varnode)  varnode;
-} LPX(RegisterDefinition);
-
-/*
- * Create a translation context
- */
-LPX(Context) LPX(createContext)(const char *slafile);
-
-/*
- * Free a translation context
- */
-void LPX(destroyContext)(LPX(Context) c);
-
-/*
- * Translate machine code to P-code
- */
-LPX(TranslationResult) *LPX(translate)(LPX(Context) c, const unsigned char *bytes, unsigned int num_bytes, uintb address, unsigned int max_instructions, bool bb_terminating);
-
-/*
- * Free a TranslationResult
- */
-void LPX(freeResult)(LPX(TranslationResult) *r);
-
-/*
- * ContextDatabase::setVariableDefault
- */
-void LPX(setVariableDefault)(LPX(Context) c, const char *name, uintm val);
-
-/*
- * Address::isConstant
- */
-int LPX(Addr_isConstant)(LPX(Address) *a);
-
-/*
- * Address::getSpaceFromConst
- */
-LPX(AddrSpace) LPX(Addr_getSpaceFromConst)(LPX(Address) *a);
-
-/*
- * AddrSpace::getName
- */
-const char *LPX(AddrSpace_getName)(LPX(AddrSpace) as);
-
-/*
- * Sleigh::getRegisterName
- */
-const char *LPX(Sleigh_getRegisterName)(LPX(Context) c, LPX(AddrSpace) as, uintb off, int4 size);
-
-/*
- * Sleigh::getAllRegisters
- *
- * Note: Returned pointer is managed by context.
- */
-LPX(RegisterDefinition) *LPX(Sleigh_getAllRegisters)(LPX(Context) c, unsigned int *count);
-
-#ifdef __cplusplus
-} /* extern "C" { */
-#endif
-
-#endif
diff --git a/pypcode/processors/.gitignore b/pypcode/processors/.gitignore
deleted file mode 100644
index 3558fa5c..00000000
--- a/pypcode/processors/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-*.sla
-*.manifest
-*.gradle
-*.java
diff --git a/pypcode/pypcode_native.cpp b/pypcode/pypcode_native.cpp
new file mode 100644
index 00000000..c95a7ac9
--- /dev/null
+++ b/pypcode/pypcode_native.cpp
@@ -0,0 +1,708 @@
+#include <cstdio>
+#include <optional>
+#include <string>
+#include <unordered_set>
+
+#include <nanobind/nanobind.h>
+#include <nanobind/stl/map.h>
+#include <nanobind/stl/optional.h>
+#include <nanobind/stl/string.h>
+#include <nanobind/stl/unique_ptr.h>
+#include <nanobind/stl/vector.h>
+
+#include "sleigh/loadimage.hh"
+#include "sleigh/opcodes.hh"
+#include "sleigh/sleigh.hh"
+#include "sleigh/space.hh"
+#include "sleigh/translate.hh"
+#include "sleigh/xml.hh"
+
+namespace nb = nanobind;
+using namespace nb::literals;
+
+const char *
+#include "__version__.py"
+    ;
+
+// #define DEBUG 1
+
+#ifndef DEBUG
+#define DEBUG 0
+#endif
+
+#if DEBUG
+#define LOG(fmt, ...) fprintf(stderr, "pypcode_native: " fmt "\n", ##__VA_ARGS__);
+#else
+#define LOG(fmt, ...) \
+    do {              \
+    } while (0)
+#endif
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+
+struct PcodeOp {
+    OpCode m_opcode;
+    std::optional<VarnodeData> m_output;
+    std::vector<VarnodeData> m_inputs;
+};
+
+class ContextPypcode : public ContextInternal {
+    bool m_finalized;
+    std::unordered_set<string> m_variables;
+
+public:
+    ContextPypcode() : ContextInternal()
+    {
+        m_finalized = false;
+    }
+
+    void finalize()
+    {
+        m_finalized = true;
+    }
+
+    virtual void registerVariable(const string &nm, int4 sbit, int4 ebit)
+    {
+        if (!m_finalized) {
+            ContextInternal::registerVariable(nm, sbit, ebit);
+            m_variables.insert(nm);
+        }
+    }
+
+    void resetAllVariables()
+    {
+        for (const string &nm : m_variables) {
+            auto val = ContextDatabase::getDefaultValue(nm);
+            setVariableRegion(nm, Address(Address::m_minimal), Address(), val);
+        }
+    }
+};
+
+class SimpleLoadImage : public LoadImage {
+    uintb m_baseaddr;
+    int4 m_length;
+    const unsigned char *m_data;
+
+public:
+    SimpleLoadImage() : LoadImage("nofile")
+    {
+        m_baseaddr = 0;
+        m_data = NULL;
+        m_length = 0;
+    }
+
+    void setData(uintb ad, const unsigned char *ptr, int4 sz)
+    {
+        m_baseaddr = ad;
+        m_data = ptr;
+        m_length = sz;
+    }
+
+    void loadFill(uint1 *ptr, int4 size, const Address &addr)
+    {
+        LOG("Filling %d bytes at %lx", size, addr.getOffset());
+        uintb start = addr.getOffset();
+        uintb max = m_baseaddr + m_length - 1;
+
+        //
+        // When decoding an instruction, SLEIGH will attempt to pull in several
+        // bytes at a time, starting at each instruction boundary.
+        //
+        // If the start address is outside of the defined range, bail out.
+        // Otherwise, if we have some data to provide but cannot satisfy the
+        // entire request, fill the remainder of the buffer with zero.
+        //
+        if (start > max || start < m_baseaddr) {
+            throw std::out_of_range("Attempting to lift outside buffer range");
+        }
+
+        for (int4 i = 0; i < size; i++) {
+            uintb curoff = start + i;
+            if ((curoff < m_baseaddr) || (curoff > max)) {
+                ptr[i] = 0;
+                continue;
+            }
+            uintb diff = curoff - m_baseaddr;
+            ptr[i] = m_data[(int4)diff];
+        }
+    }
+
+    virtual string getArchType(void) const
+    {
+        return "myload";
+    }
+    virtual void adjustVma(long adjust)
+    {
+    }
+};
+
+class PcodeEmitCacher : public PcodeEmit {
+public:
+    std::vector<PcodeOp> m_ops;
+    bool m_bb_terminating_op_emitted;
+
+    PcodeEmitCacher() : m_bb_terminating_op_emitted(false)
+    {
+        m_ops.reserve(512);
+    }
+
+    // Encode P-code ops into csleigh structures and append them to the translation buffer
+    void dump(const Address &addr, OpCode opc, VarnodeData *outvar, VarnodeData *invars, int4 num_invars)
+    {
+        LOG("Emitting pcode op %d with %d-in,%d-out varnodes from %lx",
+            opc,
+            num_invars,
+            outvar ? 1 : 0,
+            addr.getOffset());
+        m_bb_terminating_op_emitted |= opc == CPUI_BRANCH || opc == CPUI_CBRANCH || opc == CPUI_BRANCHIND ||
+                                       opc == CPUI_RETURN || opc == CPUI_CALL || opc == CPUI_CALLIND;
+
+        m_ops.emplace_back();
+        PcodeOp &op = m_ops.back();
+
+        op.m_opcode = opc;
+        if (outvar) {
+            op.m_output.emplace(*outvar);
+        }
+        op.m_inputs.reserve(num_invars);
+        for (int i = 0; i < num_invars; i++) {
+            op.m_inputs.emplace_back(invars[i]);
+        }
+    }
+};
+
+struct DisassemblyInstruction {
+    Address m_addr;
+    uint64_t m_length;
+    std::string m_mnem;
+    std::string m_body;
+};
+
+class AssemblyEmitCacher : public AssemblyEmit {
+public:
+    DisassemblyInstruction &m_disas;
+
+    AssemblyEmitCacher(DisassemblyInstruction &disas) : m_disas(disas)
+    {
+    }
+
+    void dump(const Address &addr, const std::string &mnem, const std::string &body)
+    {
+        m_disas.m_addr = addr;
+        m_disas.m_mnem = mnem;
+        m_disas.m_body = body;
+    };
+};
+
+class Disassembly {
+public:
+    std::vector<DisassemblyInstruction> m_instructions;
+
+    Disassembly()
+    {
+        LOG("Disassembly %p created", this);
+    }
+
+    Disassembly(Disassembly &&o) noexcept : m_instructions(std::move(o.m_instructions))
+    {
+        LOG("Disassembly moved from %p to %p", &o, this);
+    }
+
+    ~Disassembly()
+    {
+        LOG("Disassembly %p released", this);
+    }
+};
+
+class Translation {
+public:
+    std::vector<PcodeOp> m_ops;
+
+    Translation()
+    {
+        LOG("Translation %p created", this);
+    }
+
+    Translation(Translation &&o) noexcept : m_ops(std::move(o.m_ops))
+    {
+        LOG("Translation moved from %p to %p", &o, this);
+    }
+
+    ~Translation()
+    {
+        LOG("Translation %p released", this);
+    }
+};
+
+enum TranslateFlags {
+    BB_TERMINATING = 1,
+};
+
+class Context {
+public:
+    SimpleLoadImage m_loader;
+    ContextPypcode m_context_db;
+    DocumentStorage m_document_storage;
+    Document *m_document;
+    Element *m_tags;
+    unique_ptr<Sleigh> m_sleigh;
+
+    Context(const std::string &path)
+    {
+        LOG("Context %p created", this);
+
+        // FIXME: Globals...
+        AttributeId::initialize();
+        ElementId::initialize();
+
+        LOG("%p Loading slafile...", this);
+        m_document = m_document_storage.openDocument(path);
+        m_tags = m_document->getRoot();
+        m_document_storage.registerTag(m_tags);
+
+        LOG("Setting up translator");
+        m_sleigh.reset(new Sleigh(&m_loader, &m_context_db));
+        m_sleigh->initialize(m_document_storage);
+        m_context_db.finalize();
+    }
+
+    ~Context()
+    {
+        LOG("Context %p released", this);
+    }
+
+    void reset(void)
+    {
+        m_sleigh.reset(new Sleigh(&m_loader, &m_context_db));
+        m_sleigh->initialize(m_document_storage);
+        m_context_db.finalize();
+    }
+
+    std::unique_ptr<Disassembly>
+    disassemble(const char *bytes, unsigned int num_bytes, uint64_t address, unsigned int max_instructions)
+    {
+        LOG("%p Disassembling bytes=%p, num_bytes=%d, address=%lx", this, bytes, num_bytes, address);
+        std::unique_ptr<Disassembly> disassembly(new Disassembly());
+        int num_instructions = 0;
+        uint32_t offset = 0;
+
+        m_sleigh->fastReset();
+        m_loader.setData(address, (const unsigned char *)bytes, num_bytes);
+        disassembly->m_instructions.reserve(10);
+
+        while ((offset < num_bytes) && (!max_instructions || (num_instructions < max_instructions))) {
+            Address addr(m_sleigh->getDefaultCodeSpace(), address + offset);
+
+            disassembly->m_instructions.emplace_back();
+            DisassemblyInstruction &ins = disassembly->m_instructions.back();
+
+            AssemblyEmitCacher asm_cache(ins);
+
+            // Disassemble the next instruction. If an error occurs after successful disassembly of at least one
+            // instruction, suppress the error and return the successful disassembly. If the caller attempts
+            // disassembly again at the position where the error occurred, then propagate the error.
+            try {
+                ins.m_length = m_sleigh->printAssembly(asm_cache, addr);
+            } catch (BadDataError &err) {
+                if (offset) {
+                    disassembly->m_instructions.resize(num_instructions);
+                    break;
+                }
+                throw err;
+            }
+
+            num_instructions += 1;
+            offset += ins.m_length;
+        }
+
+        return disassembly;
+    }
+
+    std::unique_ptr<Translation> translate(const char *bytes,
+                                           unsigned int num_bytes,
+                                           uint64_t base_address,
+                                           unsigned int max_instructions,
+                                           uint32_t flags)
+    {
+        LOG("%p Translating bytes=%p, num_bytes=%d, base_address=0x%lx, max_instructions=%d flags=0x%x",
+            this,
+            bytes,
+            num_bytes,
+            base_address,
+            max_instructions,
+            flags);
+        std::unique_ptr<Translation> translation(new Translation);
+        PcodeEmitCacher pcode_cache;
+        uint32_t offset = 0;
+
+        m_sleigh->fastReset();
+        m_loader.setData(base_address, (const unsigned char *)bytes, num_bytes);
+
+        int num_instructions = 0;
+        while ((offset < num_bytes) && (!max_instructions || (num_instructions < max_instructions))) {
+            Address addr(m_sleigh->getDefaultCodeSpace(), base_address + offset);
+            LOG("Lifting at 0x%lx+0x%x=0x%lx", base_address, offset, base_address + offset);
+
+            int imark_idx = pcode_cache.m_ops.size();
+            pcode_cache.m_ops.emplace_back();
+
+            // Translate the next instruction. If an error occurs after successful translation of at least one
+            // instruction, suppress the error and return the successful translation. If the caller attempts
+            // translation again at the position where the error occurred, then propagate the error.
+            uint32_t num_bytes_decoded = 0;
+            try {
+                num_bytes_decoded = m_sleigh->oneInstruction(pcode_cache, addr);
+            } catch (BadDataError &err) {
+                if (offset) {
+                    pcode_cache.m_ops.resize(imark_idx);
+                    break;
+                }
+                throw err;
+            } catch (UnimplError &err) {
+                if (offset) {
+                    pcode_cache.m_ops.resize(imark_idx);
+                    break;
+                }
+                throw err;
+            }
+
+            PcodeOp &imark_op = pcode_cache.m_ops[imark_idx];
+            imark_op.m_opcode = OpCode::CPUI_IMARK;
+
+            // Add varnode to imark op for every decoded instruction in this translation
+            for (int sum = 0; sum < num_bytes_decoded;) {
+                imark_op.m_inputs.emplace_back();
+                VarnodeData &imark_vn = imark_op.m_inputs.back();
+                imark_vn.space = addr.getSpace();
+                imark_vn.offset = addr.getOffset() + sum;
+                imark_vn.size = m_sleigh->instructionLength(addr);
+
+                sum += imark_vn.size;
+                num_instructions++;
+            }
+
+            if ((flags & TranslateFlags::BB_TERMINATING) && pcode_cache.m_bb_terminating_op_emitted) {
+                LOG("Reached end of block");
+                break;
+            }
+
+            offset += num_bytes_decoded;
+        }
+
+        translation->m_ops = std::move(pcode_cache.m_ops);
+        return translation;
+    }
+};
+
+NB_MODULE(pypcode_native, m)
+{
+    m.attr("__version__") = __version__;
+
+    m.doc() = "pypcode native extension providing machine code disassembly and translation to P-Code.";
+
+    nb::exception<BadDataError>(m, "BadDataError"); // "Exception for bad instruction data"
+    nb::exception<UnimplError>(m, "UnimplError"); // "Exception for encountering unimplemented P-Code"
+    nb::exception<DecoderError>(m, "DecoderError"); // "An exception thrown by the XML parser"
+
+    nb::class_<AddrSpace>(m, "AddrSpace", "A region where processor data is stored.")
+        .def_prop_ro(
+            "name", [](AddrSpace &as) { return as.getName(); }, "name(self) -> str\nThe name of this address space.");
+
+    nb::class_<Address>(m, "Address", "Low level machine byte address.")
+        .def_prop_ro(
+            "space",
+            [](Address &a) { return a.getSpace(); },
+            nb::rv_policy::reference_internal,
+            "space(self) -> AddrSpace\nThe address space.")
+        .def_prop_ro(
+            "offset", [](Address &a) { return a.getOffset(); }, "offset(self) -> int\nThe offset within the space.");
+
+    nb::class_<VarnodeData>(m, "Varnode", "Data defining a specific memory location.")
+        .def(nb::init<>())
+        .def_ro("space",
+                &VarnodeData::space,
+                nb::rv_policy::reference_internal,
+                "space(self) -> AddrSpace\nThe address space.")
+        .def_ro("offset", &VarnodeData::offset, "offset(self) -> int\nThe offset within the space.")
+        .def_ro("size", &VarnodeData::size, "size(self) -> int\nThe number of bytes in the location.")
+        .def(
+            "getRegisterName",
+            [](VarnodeData &a) { return a.space->getTrans()->getRegisterName(a.space, a.offset, a.size); },
+            "Return the register name if this Varnode references a register, otherwise return the empty string.")
+        .def(
+            "getSpaceFromConst",
+            [](VarnodeData &a) { return a.getSpaceFromConst(); },
+            nb::rv_policy::reference_internal,
+            "Recover encoded address space from constant value.");
+
+    nb::enum_<OpCode>(m, "OpCode", "OpCode defining a specific p-code operation.")
+        .value("IMARK", OpCode::CPUI_IMARK, "Instruction marker")
+        .value("COPY", OpCode::CPUI_COPY, "Copy one operand to another")
+        .value("LOAD", OpCode::CPUI_LOAD, "Load from a pointer into a specified address space")
+        .value("STORE", OpCode::CPUI_STORE, "Store at a pointer into a specified address space")
+        .value("BRANCH", OpCode::CPUI_BRANCH, "Always branch")
+        .value("CBRANCH", OpCode::CPUI_CBRANCH, "Conditional branch")
+        .value("BRANCHIND", OpCode::CPUI_BRANCHIND, "Indirect branch (jumptable)")
+        .value("CALL", OpCode::CPUI_CALL, "Call to an absolute address")
+        .value("CALLIND", OpCode::CPUI_CALLIND, "Call through an indirect address")
+        .value("CALLOTHER", OpCode::CPUI_CALLOTHER, "User-defined operation")
+        .value("RETURN", OpCode::CPUI_RETURN, "Return from subroutine")
+        .value("INT_EQUAL", OpCode::CPUI_INT_EQUAL, "Integer comparison, equality (==)")
+        .value("INT_NOTEQUAL", OpCode::CPUI_INT_NOTEQUAL, "Integer comparison, in-equality (!=)")
+        .value("INT_SLESS", OpCode::CPUI_INT_SLESS, "Integer comparison, signed less-than (<)")
+        .value("INT_SLESSEQUAL", OpCode::CPUI_INT_SLESSEQUAL, "Integer comparison, signed less-than-or-equal (<=)")
+        .value("INT_LESS", OpCode::CPUI_INT_LESS, "Integer comparison, unsigned less-than (<)")
+        .value("INT_LESSEQUAL", OpCode::CPUI_INT_LESSEQUAL, "Integer comparison, unsigned less-than-or-equal (<=)")
+        .value("INT_ZEXT", OpCode::CPUI_INT_ZEXT, "Zero extension")
+        .value("INT_SEXT", OpCode::CPUI_INT_SEXT, "Sign extension")
+        .value("INT_ADD", OpCode::CPUI_INT_ADD, "Addition, signed or unsigned (+)")
+        .value("INT_SUB", OpCode::CPUI_INT_SUB, "Subtraction, signed or unsigned (-)")
+        .value("INT_CARRY", OpCode::CPUI_INT_CARRY, "Test for unsigned carry")
+        .value("INT_SCARRY", OpCode::CPUI_INT_SCARRY, "Test for signed carry")
+        .value("INT_SBORROW", OpCode::CPUI_INT_SBORROW, "Test for signed borrow")
+        .value("INT_2COMP", OpCode::CPUI_INT_2COMP, "Twos complement")
+        .value("INT_NEGATE", OpCode::CPUI_INT_NEGATE, "Logical/bitwise negation (~)")
+        .value("INT_XOR", OpCode::CPUI_INT_XOR, "Logical/bitwise exclusive-or (^)")
+        .value("INT_AND", OpCode::CPUI_INT_AND, "Logical/bitwise and (&)")
+        .value("INT_OR", OpCode::CPUI_INT_OR, "Logical/bitwise or (|)")
+        .value("INT_LEFT", OpCode::CPUI_INT_LEFT, "Left shift (<<)")
+        .value("INT_RIGHT", OpCode::CPUI_INT_RIGHT, "Right shift, logical (>>)")
+        .value("INT_SRIGHT", OpCode::CPUI_INT_SRIGHT, "Right shift, arithmetic (>>)")
+        .value("INT_MULT", OpCode::CPUI_INT_MULT, "Integer multiplication, signed and unsigned (*)")
+        .value("INT_DIV", OpCode::CPUI_INT_DIV, "Integer division, unsigned (/)")
+        .value("INT_SDIV", OpCode::CPUI_INT_SDIV, "Integer division, signed (/)")
+        .value("INT_REM", OpCode::CPUI_INT_REM, "Remainder/modulo, unsigned (%)")
+        .value("INT_SREM", OpCode::CPUI_INT_SREM, "Remainder/modulo, signed (%)")
+        .value("BOOL_NEGATE", OpCode::CPUI_BOOL_NEGATE, "Boolean negate (!)")
+        .value("BOOL_XOR", OpCode::CPUI_BOOL_XOR, "Boolean exclusive-or (^^)")
+        .value("BOOL_AND", OpCode::CPUI_BOOL_AND, "Boolean and (&&)")
+        .value("BOOL_OR", OpCode::CPUI_BOOL_OR, "Boolean or (||)")
+        .value("FLOAT_EQUAL", OpCode::CPUI_FLOAT_EQUAL, "Floating-point comparison, equality (==)")
+        .value("FLOAT_NOTEQUAL", OpCode::CPUI_FLOAT_NOTEQUAL, "Floating-point comparison, in-equality (!=)")
+        .value("FLOAT_LESS", OpCode::CPUI_FLOAT_LESS, "Floating-point comparison, less-than (<)")
+        .value("FLOAT_LESSEQUAL", OpCode::CPUI_FLOAT_LESSEQUAL, "Floating-point comparison, less-than-or-equal (<=)")
+        .value("FLOAT_NAN", OpCode::CPUI_FLOAT_NAN, "Not-a-number test (NaN)")
+        .value("FLOAT_ADD", OpCode::CPUI_FLOAT_ADD, "Floating-point addition (+)")
+        .value("FLOAT_DIV", OpCode::CPUI_FLOAT_DIV, "Floating-point division (/)")
+        .value("FLOAT_MULT", OpCode::CPUI_FLOAT_MULT, "Floating-point multiplication (*)")
+        .value("FLOAT_SUB", OpCode::CPUI_FLOAT_SUB, "Floating-point subtraction (-)")
+        .value("FLOAT_NEG", OpCode::CPUI_FLOAT_NEG, "Floating-point negation (-)")
+        .value("FLOAT_ABS", OpCode::CPUI_FLOAT_ABS, "Floating-point absolute value (abs)")
+        .value("FLOAT_SQRT", OpCode::CPUI_FLOAT_SQRT, "Floating-point square root (sqrt)")
+        .value("FLOAT_INT2FLOAT", OpCode::CPUI_FLOAT_INT2FLOAT, "Convert an integer to a floating-point")
+        .value("FLOAT_FLOAT2FLOAT", OpCode::CPUI_FLOAT_FLOAT2FLOAT, "Convert between different floating-point sizes")
+        .value("FLOAT_TRUNC", OpCode::CPUI_FLOAT_TRUNC, "Round towards zero")
+        .value("FLOAT_CEIL", OpCode::CPUI_FLOAT_CEIL, "Round towards +infinity")
+        .value("FLOAT_FLOOR", OpCode::CPUI_FLOAT_FLOOR, "Round towards -infinity")
+        .value("FLOAT_ROUND", OpCode::CPUI_FLOAT_ROUND, "Round towards nearest")
+        .value("MULTIEQUAL", OpCode::CPUI_MULTIEQUAL, "Phi-node operator")
+        .value("INDIRECT", OpCode::CPUI_INDIRECT, "Copy with an indirect effect")
+        .value("PIECE", OpCode::CPUI_PIECE, "Concatenate")
+        .value("SUBPIECE", OpCode::CPUI_SUBPIECE, "Truncate")
+        .value("CAST", OpCode::CPUI_CAST, "Cast from one data-type to another")
+        .value("PTRADD", OpCode::CPUI_PTRADD, "Index into an array ([])")
+        .value("PTRSUB", OpCode::CPUI_PTRSUB, "Drill down to a sub-field  (->)")
+        .value("SEGMENTOP", OpCode::CPUI_SEGMENTOP, "Look-up a segmented address")
+        .value("CPOOLREF", OpCode::CPUI_CPOOLREF, "Recover a value from the constant pool")
+        .value("NEW", OpCode::CPUI_NEW, "Allocate a new object (new)")
+        .value("INSERT", OpCode::CPUI_INSERT, "Insert a bit-range")
+        .value("EXTRACT", OpCode::CPUI_EXTRACT, "Extract a bit-range")
+        .value("POPCOUNT", OpCode::CPUI_POPCOUNT, "Count the 1-bits");
+
+    nb::class_<PcodeOp>(m, "PcodeOp", "Low-level representation of a single P-Code operation.")
+        .def_ro("opcode", &PcodeOp::m_opcode, "opcode(self) -> OpCode\nOpcode for this operation.")
+        .def_ro("output", &PcodeOp::m_output, "output(self) -> Optional[Varnode]\nOutput varnode for this operation.")
+        .def_ro("inputs", &PcodeOp::m_inputs, "inputs(self) -> List[Varnode]\nInput varnodes for this operation.");
+
+    nb::class_<Translation>(m, "Translation", "P-Code translation.")
+        .def_ro("ops", &Translation::m_ops, "ops(self) -> List[PcodeOp]\nThe translated sequence of P-Code ops.");
+
+    nb::class_<DisassemblyInstruction>(m, "Instruction", "Disassembled machine code instruction.")
+        .def_ro("addr", &DisassemblyInstruction::m_addr, "addr(self) -> Address\nAddress of this instruction.")
+        .def_ro(
+            "length", &DisassemblyInstruction::m_length, "length(self) -> int\nLength, in bytes, of this instruction.")
+        .def_ro("mnem", &DisassemblyInstruction::m_mnem, "mnem(self) -> str\nMnemonic string of this instruction.")
+        .def_ro("body", &DisassemblyInstruction::m_body, "body(self) -> str\nOperand string of this instruction.");
+
+    nb::class_<Disassembly>(m, "Disassembly", "Machine Code Disassembly.")
+        .def_ro("instructions",
+                &Disassembly::m_instructions,
+                "instructions(self) -> List[Instruction]\nThe disassembled instructions.");
+
+    m.attr("TRANSLATE_FLAGS_BB_TERMINATING") = static_cast<int>(TranslateFlags::BB_TERMINATING);
+
+    nb::class_<Context>(m, "Context", "Context for machine code translation and disassembly.")
+        .def(nb::init<const std::string &>())
+        .def(
+            "disassemble",
+            [](Context &t,
+               nb::bytes buf,
+               uint64_t base_address,
+               uint64_t offset,
+               uint64_t max_bytes,
+               uint64_t max_instructions) {
+                if (offset >= buf.size()) {
+                    throw std::out_of_range("offset out of range");
+                }
+                uint64_t available_bytes = buf.size() - offset;
+                max_bytes = max_bytes ? MIN(max_bytes, available_bytes) : available_bytes;
+                return t.disassemble(buf.c_str() + offset, max_bytes, base_address, max_instructions);
+            },
+            "buf"_a,
+            "base_address"_a = 0,
+            "offset"_a = 0,
+            "max_bytes"_a = 0,
+            "max_instructions"_a = 0,
+            nb::keep_alive<0, 1>(),
+            R"(Disassemble and format machine code as assembly code.
+
+            .. ipython::
+
+               In [0]: import pypcode
+                  ...: ctx = pypcode.Context("x86:LE:64:default")
+                  ...: dx = ctx.disassemble(b"\x48\x35\x78\x56\x34\x12\xc3")
+                  ...: for ins in dx.instructions:
+                  ...:     print(f"{ins.addr.offset:#x}/{ins.length}: {ins.mnem} {ins.body}")
+                  0x0/6: XOR RAX,0x12345678
+                  0x6/1: RET
+
+            Instructions are decoded from ``buf`` and formatted in :class:`.Instruction` s:
+              * the end of the buffer is reached,
+              * ``max_bytes`` or ``max_instructions`` is reached, or
+              * an exception occurs.
+
+            If an exception occurs following successful disassembly of at least one instruction, the exception is
+            discarded and the successful disassembly is returned. If the exception occurs at disassembly of the first
+            instruction, it will be raised. See below for possible exceptions.
+
+            Args:
+                buf (bytes): Machine code to disassemble.
+                base_address (int): Base address of the code at offset being decoded, 0 by default.
+                offset (int): Offset into ``bytes`` to begin disassembly, 0 by default.
+                max_bytes (int): Maximum number of bytes to disassemble, or 0 for no limit (default).
+                max_instructions (int): Maximum number of instructions to disassemble, or 0 for no limit (default).
+
+            Returns:
+                Disassembly: The disassembled machine code. Instructions are accessible through :attr:`.Disassembly.instructions`.
+
+            Raises:
+                BadDataError: The instruction at ``base_address`` could be decoded.
+            )")
+        .def(
+            "getAllRegisters",
+            [](Context &t) {
+                map<VarnodeData, string> regmap;
+                t.m_sleigh->getAllRegisters(regmap);
+                return regmap;
+            },
+            "Get a mapping of all register locations to their corresponding names.")
+        .def(
+            "getRegisterName",
+            [](Context &t, AddrSpace *space, uint64_t offset, uint32_t size) {
+                return t.m_sleigh->getRegisterName(space, offset, size);
+            },
+            "space"_a,
+            "offset"_a,
+            "size"_a,
+            R"(Get the name of a register.
+
+            Args:
+                space (AddrSpace): The address space.
+                offset (int): Offset within the address space.
+                size (int): Size of the register, in bytes.
+
+            Returns:
+                str: The register name, or the empty string if the register could not be identified.
+            )")
+        .def("reset", &Context::reset, "Reset the context.")
+        .def(
+            "setVariableDefault",
+            [](Context &t, const std::string &name, uint32_t value) { t.m_context_db.setVariableDefault(name, value); },
+            "name"_a,
+            "value"_a,
+            "Provide a default value for a context variable.")
+        .def(
+            "translate",
+            [](Context &t,
+               nb::bytes buf,
+               uint64_t base_address,
+               uint64_t offset,
+               uint64_t max_bytes,
+               uint64_t max_instructions,
+               uint64_t flags) {
+                if (offset >= buf.size()) {
+                    throw std::out_of_range("offset out of range");
+                }
+                uint64_t available_bytes = buf.size() - offset;
+                max_bytes = max_bytes ? MIN(max_bytes, available_bytes) : available_bytes;
+                return t.translate(buf.c_str() + offset, max_bytes, base_address, max_instructions, flags);
+            },
+            "buf"_a,
+            "base_address"_a = 0,
+            "offset"_a = 0,
+            "max_bytes"_a = 0,
+            "max_instructions"_a = 0,
+            "flags"_a = 0,
+            nb::keep_alive<0, 1>(),
+            R"(Translate machine code to P-Code.
+
+            .. ipython::
+
+               In [0]: import pypcode
+                  ...: ctx = pypcode.Context("x86:LE:64:default")
+                  ...: tx = ctx.translate(b"\x48\x35\x78\x56\x34\x12\xc3")  # xor rax, 0x12345678; ret
+                  ...: for op in tx.ops:
+                  ...:     print(pypcode.PcodePrettyPrinter.fmt_op(op))
+                  IMARK ram[0:6]
+                  CF = 0x0
+                  OF = 0x0
+                  RAX = RAX ^ 0x12345678
+                  SF = RAX s< 0x0
+                  ZF = RAX == 0x0
+                  unique[13180:8] = RAX & 0xff
+                  unique[13200:1] = popcount(unique[13180:8])
+                  unique[13280:1] = unique[13200:1] & 0x1
+                  PF = unique[13280:1] == 0x0
+                  IMARK ram[6:1]
+                  RIP = *[ram]RSP
+                  RSP = RSP + 0x8
+                  return RIP
+
+            Instructions are decoded from ``buf`` and translated to a sequence of :class:`.PcodeOp` s until:
+              * the end of the buffer is reached,
+              * ``max_bytes`` or ``max_instructions`` is reached,
+              * if the ``BB_TERMINATING`` flag is set, an instruction which performs a branch is encountered, or
+              * an exception occurs.
+
+            A :class:`.PcodeOp` with opcode :attr:`OpCode.IMARK` is used to identify machine instructions corresponding
+            to a translation. :attr:`OpCode.IMARK` ops precede the corresponding P-Code translation, and will have one
+            or more input :class:`.Varnode` s identifying the address and length in bytes of the source machine
+            instruction(s). The number of input :class:`.Varnode` s depends on the number of instructions that were
+            decoded for the translation of the particular instruction.
+
+            On architectures with branch delay slots, the effects of the delay slot instructions will be included in the
+            translation of the branch instruction. For this reason, it is possible that more instructions than
+            specified in ``max_instructions`` may be translated. The :attr:`OpCode.IMARK` op identifying the branch
+            instruction will contain an input :class:`.Varnode` corresponding to the branch instruction, with
+            additional input :class:`.Varnode` identifying corresponding delay slot instructions.
+
+            If an exception occurs following successful translation of at least one instruction, the exception is
+            discarded and the successful translation is returned. If the exception occurs during translation of the
+            first instruction, the exception will be raised. See below for possible exceptions.
+
+            Args:
+                buf (bytes): Machine code to translate.
+                base_address (int): Base address of the code at offset being decoded.
+                offset (int): Offset into ``bytes`` to begin translation.
+                max_bytes (int): Maximum number of bytes to translate.
+                max_instructions (int): Maximum number of instructions to translate.
+                flags (int): Flags controlling translation. See :class:`.TranslateFlags`.
+
+            Returns:
+                Translation: The P-Code translation of the input machine code. P-Code ops are accessible through :attr:`.Translation.ops`.
+
+            Raises:
+                BadDataError: The instruction at ``base_address`` could not be decoded.
+                UnimplError: The P-Code for instruction at ``base_address`` is not yet implemented.
+            )");
+}
diff --git a/pypcode/pypcode_native.pyi b/pypcode/pypcode_native.pyi
new file mode 100644
index 00000000..2eebe82a
--- /dev/null
+++ b/pypcode/pypcode_native.pyi
@@ -0,0 +1,172 @@
+from typing import Any, ClassVar, List, Optional
+
+__version__: str
+
+TRANSLATE_FLAGS_BB_TERMINATING: int
+
+class BadDataError(Exception): ...
+class DecoderError(Exception): ...
+class UnimplError(Exception): ...
+
+class AddrSpace:
+    def __init__(self, *args, **kwargs) -> None: ...
+    @property
+    def name(self) -> str: ...
+
+class Address:
+    def __init__(self, *args, **kwargs) -> None: ...
+    @property
+    def offset(self) -> int: ...
+    @property
+    def space(self) -> AddrSpace: ...
+
+class Instruction:
+    def __init__(self, *args, **kwargs) -> None: ...
+    @property
+    def addr(self) -> Address: ...
+    @property
+    def body(self) -> str: ...
+    @property
+    def length(self) -> int: ...
+    @property
+    def mnem(self) -> str: ...
+
+class Disassembly:
+    def __init__(self, *args, **kwargs) -> None: ...
+    @property
+    def instructions(self) -> List[Instruction]: ...
+
+class OpCode:
+    BOOL_AND: ClassVar[OpCode] = ...
+    BOOL_NEGATE: ClassVar[OpCode] = ...
+    BOOL_OR: ClassVar[OpCode] = ...
+    BOOL_XOR: ClassVar[OpCode] = ...
+    BRANCH: ClassVar[OpCode] = ...
+    BRANCHIND: ClassVar[OpCode] = ...
+    CALL: ClassVar[OpCode] = ...
+    CALLIND: ClassVar[OpCode] = ...
+    CALLOTHER: ClassVar[OpCode] = ...
+    CAST: ClassVar[OpCode] = ...
+    CBRANCH: ClassVar[OpCode] = ...
+    COPY: ClassVar[OpCode] = ...
+    CPOOLREF: ClassVar[OpCode] = ...
+    EXTRACT: ClassVar[OpCode] = ...
+    FLOAT_ABS: ClassVar[OpCode] = ...
+    FLOAT_ADD: ClassVar[OpCode] = ...
+    FLOAT_CEIL: ClassVar[OpCode] = ...
+    FLOAT_DIV: ClassVar[OpCode] = ...
+    FLOAT_EQUAL: ClassVar[OpCode] = ...
+    FLOAT_FLOAT2FLOAT: ClassVar[OpCode] = ...
+    FLOAT_FLOOR: ClassVar[OpCode] = ...
+    FLOAT_INT2FLOAT: ClassVar[OpCode] = ...
+    FLOAT_LESS: ClassVar[OpCode] = ...
+    FLOAT_LESSEQUAL: ClassVar[OpCode] = ...
+    FLOAT_MULT: ClassVar[OpCode] = ...
+    FLOAT_NAN: ClassVar[OpCode] = ...
+    FLOAT_NEG: ClassVar[OpCode] = ...
+    FLOAT_NOTEQUAL: ClassVar[OpCode] = ...
+    FLOAT_ROUND: ClassVar[OpCode] = ...
+    FLOAT_SQRT: ClassVar[OpCode] = ...
+    FLOAT_SUB: ClassVar[OpCode] = ...
+    FLOAT_TRUNC: ClassVar[OpCode] = ...
+    IMARK: ClassVar[OpCode] = ...
+    INDIRECT: ClassVar[OpCode] = ...
+    INSERT: ClassVar[OpCode] = ...
+    INT_2COMP: ClassVar[OpCode] = ...
+    INT_ADD: ClassVar[OpCode] = ...
+    INT_AND: ClassVar[OpCode] = ...
+    INT_CARRY: ClassVar[OpCode] = ...
+    INT_DIV: ClassVar[OpCode] = ...
+    INT_EQUAL: ClassVar[OpCode] = ...
+    INT_LEFT: ClassVar[OpCode] = ...
+    INT_LESS: ClassVar[OpCode] = ...
+    INT_LESSEQUAL: ClassVar[OpCode] = ...
+    INT_MULT: ClassVar[OpCode] = ...
+    INT_NEGATE: ClassVar[OpCode] = ...
+    INT_NOTEQUAL: ClassVar[OpCode] = ...
+    INT_OR: ClassVar[OpCode] = ...
+    INT_REM: ClassVar[OpCode] = ...
+    INT_RIGHT: ClassVar[OpCode] = ...
+    INT_SBORROW: ClassVar[OpCode] = ...
+    INT_SCARRY: ClassVar[OpCode] = ...
+    INT_SDIV: ClassVar[OpCode] = ...
+    INT_SEXT: ClassVar[OpCode] = ...
+    INT_SLESS: ClassVar[OpCode] = ...
+    INT_SLESSEQUAL: ClassVar[OpCode] = ...
+    INT_SREM: ClassVar[OpCode] = ...
+    INT_SRIGHT: ClassVar[OpCode] = ...
+    INT_SUB: ClassVar[OpCode] = ...
+    INT_XOR: ClassVar[OpCode] = ...
+    INT_ZEXT: ClassVar[OpCode] = ...
+    LOAD: ClassVar[OpCode] = ...
+    MULTIEQUAL: ClassVar[OpCode] = ...
+    NEW: ClassVar[OpCode] = ...
+    PIECE: ClassVar[OpCode] = ...
+    POPCOUNT: ClassVar[OpCode] = ...
+    PTRADD: ClassVar[OpCode] = ...
+    PTRSUB: ClassVar[OpCode] = ...
+    RETURN: ClassVar[OpCode] = ...
+    SEGMENTOP: ClassVar[OpCode] = ...
+    STORE: ClassVar[OpCode] = ...
+    SUBPIECE: ClassVar[OpCode] = ...
+    __entries: ClassVar[dict] = ...
+    __name__: Any
+    def __init__(self, *args, **kwargs) -> None: ...
+    def __eq__(self, other) -> Any: ...
+    def __ge__(self, other) -> Any: ...
+    def __gt__(self, other) -> Any: ...
+    def __hash__(self) -> Any: ...
+    def __index__(self) -> Any: ...
+    def __int__(self) -> Any: ...
+    def __le__(self, other) -> Any: ...
+    def __lt__(self, other) -> Any: ...
+    def __ne__(self, other) -> Any: ...
+
+class Varnode:
+    def __init__(self) -> None: ...
+    def getRegisterName(self) -> str: ...
+    def getSpaceFromConst(self) -> AddrSpace: ...
+    @property
+    def offset(self) -> int: ...
+    @property
+    def size(self) -> int: ...
+    @property
+    def space(self) -> AddrSpace: ...
+
+class PcodeOp:
+    def __init__(self, *args, **kwargs) -> None: ...
+    @property
+    def inputs(self) -> List[Varnode]: ...
+    @property
+    def opcode(self) -> OpCode: ...
+    @property
+    def output(self) -> Optional[Varnode]: ...
+
+class Translation:
+    def __init__(self, *args, **kwargs) -> None: ...
+    @property
+    def ops(self) -> List[PcodeOp]: ...
+
+class Context:
+    def __init__(self, *args, **kwargs) -> None: ...
+    def disassemble(
+        self,
+        bytes: bytes,
+        base_address: int = ...,
+        offset: int = ...,
+        max_bytes: int = ...,
+        max_instructions: int = ...,
+    ) -> Disassembly: ...
+    def getAllRegisters(self) -> dict[Varnode, str]: ...
+    def getRegisterName(self, space: AddrSpace, offset: int, size: int) -> str: ...
+    def reset(self) -> None: ...
+    def setVariableDefault(self, name: str, value: int) -> None: ...
+    def translate(
+        self,
+        bytes: bytes,
+        base_address: int = ...,
+        offset: int = ...,
+        max_bytes: int = ...,
+        max_instructions: int = ...,
+        flags: int = ...,
+    ) -> Translation: ...
diff --git a/pypcode/native/sleigh/Makefile b/pypcode/sleigh/Makefile
similarity index 100%
rename from pypcode/native/sleigh/Makefile
rename to pypcode/sleigh/Makefile
diff --git a/pypcode/native/sleigh/address.cc b/pypcode/sleigh/address.cc
similarity index 100%
rename from pypcode/native/sleigh/address.cc
rename to pypcode/sleigh/address.cc
diff --git a/pypcode/native/sleigh/address.hh b/pypcode/sleigh/address.hh
similarity index 100%
rename from pypcode/native/sleigh/address.hh
rename to pypcode/sleigh/address.hh
diff --git a/pypcode/native/sleigh/context.cc b/pypcode/sleigh/context.cc
similarity index 100%
rename from pypcode/native/sleigh/context.cc
rename to pypcode/sleigh/context.cc
diff --git a/pypcode/native/sleigh/context.hh b/pypcode/sleigh/context.hh
similarity index 100%
rename from pypcode/native/sleigh/context.hh
rename to pypcode/sleigh/context.hh
diff --git a/pypcode/native/sleigh/emulate.cc b/pypcode/sleigh/emulate.cc
similarity index 100%
rename from pypcode/native/sleigh/emulate.cc
rename to pypcode/sleigh/emulate.cc
diff --git a/pypcode/native/sleigh/emulate.hh b/pypcode/sleigh/emulate.hh
similarity index 100%
rename from pypcode/native/sleigh/emulate.hh
rename to pypcode/sleigh/emulate.hh
diff --git a/pypcode/native/sleigh/error.hh b/pypcode/sleigh/error.hh
similarity index 97%
rename from pypcode/native/sleigh/error.hh
rename to pypcode/sleigh/error.hh
index 788b2421..d25f4b3a 100644
--- a/pypcode/native/sleigh/error.hh
+++ b/pypcode/sleigh/error.hh
@@ -45,6 +45,8 @@ struct LowlevelError {
   string explain;		///< Explanatory string
   /// Initialize the error with an explanatory string
   LowlevelError(const string &s) { explain = s; }
+
+  const char *what() { return explain.c_str(); }
 };
 
 /// \brief A generic recoverable error
diff --git a/pypcode/native/sleigh/filemanage.cc b/pypcode/sleigh/filemanage.cc
similarity index 100%
rename from pypcode/native/sleigh/filemanage.cc
rename to pypcode/sleigh/filemanage.cc
diff --git a/pypcode/native/sleigh/filemanage.hh b/pypcode/sleigh/filemanage.hh
similarity index 100%
rename from pypcode/native/sleigh/filemanage.hh
rename to pypcode/sleigh/filemanage.hh
diff --git a/pypcode/native/sleigh/float.cc b/pypcode/sleigh/float.cc
similarity index 100%
rename from pypcode/native/sleigh/float.cc
rename to pypcode/sleigh/float.cc
diff --git a/pypcode/native/sleigh/float.hh b/pypcode/sleigh/float.hh
similarity index 100%
rename from pypcode/native/sleigh/float.hh
rename to pypcode/sleigh/float.hh
diff --git a/pypcode/native/sleigh/globalcontext.cc b/pypcode/sleigh/globalcontext.cc
similarity index 100%
rename from pypcode/native/sleigh/globalcontext.cc
rename to pypcode/sleigh/globalcontext.cc
diff --git a/pypcode/native/sleigh/globalcontext.hh b/pypcode/sleigh/globalcontext.hh
similarity index 100%
rename from pypcode/native/sleigh/globalcontext.hh
rename to pypcode/sleigh/globalcontext.hh
diff --git a/pypcode/native/sleigh/loadimage.cc b/pypcode/sleigh/loadimage.cc
similarity index 100%
rename from pypcode/native/sleigh/loadimage.cc
rename to pypcode/sleigh/loadimage.cc
diff --git a/pypcode/native/sleigh/loadimage.hh b/pypcode/sleigh/loadimage.hh
similarity index 100%
rename from pypcode/native/sleigh/loadimage.hh
rename to pypcode/sleigh/loadimage.hh
diff --git a/pypcode/native/sleigh/loadimage_bfd.cc b/pypcode/sleigh/loadimage_bfd.cc
similarity index 100%
rename from pypcode/native/sleigh/loadimage_bfd.cc
rename to pypcode/sleigh/loadimage_bfd.cc
diff --git a/pypcode/native/sleigh/loadimage_bfd.hh b/pypcode/sleigh/loadimage_bfd.hh
similarity index 100%
rename from pypcode/native/sleigh/loadimage_bfd.hh
rename to pypcode/sleigh/loadimage_bfd.hh
diff --git a/pypcode/native/sleigh/marshal.cc b/pypcode/sleigh/marshal.cc
similarity index 100%
rename from pypcode/native/sleigh/marshal.cc
rename to pypcode/sleigh/marshal.cc
diff --git a/pypcode/native/sleigh/marshal.hh b/pypcode/sleigh/marshal.hh
similarity index 100%
rename from pypcode/native/sleigh/marshal.hh
rename to pypcode/sleigh/marshal.hh
diff --git a/pypcode/native/sleigh/memstate.cc b/pypcode/sleigh/memstate.cc
similarity index 100%
rename from pypcode/native/sleigh/memstate.cc
rename to pypcode/sleigh/memstate.cc
diff --git a/pypcode/native/sleigh/memstate.hh b/pypcode/sleigh/memstate.hh
similarity index 100%
rename from pypcode/native/sleigh/memstate.hh
rename to pypcode/sleigh/memstate.hh
diff --git a/pypcode/native/sleigh/opbehavior.cc b/pypcode/sleigh/opbehavior.cc
similarity index 100%
rename from pypcode/native/sleigh/opbehavior.cc
rename to pypcode/sleigh/opbehavior.cc
diff --git a/pypcode/native/sleigh/opbehavior.hh b/pypcode/sleigh/opbehavior.hh
similarity index 100%
rename from pypcode/native/sleigh/opbehavior.hh
rename to pypcode/sleigh/opbehavior.hh
diff --git a/pypcode/native/sleigh/opcodes.cc b/pypcode/sleigh/opcodes.cc
similarity index 100%
rename from pypcode/native/sleigh/opcodes.cc
rename to pypcode/sleigh/opcodes.cc
diff --git a/pypcode/native/sleigh/opcodes.hh b/pypcode/sleigh/opcodes.hh
similarity index 99%
rename from pypcode/native/sleigh/opcodes.hh
rename to pypcode/sleigh/opcodes.hh
index 14ec9977..45733fe1 100644
--- a/pypcode/native/sleigh/opcodes.hh
+++ b/pypcode/sleigh/opcodes.hh
@@ -33,6 +33,9 @@ using namespace std;
 ///   - Logical operations
 ///   - Extension and truncation operations
 enum OpCode {
+  CPUI_IMARK = 0,
+
+
   CPUI_COPY = 1,		///< Copy one operand to another
   CPUI_LOAD = 2,		///< Load from a pointer into a specified address space
   CPUI_STORE = 3,		///< Store at a pointer into a specified address space
diff --git a/pypcode/native/sleigh/partmap.hh b/pypcode/sleigh/partmap.hh
similarity index 100%
rename from pypcode/native/sleigh/partmap.hh
rename to pypcode/sleigh/partmap.hh
diff --git a/pypcode/native/sleigh/pcodecompile.cc b/pypcode/sleigh/pcodecompile.cc
similarity index 100%
rename from pypcode/native/sleigh/pcodecompile.cc
rename to pypcode/sleigh/pcodecompile.cc
diff --git a/pypcode/native/sleigh/pcodecompile.hh b/pypcode/sleigh/pcodecompile.hh
similarity index 100%
rename from pypcode/native/sleigh/pcodecompile.hh
rename to pypcode/sleigh/pcodecompile.hh
diff --git a/pypcode/native/sleigh/pcodeparse.cc b/pypcode/sleigh/pcodeparse.cc
similarity index 100%
rename from pypcode/native/sleigh/pcodeparse.cc
rename to pypcode/sleigh/pcodeparse.cc
diff --git a/pypcode/native/sleigh/pcodeparse.hh b/pypcode/sleigh/pcodeparse.hh
similarity index 100%
rename from pypcode/native/sleigh/pcodeparse.hh
rename to pypcode/sleigh/pcodeparse.hh
diff --git a/pypcode/native/sleigh/pcodeparse.y b/pypcode/sleigh/pcodeparse.y
similarity index 100%
rename from pypcode/native/sleigh/pcodeparse.y
rename to pypcode/sleigh/pcodeparse.y
diff --git a/pypcode/native/sleigh/pcoderaw.cc b/pypcode/sleigh/pcoderaw.cc
similarity index 100%
rename from pypcode/native/sleigh/pcoderaw.cc
rename to pypcode/sleigh/pcoderaw.cc
diff --git a/pypcode/native/sleigh/pcoderaw.hh b/pypcode/sleigh/pcoderaw.hh
similarity index 100%
rename from pypcode/native/sleigh/pcoderaw.hh
rename to pypcode/sleigh/pcoderaw.hh
diff --git a/pypcode/native/sleigh/semantics.cc b/pypcode/sleigh/semantics.cc
similarity index 100%
rename from pypcode/native/sleigh/semantics.cc
rename to pypcode/sleigh/semantics.cc
diff --git a/pypcode/native/sleigh/semantics.hh b/pypcode/sleigh/semantics.hh
similarity index 100%
rename from pypcode/native/sleigh/semantics.hh
rename to pypcode/sleigh/semantics.hh
diff --git a/pypcode/native/sleigh/sleigh.cc b/pypcode/sleigh/sleigh.cc
similarity index 99%
rename from pypcode/native/sleigh/sleigh.cc
rename to pypcode/sleigh/sleigh.cc
index 0fd86e64..f1d72a7b 100644
--- a/pypcode/native/sleigh/sleigh.cc
+++ b/pypcode/sleigh/sleigh.cc
@@ -476,6 +476,13 @@ DisassemblyCache::DisassemblyCache(Translate *trans,ContextCache *ccache,AddrSpa
   initialize(cachesize,windowsize);		// Set default settings for the cache
 }
 
+void DisassemblyCache::fastReset(void)
+
+{
+  for(int4 i=0;i<minimumreuse;++i)
+    list[i]->setParserState(ParserContext::uninitialized);
+}
+
 /// Return a (possibly cached) ParserContext that is associated with \e addr
 /// If n different calls to this interface are made with n different Addresses, if
 ///    - n <= minimumreuse   AND
@@ -542,6 +549,14 @@ void Sleigh::reset(LoadImage *ld,ContextDatabase *c_db)
   discache = (DisassemblyCache *)0;
 }
 
+void Sleigh::fastReset()
+
+{
+  if (discache) {
+    discache->fastReset();
+  }
+}
+
 /// The .sla file from the document store is loaded and cache objects are prepared
 /// \param store is the document store containing the main \<sleigh> tag.
 void Sleigh::initialize(DocumentStorage &store)
diff --git a/pypcode/native/sleigh/sleigh.hh b/pypcode/sleigh/sleigh.hh
similarity index 99%
rename from pypcode/native/sleigh/sleigh.hh
rename to pypcode/sleigh/sleigh.hh
index 74fc5cbf..d15a49fc 100644
--- a/pypcode/native/sleigh/sleigh.hh
+++ b/pypcode/sleigh/sleigh.hh
@@ -117,6 +117,7 @@ public:
   DisassemblyCache(Translate *trans,ContextCache *ccache,AddrSpace *cspace,int4 cachesize,int4 windowsize);	///< Constructor
   ~DisassemblyCache(void) { free(); }	///< Destructor
   ParserContext *getParserContext(const Address &addr);		///< Get the parser for a particular Address
+  void fastReset();              ///< Reset parser states to uninitialized
 };
 
 /// \brief Build p-code from a pre-parsed instruction
@@ -172,6 +173,7 @@ public:
   Sleigh(LoadImage *ld,ContextDatabase *c_db);		///< Constructor
   virtual ~Sleigh(void);				///< Destructor
   void reset(LoadImage *ld,ContextDatabase *c_db);	///< Reset the engine for a new program
+  void fastReset();					///< Quickly reset the engine
   virtual void initialize(DocumentStorage &store);
   virtual void registerContext(const string &name,int4 sbit,int4 ebit);
   virtual void setContextDefault(const string &nm,uintm val);
diff --git a/pypcode/native/sleigh/sleighbase.cc b/pypcode/sleigh/sleighbase.cc
similarity index 100%
rename from pypcode/native/sleigh/sleighbase.cc
rename to pypcode/sleigh/sleighbase.cc
diff --git a/pypcode/native/sleigh/sleighbase.hh b/pypcode/sleigh/sleighbase.hh
similarity index 100%
rename from pypcode/native/sleigh/sleighbase.hh
rename to pypcode/sleigh/sleighbase.hh
diff --git a/pypcode/native/sleigh/slgh_compile.cc b/pypcode/sleigh/slgh_compile.cc
similarity index 100%
rename from pypcode/native/sleigh/slgh_compile.cc
rename to pypcode/sleigh/slgh_compile.cc
diff --git a/pypcode/native/sleigh/slgh_compile.hh b/pypcode/sleigh/slgh_compile.hh
similarity index 100%
rename from pypcode/native/sleigh/slgh_compile.hh
rename to pypcode/sleigh/slgh_compile.hh
diff --git a/pypcode/native/sleigh/slghparse.cc b/pypcode/sleigh/slghparse.cc
similarity index 100%
rename from pypcode/native/sleigh/slghparse.cc
rename to pypcode/sleigh/slghparse.cc
diff --git a/pypcode/native/sleigh/slghparse.hh b/pypcode/sleigh/slghparse.hh
similarity index 100%
rename from pypcode/native/sleigh/slghparse.hh
rename to pypcode/sleigh/slghparse.hh
diff --git a/pypcode/native/sleigh/slghparse.y b/pypcode/sleigh/slghparse.y
similarity index 100%
rename from pypcode/native/sleigh/slghparse.y
rename to pypcode/sleigh/slghparse.y
diff --git a/pypcode/native/sleigh/slghpatexpress.cc b/pypcode/sleigh/slghpatexpress.cc
similarity index 100%
rename from pypcode/native/sleigh/slghpatexpress.cc
rename to pypcode/sleigh/slghpatexpress.cc
diff --git a/pypcode/native/sleigh/slghpatexpress.hh b/pypcode/sleigh/slghpatexpress.hh
similarity index 100%
rename from pypcode/native/sleigh/slghpatexpress.hh
rename to pypcode/sleigh/slghpatexpress.hh
diff --git a/pypcode/native/sleigh/slghpattern.cc b/pypcode/sleigh/slghpattern.cc
similarity index 100%
rename from pypcode/native/sleigh/slghpattern.cc
rename to pypcode/sleigh/slghpattern.cc
diff --git a/pypcode/native/sleigh/slghpattern.hh b/pypcode/sleigh/slghpattern.hh
similarity index 100%
rename from pypcode/native/sleigh/slghpattern.hh
rename to pypcode/sleigh/slghpattern.hh
diff --git a/pypcode/native/sleigh/slghscan.cc b/pypcode/sleigh/slghscan.cc
similarity index 99%
rename from pypcode/native/sleigh/slghscan.cc
rename to pypcode/sleigh/slghscan.cc
index 1ac87e2c..8ae05860 100644
--- a/pypcode/native/sleigh/slghscan.cc
+++ b/pypcode/sleigh/slghscan.cc
@@ -1546,9 +1546,9 @@ extern int yylex (void);
  */
 YY_DECL
 {
-	register yy_state_type yy_current_state;
-	register char *yy_cp, *yy_bp;
-	register int yy_act;
+	yy_state_type yy_current_state;
+	char *yy_cp, *yy_bp;
+	int yy_act;
     
 #line 493 "slghscan.l"
 
@@ -1598,7 +1598,7 @@ YY_DECL
 yy_match:
 		do
 			{
-			register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
+			YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
 			if ( yy_accept[yy_current_state] )
 				{
 				(yy_last_accepting_state) = yy_current_state;
@@ -2630,9 +2630,9 @@ ECHO;
  */
 static int yy_get_next_buffer (void)
 {
-    	register char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
-	register char *source = (yytext_ptr);
-	register int number_to_move, i;
+    	char *dest = YY_CURRENT_BUFFER_LVALUE->yy_ch_buf;
+	char *source = (yytext_ptr);
+	int number_to_move, i;
 	int ret_val;
 
 	if ( (yy_c_buf_p) > &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[(yy_n_chars) + 1] )
@@ -2764,15 +2764,15 @@ static int yy_get_next_buffer (void)
 
     static yy_state_type yy_get_previous_state (void)
 {
-	register yy_state_type yy_current_state;
-	register char *yy_cp;
+	yy_state_type yy_current_state;
+	char *yy_cp;
     
 	yy_current_state = (yy_start);
 	yy_current_state += YY_AT_BOL();
 
 	for ( yy_cp = (yytext_ptr) + YY_MORE_ADJ; yy_cp < (yy_c_buf_p); ++yy_cp )
 		{
-		register YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
+		YY_CHAR yy_c = (*yy_cp ? yy_ec[YY_SC_TO_UI(*yy_cp)] : 1);
 		if ( yy_accept[yy_current_state] )
 			{
 			(yy_last_accepting_state) = yy_current_state;
@@ -2797,10 +2797,10 @@ static int yy_get_next_buffer (void)
  */
     static yy_state_type yy_try_NUL_trans  (yy_state_type yy_current_state )
 {
-	register int yy_is_jam;
-    	register char *yy_cp = (yy_c_buf_p);
+	int yy_is_jam;
+    	char *yy_cp = (yy_c_buf_p);
 
-	register YY_CHAR yy_c = 1;
+	YY_CHAR yy_c = 1;
 	if ( yy_accept[yy_current_state] )
 		{
 		(yy_last_accepting_state) = yy_current_state;
@@ -2818,9 +2818,9 @@ static int yy_get_next_buffer (void)
 		return yy_is_jam ? 0 : yy_current_state;
 }
 
-    static void yyunput (int c, register char * yy_bp )
+    static void yyunput (int c, char * yy_bp )
 {
-	register char *yy_cp;
+	char *yy_cp;
     
     yy_cp = (yy_c_buf_p);
 
@@ -2830,10 +2830,10 @@ static int yy_get_next_buffer (void)
 	if ( yy_cp < YY_CURRENT_BUFFER_LVALUE->yy_ch_buf + 2 )
 		{ /* need to shift things up to make room */
 		/* +2 for EOB chars. */
-		register yy_size_t number_to_move = (yy_n_chars) + 2;
-		register char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
+		yy_size_t number_to_move = (yy_n_chars) + 2;
+		char *dest = &YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[
 					YY_CURRENT_BUFFER_LVALUE->yy_buf_size + 2];
-		register char *source =
+		char *source =
 				&YY_CURRENT_BUFFER_LVALUE->yy_ch_buf[number_to_move];
 
 		while ( source > YY_CURRENT_BUFFER_LVALUE->yy_ch_buf )
@@ -3444,7 +3444,7 @@ int yylex_destroy  (void)
 #ifndef yytext_ptr
 static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
 {
-	register int i;
+	int i;
 	for ( i = 0; i < n; ++i )
 		s1[i] = s2[i];
 }
@@ -3453,7 +3453,7 @@ static void yy_flex_strncpy (char* s1, yyconst char * s2, int n )
 #ifdef YY_NEED_STRLEN
 static int yy_flex_strlen (yyconst char * s )
 {
-	register int n;
+	int n;
 	for ( n = 0; s[n]; ++n )
 		;
 
diff --git a/pypcode/native/sleigh/slghscan.l b/pypcode/sleigh/slghscan.l
similarity index 100%
rename from pypcode/native/sleigh/slghscan.l
rename to pypcode/sleigh/slghscan.l
diff --git a/pypcode/native/sleigh/slghsymbol.cc b/pypcode/sleigh/slghsymbol.cc
similarity index 100%
rename from pypcode/native/sleigh/slghsymbol.cc
rename to pypcode/sleigh/slghsymbol.cc
diff --git a/pypcode/native/sleigh/slghsymbol.hh b/pypcode/sleigh/slghsymbol.hh
similarity index 100%
rename from pypcode/native/sleigh/slghsymbol.hh
rename to pypcode/sleigh/slghsymbol.hh
diff --git a/pypcode/native/sleigh/space.cc b/pypcode/sleigh/space.cc
similarity index 100%
rename from pypcode/native/sleigh/space.cc
rename to pypcode/sleigh/space.cc
diff --git a/pypcode/native/sleigh/space.hh b/pypcode/sleigh/space.hh
similarity index 100%
rename from pypcode/native/sleigh/space.hh
rename to pypcode/sleigh/space.hh
diff --git a/pypcode/native/sleigh/translate.cc b/pypcode/sleigh/translate.cc
similarity index 100%
rename from pypcode/native/sleigh/translate.cc
rename to pypcode/sleigh/translate.cc
diff --git a/pypcode/native/sleigh/translate.hh b/pypcode/sleigh/translate.hh
similarity index 100%
rename from pypcode/native/sleigh/translate.hh
rename to pypcode/sleigh/translate.hh
diff --git a/pypcode/native/sleigh/types.h b/pypcode/sleigh/types.h
similarity index 100%
rename from pypcode/native/sleigh/types.h
rename to pypcode/sleigh/types.h
diff --git a/pypcode/native/sleigh/xml.cc b/pypcode/sleigh/xml.cc
similarity index 100%
rename from pypcode/native/sleigh/xml.cc
rename to pypcode/sleigh/xml.cc
diff --git a/pypcode/native/sleigh/xml.hh b/pypcode/sleigh/xml.hh
similarity index 99%
rename from pypcode/native/sleigh/xml.hh
rename to pypcode/sleigh/xml.hh
index a1c763a3..e5773343 100644
--- a/pypcode/native/sleigh/xml.hh
+++ b/pypcode/sleigh/xml.hh
@@ -288,6 +288,8 @@ public:
 struct DecoderError {
   string explain;		///< Explanatory string
   DecoderError(const string &s) { explain = s; }	///< Constructor
+  
+  const char *what() { return explain.c_str(); }
 };
 
 /// \brief Start-up the XML parser given a stream and a handler
diff --git a/pypcode/native/sleigh/xml.y b/pypcode/sleigh/xml.y
similarity index 100%
rename from pypcode/native/sleigh/xml.y
rename to pypcode/sleigh/xml.y
diff --git a/pyproject.toml b/pyproject.toml
index c95819e0..323d8062 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,5 +1,5 @@
 [build-system]
-requires = ["setuptools", "cffi", "cmake"]
+requires = ["setuptools", "nanobind", "cmake"]
 build-backend = "setuptools.build_meta"
 
 [tool.black]
diff --git a/scripts/benchmark.py b/scripts/benchmark.py
new file mode 100755
index 00000000..0a0f6af9
--- /dev/null
+++ b/scripts/benchmark.py
@@ -0,0 +1,343 @@
+#!/usr/bin/env python3
+# pylint:disable=import-outside-toplevel,wrong-import-position
+"""
+Benchmark disassembly and IR lifting performance for pypcode and other libraries.
+"""
+
+import argparse
+import csv
+import functools
+import gc
+import hashlib
+import logging
+import os
+import pickle
+import random
+import sys
+import time
+from dataclasses import dataclass
+
+from typing import cast, Any, Callable, Dict, Iterable, List, Tuple
+
+
+logging.basicConfig(level=logging.INFO)
+log = logging.getLogger(__name__)
+log.setLevel(logging.INFO)
+
+
+try:
+    import_start_time = time.time()
+
+    import capstone  # type: ignore
+
+    CAPSTONE_IMPORT_DURATION = time.time() - import_start_time
+    del import_start_time
+    HAVE_CAPSTONE = True
+except ImportError:
+    HAVE_CAPSTONE = False
+
+try:
+    import_start_time = time.time()
+
+    import pypcode
+
+    PYPCODE_IMPORT_DURATION = time.time() - import_start_time
+    del import_start_time
+    HAVE_PYPCODE = True
+except ImportError:
+    HAVE_PYPCODE = False
+
+try:
+    import_start_time = time.time()
+
+    import archinfo
+    import pyvex
+
+    PYVEX_IMPORT_DURATION = time.time() - import_start_time
+    del import_start_time
+    HAVE_PYVEX = True
+except ImportError:
+    HAVE_PYVEX = False
+
+
+@dataclass
+class Block:
+    """Block to translate"""
+
+    addr: int
+    data: bytes
+
+
+@dataclass
+class BenchmarkResult:
+    """Result of a benchmark run"""
+
+    startup_duration: float
+    translation_duration: float
+
+
+def get_file_hash(binary_path: str) -> str:
+    log.info("Calculating sha256 of file '%s'", binary_path)
+    m = hashlib.sha256()
+    with open(binary_path, "rb") as f:
+        m.update(f.read())
+    d = m.hexdigest()
+    log.info("sha256:%s", d)
+    return d
+
+
+def get_blocks(binary_path: str) -> List[Block]:
+    blocks_file_path = f"blocks_{get_file_hash(binary_path)[0:8]}.cache"
+    blocks: List[Block] = []
+
+    if not os.path.exists(blocks_file_path):
+        log.info("Recovering blocks from CFG...")
+
+        try:
+            import angr
+
+            logging.getLogger("angr").setLevel(logging.WARNING)
+            logging.getLogger("cle").setLevel(logging.WARNING)
+            logging.getLogger("pyvex").setLevel(logging.WARNING)
+            logging.getLogger("claripy").setLevel(logging.WARNING)
+        except ImportError:
+            log.error("Install angr to build list of blocks")
+            sys.exit(1)
+
+        p = angr.Project(binary_path, auto_load_libs=False)
+        cfg = p.analyses[angr.analyses.CFGFast].prep(show_progressbar=True)(
+            resolve_indirect_jumps=False, force_smart_scan=False
+        )
+        for n in cast(Iterable[angr.analyses.cfg.CFGNode], cfg.model.nodes()):
+            if n.byte_string:
+                blocks.append(Block(n.addr, n.byte_string))
+        log.info("Saving blocks to file '%s' for subsequent benchmarks...", blocks_file_path)
+        with open(blocks_file_path, "wb") as f:
+            pickle.dump(blocks, f)
+    else:
+        log.info("Loading blocks from cache file '%s'...", blocks_file_path)
+        with open(blocks_file_path, "rb") as f:
+            blocks = pickle.load(f)
+
+    return blocks
+
+
+def benchmark_pypcode(blocks: List[Block], iter_ops: bool = False, iter_varnodes: bool = False) -> BenchmarkResult:
+    assert pypcode is not None
+
+    start_time = time.time()
+    ctx = pypcode.Context("x86:LE:64:default")
+    startup_duration = time.time() - start_time
+
+    start_time = time.time()
+    count = 0
+    for block in blocks:
+        t = ctx.translate(block.data, block.addr)
+        if iter_ops and not iter_varnodes:
+            for _ in t.ops:
+                count += 1
+        if iter_varnodes:
+            for op in t.ops:
+                for _ in op.inputs:
+                    count += 1
+    translation_duration = time.time() - start_time
+
+    return BenchmarkResult(startup_duration, translation_duration)
+
+
+def benchmark_pypcode_disassembly(blocks: List[Block]) -> BenchmarkResult:
+    assert pypcode is not None
+
+    start_time = time.time()
+    ctx = pypcode.Context("x86:LE:64:default")
+    startup_duration = time.time() - start_time
+
+    start_time = time.time()
+    size = 0
+    for block in blocks:
+        for ins in ctx.disassemble(block.data, block.addr).instructions:
+            size += ins.length
+    translation_duration = time.time() - start_time
+
+    return BenchmarkResult(startup_duration, translation_duration)
+
+
+def benchmark_pyvex(blocks: List[Block], **vex_args) -> BenchmarkResult:
+    assert pyvex is not None
+
+    start_time = time.time()
+    arch = archinfo.ArchAMD64()
+    startup_duration = time.time() - start_time
+
+    start_time = time.time()
+    for block in blocks:
+        pyvex.lift(block.data, block.addr, arch, **vex_args)
+    translation_duration = time.time() - start_time
+
+    return BenchmarkResult(startup_duration, translation_duration)
+
+
+def benchmark_capstone(blocks: List[Block], lite: bool = False) -> BenchmarkResult:
+    assert capstone is not None
+
+    start_time = time.time()
+    md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
+    startup_duration = time.time() - start_time
+
+    start_time = time.time()
+    size = 0
+    if lite:
+        for block in blocks:
+            for ins in md.disasm_lite(block.data, block.addr):
+                size += ins[1]
+    else:
+        for block in blocks:
+            for ins in md.disasm(block.data, block.addr):
+                size += ins.size
+    translation_duration = time.time() - start_time
+
+    return BenchmarkResult(startup_duration, translation_duration)
+
+
+def gen_benchmarks_from_configurations(
+    name: str, benchmark_func: Callable, configurations: List[Dict[str, Any]]
+) -> List[Tuple[str, Callable]]:
+    benchmarks: List[Tuple[str, Callable]] = []
+    for config in configurations:
+        name_append = ""
+        if config:
+            name_append += f" ({', '.join(f'{k}={v}' for k, v in config.items())})"
+        benchmarks.append((name + name_append, functools.partial(benchmark_func, **config)))
+    return benchmarks
+
+
+def main() -> None:
+    ap = argparse.ArgumentParser(description=__doc__)
+    ap.add_argument(
+        "-b", "--binary", default=sys.executable, help="Binary to benchmark with (Python interpreter by default)"
+    )
+    ap.add_argument(
+        "-c",
+        "--coverage",
+        default=1,
+        type=float,
+        help="Percentage of blocks to include in benchmark in (0,1] (default=1)",
+    )
+    ap.add_argument(
+        "--skip", nargs="*", default=[], help="Skip benchmarks for a particular package (pypcode, pyvex, capstone)"
+    )
+    ap.add_argument("--csv", help="Save results to CSV file")
+    args = ap.parse_args()
+
+    # Display import times (use python -Ximporttime for more accurate import profiling)
+    imports = []
+    if HAVE_CAPSTONE:
+        imports.append(("capstone", capstone.__version__, CAPSTONE_IMPORT_DURATION))
+    if HAVE_PYPCODE:
+        imports.append(("pypcode", pypcode.__version__, PYPCODE_IMPORT_DURATION))
+    if HAVE_PYVEX:
+        imports.append(("pyvex", pyvex.__version__, PYVEX_IMPORT_DURATION))
+    for name, version, import_duration in imports:
+        log.info("%s v%s took %.2f ms to import", name, version, import_duration * 1000)
+
+    benchmarks: List[Tuple[str, Callable]] = []
+    if HAVE_CAPSTONE and "capstone" not in args.skip:
+        benchmarks.extend(
+            gen_benchmarks_from_configurations(
+                "capstone Disassemble",
+                benchmark_capstone,
+                [
+                    {},
+                    {"lite": True},
+                ],
+            )
+        )
+    if HAVE_PYPCODE and "pypcode" not in args.skip:
+        benchmarks.append(("pypcode Disassemble", benchmark_pypcode_disassembly))
+        benchmarks.extend(
+            gen_benchmarks_from_configurations(
+                "pypcode Lift",
+                benchmark_pypcode,
+                [
+                    {},
+                    {"iter_ops": True},  # Iterate over all ops
+                    {"iter_varnodes": True},  # Iterate over all ops and varnodes
+                ],
+            )
+        )
+    if HAVE_PYVEX and "pyvex" not in args.skip:
+        benchmarks.extend(
+            gen_benchmarks_from_configurations(
+                "pyvex Lift",
+                benchmark_pyvex,
+                [
+                    {},
+                    {"opt_level": -1},
+                    {"opt_level": 0},
+                    {"skip_stmts": True},
+                    {"collect_data_refs": True},
+                ],
+            )
+        )
+    if not benchmarks:
+        log.error("No benchmarks to run. Install pypcode to run a benchmark.")
+        sys.exit(1)
+
+    log.info("Using blocks from binary '%s' for benchmarking", args.binary)
+    blocks = get_blocks(args.binary)
+    num_blocks = len(blocks)
+
+    # Handle reduced block sampling
+    assert 0 < args.coverage <= 1, "Specified coverage percentage not in range (0, 1]"
+    if args.coverage < 1:
+        blocks = random.choices(blocks, k=int(num_blocks * args.coverage))
+        num_blocks = len(blocks)
+
+    if num_blocks == 0:
+        log.error("No blocks included in benchmark!")
+        sys.exit(1)
+
+    blocks_total_size = sum(len(block.data) for block in blocks)
+    log.info("Benchmark includes %d blocks totaling %.1f KiB", num_blocks, blocks_total_size / 1024)
+
+    gc.collect()
+    results: List[Tuple[str, BenchmarkResult]] = []
+    gc.disable()
+    for name, benchmark in benchmarks:
+        log.info("Benchmarking %s performance...", name)
+        results.append((name, benchmark(blocks)))
+    gc.enable()
+
+    rows = [["Benchmark", "Startup ms", "Process s", "KiB/s", "kBlock/s", "us/Block"]]
+    num_cols = len(rows[0])
+
+    for name, result in results:
+        rows.append(
+            [
+                name,
+                f"{result.startup_duration * 1000:.3f}",
+                f"{result.translation_duration:.3f}",
+                f"{(blocks_total_size / result.translation_duration) / 1024:.2f}",
+                f"{num_blocks / result.translation_duration / 1000:.2f}",
+                f"{result.translation_duration / num_blocks * 1000000:.3f}",
+            ]
+        )
+
+    col_widths = [max(len(row[c]) for row in rows) for c in range(num_cols)]
+    row = rows[0]
+    header = " | ".join(row[c].ljust(col_widths[c]) for c in range(num_cols))
+    log.info("-" * len(header))
+    log.info("%s", header)
+    log.info("-" * len(header))
+    for row in rows[1:]:
+        log.info("%s", " | ".join(row[c].ljust(col_widths[c]) for c in range(num_cols)))
+
+    if args.csv:
+        with open(args.csv, "w", encoding="utf-8") as f:
+            writer = csv.writer(f)
+            for row in rows:
+                writer.writerow(row)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/pypcode/native/sleigh_download.sh b/scripts/sleigh_download.sh
similarity index 95%
rename from pypcode/native/sleigh_download.sh
rename to scripts/sleigh_download.sh
index 79aac150..171e68a2 100755
--- a/pypcode/native/sleigh_download.sh
+++ b/scripts/sleigh_download.sh
@@ -6,7 +6,7 @@ git clone --depth=1 -b Ghidra_10.2.3_build https://github.com/NationalSecurityAg
 
 # We just need Makefile and $(LIBSLA_SOURCE) defined inside Makefile. Do it this
 # way to make sure we stay up to date with the list of required files.
-SLEIGH_SRC_DIR=sleigh_src
+SLEIGH_SRC_DIR=sleigh
 pushd ghidra_src/Ghidra/Features/Decompiler/src/decompile/cpp/
 echo -e "$SLEIGH_SRC_DIR:\n\tmkdir -p $SLEIGH_SRC_DIR\n\tcp \$(LIBSLA_SOURCE) Makefile $SLEIGH_SRC_DIR" >> Makefile
 make $SLEIGH_SRC_DIR
diff --git a/setup.cfg b/setup.cfg
index 5ec1aa64..8eb23ddd 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
 [metadata]
 name = pypcode
-version = attr: pypcode.__version__
+version = attr: pypcode.__version__.__version__
 description = Machine code disassembly and IR translation library
 long_description = file: README.md
 long_description_content_type = text/markdown
@@ -18,12 +18,7 @@ classifiers =
 [options]
 packages =
     pypcode
-install_requires =
-    cffi
-    types-cffi
 python_requires = >=3.8
-setup_requires =
-    cffi
 
 [options.extras_require]
 docs =
diff --git a/setup.py b/setup.py
index 1f8c6781..f511dbc9 100644
--- a/setup.py
+++ b/setup.py
@@ -1,12 +1,53 @@
 #!/usr/bin/env python3
 import os
+import platform
+import struct
+import subprocess
 import sys
-from setuptools import setup
+from setuptools import setup, Extension
+from setuptools.command.build_ext import build_ext
+
 
 ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
-sys.path.append(ROOT_DIR)
+BUILD_DIR = os.path.join(ROOT_DIR, "build", "native")
+
+
+class BuildExtension(build_ext):
+    """
+    Runs cmake to build the pypcode_native extension, sleigh binary, and runs sleigh to build .sla files.
+    """
+
+    def run(self):
+        try:
+            subprocess.check_output(["cmake", "--version"])
+        except OSError as exc:
+            raise RuntimeError("Please install CMake to build") from exc
+
+        install_pkg_root = os.path.abspath(os.path.join(ROOT_DIR if self.inplace else self.build_lib, "pypcode"))
+        cmake_install_prefix = install_pkg_root
+        cmake_config_args = [
+            f"-DCMAKE_INSTALL_PREFIX={cmake_install_prefix}",
+            f"-DPython_EXECUTABLE={sys.executable}",
+        ]
+        cmake_build_args = []
+        if platform.system() == "Windows":
+            is_64b = struct.calcsize("P") * 8 == 64
+            cmake_config_args += ["-A", "x64" if is_64b else "Win32"]
+            cmake_build_args += ["--config", "Release"]
+
+        # Build sleigh and pypcode_native extension
+        subprocess.check_call(["cmake", "-S", ".", "-B", BUILD_DIR] + cmake_config_args, cwd=ROOT_DIR)
+        subprocess.check_call(
+            ["cmake", "--build", BUILD_DIR, "--parallel", "--verbose"] + cmake_build_args,
+            cwd=ROOT_DIR,
+        )
+        subprocess.check_call(["cmake", "--install", BUILD_DIR], cwd=ROOT_DIR)
 
-import build_cffi  # pylint:disable=wrong-import-position
+        # Build sla files
+        bin_ext = ".exe" if platform.system() == "Windows" else ""
+        sleigh_bin = os.path.join(install_pkg_root, "bin", "sleigh" + bin_ext)
+        specfiles_dir = os.path.join(install_pkg_root, "processors")
+        subprocess.check_call([sleigh_bin, "-a", specfiles_dir])
 
 
 def add_pkg_data_dirs(pkg, dirs):
@@ -18,32 +59,10 @@ def add_pkg_data_dirs(pkg, dirs):
     return pkg_data
 
 
-cmdclass = {"build_ext": build_cffi.FfiPreBuildExtension}
-
-try:
-    from wheel.bdist_wheel import bdist_wheel
-
-    class bdist_wheel_abi3(bdist_wheel):
-        """
-        Helper to generate correct platform tag for stable ABI wheels
-        """
-
-        def get_tag(self):
-            python, abi, plat = super().get_tag()
-            if python.startswith("cp"):
-                return "cp38", "abi3", plat
-            return python, abi, plat
-
-    cmdclass["bdist_wheel"] = bdist_wheel_abi3
-except ImportError:
-    pass
-
-
 setup(
+    ext_modules=[Extension(name="pypcode_native", sources=[])],
     package_data={
-        "pypcode": add_pkg_data_dirs("pypcode", ["_csleigh", "bin", "docs", "processors"])
-        + ["py.typed", "_csleigh.pyi"]
+        "pypcode": add_pkg_data_dirs("pypcode", ["bin", "docs", "processors"]) + ["py.typed", "pypcode_native.pyi"]
     },
-    cffi_modules=["build_cffi.py:ffibuilder"],
-    cmdclass=cmdclass,
+    cmdclass={"build_ext": BuildExtension},
 )


Test patch:
diff --git a/tests/test.py b/tests/test.py
index 929772ca..0d0f0dbe 100755
--- a/tests/test.py
+++ b/tests/test.py
@@ -1,36 +1,300 @@
 #!/usr/bin/env python3
 # pylint:disable=no-self-use
 
+import gc
+import logging
 from unittest import main, TestCase
+from typing import List
 
-from pypcode import ArchLanguage, BadDataError, Context, ContextCreationError, UnimplError
+from pypcode import (
+    Arch,
+    ArchLanguage,
+    BadDataError,
+    Context,
+    DecoderError,
+    OpCode,
+    PcodeOp,
+    TranslateFlags,
+    Translation,
+    UnimplError,
+)
 
+# logging.basicConfig(level=logging.DEBUG)
+log = logging.getLogger(__name__)
 
-class Tests(TestCase):
+
+def get_imarks(translation: Translation) -> List[PcodeOp]:
+    return [op for op in translation.ops if op.opcode == OpCode.IMARK]
+
+
+# from pypcode import PcodePrettyPrinter
+# def pp_tx(translation: Translation) -> None:
+#     for op in translation.ops:
+#         log.debug("%s", PcodePrettyPrinter.fmt_op(op))
+
+
+# def pp_dx(disassembly) -> None:
+#     for ins in disassembly:
+#         log.debug("%x/%d: %s %s", ins.addr.offset, ins.length, ins.mnem, ins.body)
+
+
+class ContextTests(TestCase):
     """
-    Basic tests
+    Basic Context tests
     """
 
-    def test_failed_context_creation(self):
+    def tearDown(self):
+        gc.collect()
+
+    def test_can_create_all_language_contexts(self):
+        for arch in Arch.enumerate():
+            for lang in arch.languages:
+                with self.subTest(lang=lang.id):
+                    log.debug("Creating context for %s", lang.id)
+                    Context(lang)
+
+    def test_context_creation_failure(self):
         lang = ArchLanguage.from_id("x86:LE:64:default")
         bad_lang = ArchLanguage("/bad/arch/path", lang.ldef)
-        with self.assertRaises(ContextCreationError):
+        with self.assertRaises(DecoderError):
             Context(bad_lang)
 
-    def test_x86_64_translation(self):
-        ctx = Context(ArchLanguage.from_id("x86:LE:64:default"))
-        result = ctx.translate(b"\x48\x35\x78\x56\x34\x12\xc3", 0)
-        assert len(result.instructions) == 2
+    def test_context_premature_release(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xc3")
+        del ctx
+        log.debug("Should not crash: %d", len(tx.ops[0].inputs[0].space.name))
+        del tx
+        log.debug("--")
+
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xc3")
+        op = tx.ops[0]
+        del tx  # Should not be released
+        del ctx  # Should not be released while op is alive
+        log.debug("Should not crash: %d", len(op.inputs[0].space.name))
+        del op  # Now ctx, tx can be released
+        log.debug("--")
+
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xc3")
+        vn = tx.ops[0].inputs[0]
+        del tx
+        del ctx
+        log.debug("Should not crash: %d", len(vn.space.name))
+        del vn  # Now ctx, tx can be released
+        log.debug("--")
+
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xc3")
+        space = tx.ops[0].inputs[0].space
+        del tx
+        del ctx
+        log.debug("Should not crash: %d", len(space.name))
+        del space  # Now ctx, tx can be released. Space is managed by context, so C++ obj should not be released.
+        log.debug("--")
+
+
+class RegistersTests(TestCase):
+    """
+    Context register lookup tests
+    """
+
+    def test_registers(self):
+        ctx = Context("x86:LE:64:default")
+        assert "RAX" in ctx.registers
+
+    def test_getRegisterName(self):
+        ctx = Context("x86:LE:64:default")
+        ri = ctx.registers["RAX"]
+        assert ctx.getRegisterName(ri.space, ri.offset, ri.size) == "RAX"
+
+
+class AddrSpaceTests(TestCase):
+    """
+    AddrSpace tests
+    """
+
+    def test_name(self):
+        ctx = Context("x86:LE:64:default")
+        assert ctx.translate(b"\xeb\xfe").ops[1].inputs[0].space.name == "ram"
+
+
+class VarnodeTests(TestCase):
+    """
+    Varnode tests
+    """
+
+    def test_getSpaceFromConst(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x48\x8B\x41\x01")  # mov rax, [rcx + 1]
+        assert tx.ops[2].inputs[0].getSpaceFromConst().name == "ram"
+
+    def test_getRegisterName(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x48\x8B\x41\x01")  # mov rax, [rcx + 1]
+        assert tx.ops[1].inputs[0].getRegisterName() == "RCX"
+        assert tx.ops[1].inputs[1].getRegisterName() == ""
+
+
+class DisassembleTests(TestCase):
+    """
+    Context::disassemble tests
+    """
+
+    def test_disassemble(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\x90\xeb\xfe")
+        assert len(dx.instructions) == 2
+        ins = dx.instructions[1]
+        assert ins.addr.offset == 1
+        assert ins.length == 2
+        assert ins.mnem == "JMP"
+        assert ins.body == "0x1"
+
+    def test_decode_failure(self):
+        ctx = Context("x86:LE:64:default")
+        with self.assertRaises(BadDataError):
+            ctx.disassemble(b"\x40\x40")
+
+    def test_partial_decode_failure(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\xff\xc0\x90\x40\x40")  # inc eax; nop; bad
+        assert len(dx.instructions) == 2
+
+    def test_not_cached(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\xeb\xfe", 5)
+        dx = ctx.disassemble(b"\xc3", 5)
+        ins = dx.instructions[0]
+        assert ins.addr.offset == 5
+        assert ins.length == 1
+        assert ins.mnem == "RET"
+        assert ins.body == ""
+
+    def test_arg_base_address(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\xeb\xfe", 10)
+        assert len(dx.instructions) == 1
+        assert dx.instructions[0].mnem == "JMP"
+        assert dx.instructions[0].body == "0xa"
+
+    def test_arg_offset(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\x90\xeb\xfe", offset=1)
+        assert len(dx.instructions) == 1
+
+    def test_arg_offset_out_of_range(self):
+        ctx = Context("x86:LE:64:default")
+        with self.assertRaises(IndexError):
+            ctx.disassemble(b"\x90\xeb\xfe", offset=3)
+
+    def test_arg_max_bytes(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\x90\xeb\xfe", max_bytes=1)
+        assert len(dx.instructions) == 1
+
+    def test_arg_max_instructions(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\x90\xeb\xfe", max_instructions=1)
+        assert len(dx.instructions) == 1
+
+
+class TranslateTests(TestCase):
+    """
+    Context::translate tests
+    """
+
+    def test_translate(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x48\x35\x78\x56\x34\x12\xc3")
+        assert len(get_imarks(tx)) == 2
 
     def test_decode_failure(self):
-        ctx = Context(ArchLanguage.from_id("x86:LE:64:default"))
+        ctx = Context("x86:LE:64:default")
         with self.assertRaises(BadDataError):
-            ctx.translate(b"\x40\x40", 0)
+            ctx.translate(b"\x40\x40")
+
+    def test_partial_decode_failure(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xff\xc0\x40\x40")  # inc eax; bad
+        assert len(get_imarks(tx)) == 1
 
     def test_unimpl_failure(self):
-        ctx = Context(ArchLanguage.from_id("Toy:BE:32:default"))
+        ctx = Context("Toy:BE:32:default")
         with self.assertRaises(UnimplError):
-            ctx.translate(b"\xa8\x00", 0)
+            ctx.translate(b"\xa8\x00")
+
+    def test_partial_unimpl_failure(self):
+        ctx = Context("Toy:BE:32:default")
+        tx = ctx.translate(b"\xd0\x00\xa8\x00")  # and r0, r0; unimpl
+        assert len(get_imarks(tx)) == 1
+
+    def test_not_cached(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xeb\xfe", 5)
+        tx = ctx.translate(b"\xc3", 5)
+        assert tx.ops[-1].opcode == OpCode.RETURN
+
+    def test_translate_and_disassemble_not_cached(self):
+        ctx = Context("x86:LE:64:default")
+        dx = ctx.disassemble(b"\xeb\xfe", 5)
+        tx = ctx.translate(b"\xc3", 5)
+        assert tx.ops[-1].opcode == OpCode.RETURN
+
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xc3", 5)
+        dx = ctx.disassemble(b"\xeb\xfe", 5)
+        assert len(dx.instructions) == 1
+        ins = dx.instructions[0]
+        assert ins.addr.offset == 5
+        assert ins.length == 2
+        assert ins.mnem == "JMP"
+        assert ins.body == "0x5"
+
+    def test_arg_base_address(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\xeb\xfe", 10)  # jmp $
+        assert len(tx.ops) == 2
+        assert len(tx.ops[0].inputs) == 1  # Check just one instruction decoded
+        assert tx.ops[0].inputs[0].offset == 10  # Check IMARK
+        assert tx.ops[1].inputs[0].offset == 10  # Check jump target
+
+    def test_arg_offset(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x90\x90\xeb\xfe", offset=2)  # nop; nop; jmp $
+        assert len(get_imarks(tx)) == 1
+
+    def test_arg_offset_out_of_range(self):
+        ctx = Context("x86:LE:64:default")
+        with self.assertRaises(IndexError):
+            ctx.translate(b"\x90\x90\xeb\xfe", offset=10)  # nop; nop; jmp $
+
+    def test_arg_max_bytes(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x90\x90\x90", max_bytes=1)
+        assert len(get_imarks(tx)) == 1
+
+    def test_arg_max_instructions(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x90\x90\x90", max_instructions=2)
+        assert len(get_imarks(tx)) == 2
+
+    def test_arg_flag_bb_terminating(self):
+        ctx = Context("x86:LE:64:default")
+        tx = ctx.translate(b"\x90\xeb\xfe\x90\x90", flags=TranslateFlags.BB_TERMINATING)
+        assert len(get_imarks(tx)) == 2
+
+    def test_delay_slot(self):
+        ctx = Context("MIPS:BE:32:default")
+        tx = ctx.translate(b"\x10@\x00\x06\x00 \x08%", 0x4009F4)
+        imarks = get_imarks(tx)
+        assert len(imarks) == 1
+        assert len(imarks[0].inputs) == 2
+        assert imarks[0].inputs[0].offset == 0x4009F4
+        assert imarks[0].inputs[0].size == 4
+        assert imarks[0].inputs[1].offset == 0x4009F8
+        assert imarks[0].inputs[1].size == 4
 
 
 if __name__ == "__main__":