diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
--- /dev/null
+++ b/ChangeLog.md
@@ -0,0 +1,3 @@
+# Changelog for cppfilt
+
+## Unreleased changes
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Georg Rudoy (c) 2018
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Author name here nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,21 @@
+# cppfilt
+
+[![Build Status](https://travis-ci.org/0xd34df00d/cppfilt.svg?branch=master)](https://travis-ci.org/0xd34df00d/cppfilt)
+
+This library provides bindings to the system ABI-exposed C++ name demangling
+routines. It provides both `IO`-based API in the `System.Demangle` module
+and the pure one in `System.Demangle.Pure`. The latter is deduced as unsafe
+due to `unsafePerformIO` (which should be fine, though, since demangling is
+hopefully referentially transparent).
+
+
+## Supported systems
+
+For now demangling is done using either `libstdc++` or `libc++` (controlled
+via the `use-libcpp` build flag). Adding support for other ABIs in a similar
+fashion should be easy.
+
+This approach is probably suboptimal, though, since one might want to try
+different ABIs that might be available simultaneously (since even clang and
+gcc mangle some names slightly differently), but it's not entirely obvious
+what the API should be like in this case.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/bench/Bench.hs b/bench/Bench.hs
new file mode 100644
--- /dev/null
+++ b/bench/Bench.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+
+import Criterion.Main
+import Data.ByteString(ByteString)
+import Data.String
+
+import qualified System.Demangle as D
+
+testData :: IsString a => [(String, a)]
+testData = [
+    ("simple", "_ZTI5IInfo"),
+    ("operator", "_ZrsR11QDataStreamR5QUuid"),
+    ("complex", "_ZTSZN10LeechCraft4Util15InstanceFunctorI7QFutureINS0_6EitherIN5boost7variantIN5vmime10exceptions20authentication_errorEJNS7_16connection_errorENS6_8security4cert20certificateExceptionENS_6Snails23GenericExceptionWrapperEEEENSD_19AccountThreadWorker10SyncResultEEEEE5ApplyINS0_7VisitorINS0_4VoidEJZNSD_7Account15SynchronizeImplERK5QListI11QStringListERK10QByteArrayNSD_12TaskPriorityEE3$_1ZNSO_15SynchronizeImplEST_SW_SX_E3$_2EEEEES2_INSt5decayINSt9result_ofIFT_SI_EE4typeEE4typeEERKSJ_RKS13_EUlRKSI_E_"),
+    ("rly complex", "_ZN5boost6detail7variant15visitation_implIN4mpl_4int_ILi0EEENS1_20visitation_impl_stepINS_3mpl6l_iterINS7_6l_itemINS3_5long_ILl7EEEN5vmime10exceptions20authentication_errorENS9_INSA_ILl6EEENSD_16connection_errorENS9_INSA_ILl5EEENSC_8security4cert20certificateExceptionENS9_INSA_ILl4EEEN10LeechCraft6Snails23GenericExceptionWrapperENS9_INSA_ILl3EEENSN_15MessageNotFoundENS9_INSA_ILl2EEENSN_13FileOpenErrorENS9_INSA_ILl1EEENSN_18AttachmentNotFoundENS7_5l_endEEEEEEEEEEEEEEEEENS8_ISV_EEEENS1_14invoke_visitorINSM_4Util6detail7VisitorIvJZZNSN_10ThreadPool20PerformScheduledFuncI16QFutureInterfaceINS17_6EitherINS_7variantISE_JSG_SK_SO_SQ_SS_SU_EEENS17_4VoidEEEEMNSN_19AccountThreadWorkerEFNS1D_INS1E_ISQ_JSS_SU_EEES1G_EESt10shared_ptrINSN_7MessageEERK7QStringS1R_EJS1O_S1P_S1P_EEEvPNSN_13AccountThreadET_NSN_12TaskPriorityERKT0_DpRKT1_ENUlS1W_E_clIS1H_EEDaS1W_EUlRKSE_E_ZZNS1B_IS1I_S1T_JS1O_S1P_S1P_EEEvS1V_S1W_S1X_S20_S24_ENS26_IS1H_EEDaS1W_EUlS1W_E_EEEEEPKvNS1F_18has_fallback_type_EEENT1_11result_typeEiiRS2G_T2_NS3_5bool_ILb0EEET3_PS1W_PS1Y_")
+  ]
+
+main :: IO ()
+main = defaultMain [
+    bgroup "demangle string"     [ bench name $ nfIO $ D.demangle @String mangled | (name, mangled) <- testData],
+    bgroup "demangle bytestring" [ bench name $ nfIO $ D.demangle @ByteString mangled | (name, mangled) <- testData]
+  ]
diff --git a/cppfilt.cabal b/cppfilt.cabal
new file mode 100644
--- /dev/null
+++ b/cppfilt.cabal
@@ -0,0 +1,83 @@
+-- This file has been generated from package.yaml by hpack version 0.20.0.
+--
+-- see: https://github.com/sol/hpack
+--
+-- hash: e0c10543991dcafe3f792b8d89a01a6778270aebbde0275b126c27a106b7366f
+
+name:           cppfilt
+version:        0.1.0.0
+synopsis:       Bindings for C++ demangling routines
+description:    Please see the README on Github at <https://github.com/0xd34df00d/cppfilt#readme>
+category:       System
+homepage:       https://github.com/0xd34df00d/cppfilt#readme
+bug-reports:    https://github.com/0xd34df00d/cppfilt/issues
+author:         Georg Rudoy
+maintainer:     0xd34df00d@gmail.com
+copyright:      2018 Georg Rudoy
+license:        BSD3
+license-file:   LICENSE
+build-type:     Simple
+cabal-version:  >= 1.10
+
+extra-source-files:
+    ChangeLog.md
+    README.md
+
+source-repository head
+  type: git
+  location: https://github.com/0xd34df00d/cppfilt
+
+flag use-libcpp
+  description: Use libc++ for demangling (if you're building your C++ code with clang) instead of libstdc++
+  manual: True
+  default: False
+
+library
+  hs-source-dirs:
+      src
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+  if flag(use-libcpp) || os(darwin)
+    extra-libraries:
+        c++
+  else
+    extra-libraries:
+        stdc++
+  exposed-modules:
+      Data.CStringRepresentable
+      System.Demangle
+      System.Demangle.Pure
+  other-modules:
+      Paths_cppfilt
+  default-language: Haskell2010
+
+test-suite cppfilt-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  hs-source-dirs:
+      test
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , cppfilt
+    , hspec
+  other-modules:
+      Paths_cppfilt
+  default-language: Haskell2010
+
+benchmark cppfilt-benchmarks
+  type: exitcode-stdio-1.0
+  main-is: Bench.hs
+  hs-source-dirs:
+      bench
+  ghc-options: -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , bytestring
+    , cppfilt
+    , criterion
+  other-modules:
+      Paths_cppfilt
+  default-language: Haskell2010
diff --git a/src/Data/CStringRepresentable.hs b/src/Data/CStringRepresentable.hs
new file mode 100644
--- /dev/null
+++ b/src/Data/CStringRepresentable.hs
@@ -0,0 +1,34 @@
+{-# LANGUAGE FlexibleInstances #-}
+
+{-|
+ Module      : Data.CStringRepresentable
+ Description : Type class for string-like objects nicely mapping to 'CString's
+ Stability   : experimental
+
+ This module provides the type class (and a few implementations) for values that
+ can be efficiently converted to/from 'CString'. Instances for 'String' and
+ strict 'BS.ByteString' are provided, and the use of 'BS.ByteString' is
+ recommended if performance is a concern.
+ -}
+
+module Data.CStringRepresentable where
+
+import qualified Data.ByteString.Char8 as BS
+import Foreign.C
+
+-- | Represents values that can be efficiently converted to and from 'CString's.
+class CStringRepresentable a where
+  -- | Perform a function on the 'CString' representation of the value.
+  toCString :: a -> (CString -> IO b) -> IO b
+  -- | Convert 'CString' to a value of the type. The ownership is not transferred.
+  fromCString :: CString -> IO a
+
+-- | 'String's can be represented as 'CString's (albeit conversions are inefficient).
+instance CStringRepresentable String where
+  toCString = withCString
+  fromCString = peekCString
+
+-- | 'BS.ByteString's can be represented as 'CString's.
+instance CStringRepresentable BS.ByteString where
+  toCString = BS.useAsCString
+  fromCString = BS.packCString
diff --git a/src/System/Demangle.hs b/src/System/Demangle.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Demangle.hs
@@ -0,0 +1,43 @@
+{-# LANGUAGE ForeignFunctionInterface #-}
+
+{-|
+ Module      : System.Demangle
+ Description : Functions for demangling C++ names
+ Stability   : experimental
+ Portability : wherever gcc lives
+
+ This module provides bindings to the platform C++ ABI demangling routines
+ (where the platform is currently limited to gcc). Any string-like type
+ implementing 'CStringRepresentable' can be used, though strict bytestrings are
+ the most performant.
+
+ This module provides the raw 'IO'-living functions. Thin wrappers pretending
+ to be pure live in the "System.Demangle.Pure" module.
+ -}
+
+module System.Demangle(demangle) where
+
+import qualified Data.ByteString.Char8 as BS
+import Control.Exception
+
+import Foreign.C
+import Foreign.Marshal.Alloc
+import Foreign.Ptr
+
+import Data.CStringRepresentable
+
+foreign import ccall "__cxa_demangle"
+  cxa_demangle :: CString -> CString -> Ptr CSize -> Ptr CInt -> IO CString
+
+-- |Try to demangle a mangled C++ name.
+demangle :: CStringRepresentable s => s -> IO (Maybe s)
+demangle str = toCString str $ \str' ->
+  bracket
+    (cxa_demangle str' nullPtr nullPtr nullPtr)
+    free
+    $ \res ->
+      if res == nullPtr
+        then pure Nothing
+        else do
+          res' <- fromCString res
+          pure $ Just res'
diff --git a/src/System/Demangle/Pure.hs b/src/System/Demangle/Pure.hs
new file mode 100644
--- /dev/null
+++ b/src/System/Demangle/Pure.hs
@@ -0,0 +1,26 @@
+{-|
+ Module      : System.Demangle.Pure
+ Description : Functions for demangling C++ names pretending to be pure
+ Stability   : experimental
+ Portability : wherever gcc lives
+
+ This module provides bindings to the platform C++ ABI demangling routines
+ (where the platform is currently limited to gcc). Any string-like type
+ implementing 'CStringRepresentable' can be used, though strict bytestrings are
+ the most performant.
+
+ This module provides the pure-ish functions which hide the intrinsic 'IO'
+ nature of doing FFI calls behind 'unsafePerformIO'. Demangling is
+ referentially transparent, though, so that should be fine.
+ -}
+
+module System.Demangle.Pure where
+
+import System.IO.Unsafe
+
+import qualified System.Demangle as D
+import Data.CStringRepresentable
+
+-- |Try to demangle a mangled C++ name.
+demangle :: CStringRepresentable s => s -> Maybe s
+demangle = unsafePerformIO . D.demangle
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,48 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TypeApplications #-}
+
+import Test.Hspec
+
+import qualified Data.ByteString.Char8 as BS
+import Data.String
+import Control.Monad
+
+import qualified System.Demangle as D
+import qualified System.Demangle.Pure as DP
+
+import Data.CStringRepresentable
+
+ioDemangledIs :: (Eq a, Show a, CStringRepresentable a) => a -> Maybe a -> IO ()
+ioDemangledIs mangled ref = do
+  res <- D.demangle mangled
+  res `shouldBe` ref
+
+pureDemangledIs :: (Eq a, Show a, CStringRepresentable a) => a -> Maybe a -> IO ()
+pureDemangledIs mangled ref = DP.demangle mangled `shouldBe` ref
+
+testData :: IsString a => [(String, [(a, Maybe a)])]
+testData = [
+    ("demangles simple class names", [
+        ("_ZTS5IInfo", Just "typeinfo name for IInfo")
+      ]),
+    ("demangles some operators", [
+        ("_ZrsR11QDataStreamR5QUuid", Just "operator>>(QDataStream&, QUuid&)")
+      ]),
+    ("demangle fails", [
+        ("_test", Nothing)
+      ])
+  ]
+
+runTests :: IsString a => (a -> Maybe a -> IO ()) -> SpecWith ()
+runTests func =
+  forM_ testData $ \(str, pairs) ->
+    it str $ forM_ pairs $ uncurry func
+
+main :: IO ()
+main = hspec $ do
+  describe "Strings" $ do
+    describe "IO demangle" $ runTests @String ioDemangledIs
+    describe "Pure demangle" $ runTests @String pureDemangledIs
+  describe "ByteStrings" $ do
+    describe "IO demangle" $ runTests @BS.ByteString ioDemangledIs
+    describe "Pure demangle" $ runTests @BS.ByteString pureDemangledIs
