diff --git a/Data/Text.hs b/Data/Text.hs
--- a/Data/Text.hs
+++ b/Data/Text.hs
@@ -1,5 +1,8 @@
 {-# LANGUAGE BangPatterns, CPP, MagicHash, Rank2Types, UnboxedTuples #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 
 -- |
 -- Module      : Data.Text
@@ -180,13 +183,16 @@
 
     -- -* Ordered text
     -- , sort
+
+    -- * Low level operations
+    , copy
     ) where
 
 import Prelude (Char, Bool(..), Int, Maybe(..), String,
                 Eq(..), Ord(..), Ordering(..), (++),
                 Read(..), Show(..),
                 (&&), (||), (+), (-), (.), ($), ($!), (>>), (*),
-                div, maxBound, not, return, otherwise)
+                maxBound, not, return, otherwise, quot)
 #if defined(HAVE_DEEPSEQ)
 import Control.DeepSeq (NFData)
 #endif
@@ -370,7 +376,7 @@
 -- | /O(n)/ Convert a 'String' into a 'Text'.  Subject to
 -- fusion.  Performs replacement on invalid scalar values.
 pack :: String -> Text
-pack = unstream . S.streamList . L.map safe
+pack = unstream . S.map safe . S.streamList
 {-# INLINE [1] pack #-}
 
 -- | /O(n)/ Convert a Text into a String.  Subject to fusion.
@@ -378,17 +384,17 @@
 unpack = S.unstreamList . stream
 {-# INLINE [1] unpack #-}
 
--- | /O(n)/ Convert a literal string into a Text.
+-- | /O(n)/ Convert a literal string into a Text.  Subject to fusion.
 unpackCString# :: Addr# -> Text
 unpackCString# addr# = unstream (S.streamCString# addr#)
 {-# NOINLINE unpackCString# #-}
 
 {-# RULES "TEXT literal" forall a.
-    unstream (S.streamList (L.map safe (GHC.unpackCString# a)))
+    unstream (S.map safe (S.streamList (GHC.unpackCString# a)))
       = unpackCString# a #-}
 
 {-# RULES "TEXT literal UTF8" forall a.
-    unstream (S.streamList (L.map safe (GHC.unpackCStringUtf8# a)))
+    unstream (S.map safe (S.streamList (GHC.unpackCStringUtf8# a)))
       = unpackCString# a #-}
 
 -- | /O(1)/ Convert a character into a Text.  Subject to fusion.
@@ -731,7 +737,7 @@
     | otherwise = replicateChar l c `append` t `append` replicateChar r c
   where len = length t
         d   = k - len
-        r   = d `div` 2
+        r   = d `quot` 2
         l   = d - r
 {-# INLINE center #-}
 
@@ -903,11 +909,11 @@
 -- @t@ repeated @n@ times.
 replicate :: Int -> Text -> Text
 replicate n t@(Text a o l)
-    | n <= 0 || l <= 0      = empty
-    | n == 1                = t
-    | isSingleton t         = replicateChar n (unsafeHead t)
-    | n <= maxBound `div` l = Text (A.run x) 0 len
-    | otherwise             = overflowError "replicate"
+    | n <= 0 || l <= 0       = empty
+    | n == 1                 = t
+    | isSingleton t          = replicateChar n (unsafeHead t)
+    | n <= maxBound `quot` l = Text (A.run x) 0 len
+    | otherwise              = overflowError "replicate"
   where
     len = l * n
     x = do
@@ -1574,3 +1580,19 @@
 
 overflowError :: String -> a
 overflowError fun = P.error $ "Data.Text." ++ fun ++ ": size overflow"
+
+-- | /O(n)/ Make a distinct copy of the given string, sharing no
+-- storage with the original string.
+--
+-- As an example, suppose you read a large string, of which you need
+-- only a small portion.  If you do not use 'copy', the entire original
+-- array will be kept alive in memory by the smaller string. Making a
+-- copy \"breaks the link\" to the original array, allowing it to be
+-- garbage collected if there are no other live references to it.
+copy :: Text -> Text
+copy (Text arr off len) = Text (A.run go) 0 len
+  where
+    go = do
+      marr <- A.new len
+      A.copyI marr 0 arr off len
+      return marr
diff --git a/Data/Text/Array.hs b/Data/Text/Array.hs
--- a/Data/Text/Array.hs
+++ b/Data/Text/Array.hs
@@ -114,7 +114,7 @@
 -- | Create an uninitialized mutable array.
 new :: forall s. Int -> ST s (MArray s)
 new n
-  | n < 0 || n .&. highBit /= 0 = error $ "Data.Text.Array.new: size overflow"
+  | n < 0 || n .&. highBit /= 0 = array_size_error
   | otherwise = ST $ \s1# ->
        case newByteArray# len# s1# of
          (# s2#, marr# #) -> (# s2#, MArray marr#
@@ -126,6 +126,9 @@
         highBit    = maxBound `xor` (maxBound `shiftR` 1)
 {-# INLINE new #-}
 
+array_size_error :: a
+array_size_error = error "Data.Text.Array.new: size overflow"
+
 -- | Freeze a mutable array. Do not mutate the 'MArray' afterwards!
 unsafeFreeze :: MArray s -> ST s Array
 unsafeFreeze MArray{..} = ST $ \s# ->
@@ -181,6 +184,7 @@
                  (marr,b) <- k
                  arr <- unsafeFreeze marr
                  return (arr,b))
+{-# INLINE run2 #-}
 
 -- | Copy some elements of a mutable array.
 copyM :: MArray s               -- ^ Destination
diff --git a/Data/Text/Encoding.hs b/Data/Text/Encoding.hs
--- a/Data/Text/Encoding.hs
+++ b/Data/Text/Encoding.hs
@@ -1,5 +1,8 @@
 {-# LANGUAGE BangPatterns, CPP, ForeignFunctionInterface, MagicHash,
     UnliftedFFITypes #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.Text.Encoding
 -- Copyright   : (c) 2009, 2010, 2011 Bryan O'Sullivan,
@@ -23,6 +26,7 @@
     -- * Decoding ByteStrings to Text
     -- $strict
       decodeASCII
+    , decodeLatin1
     , decodeUtf8
     , decodeUtf16LE
     , decodeUtf16BE
@@ -57,7 +61,7 @@
 import Data.ByteString as B
 import Data.ByteString.Internal as B
 import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
-import Data.Text.Internal (Text(..))
+import Data.Text.Internal (Text(..), safe, textP)
 import Data.Text.Private (runText)
 import Data.Text.UnsafeChar (ord, unsafeWrite)
 import Data.Text.UnsafeShift (shiftL, shiftR)
@@ -68,11 +72,11 @@
 import Foreign.Ptr (Ptr, minusPtr, plusPtr)
 import Foreign.Storable (peek, poke)
 import GHC.Base (MutableByteArray#)
-import System.IO.Unsafe (unsafePerformIO)
 import qualified Data.Text.Array as A
 import qualified Data.Text.Encoding.Fusion as E
 import qualified Data.Text.Encoding.Utf16 as U16
 import qualified Data.Text.Fusion as F
+import Data.Text.Unsafe (unsafeDupablePerformIO)
 
 -- $strict
 --
@@ -90,11 +94,23 @@
 -- | /Deprecated/.  Decode a 'ByteString' containing 7-bit ASCII
 -- encoded text.
 --
--- This function is deprecated.  Use 'decodeUtf8' instead.
+-- This function is deprecated.  Use 'decodeLatin1' instead.
 decodeASCII :: ByteString -> Text
 decodeASCII = decodeUtf8
 {-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
 
+-- | Decode a 'ByteString' containing Latin-1 (aka ISO-8859-1) encoded text.
+--
+-- 'decodeLatin1' is semantically equivalent to
+--  @Data.Text.pack . Data.ByteString.Char8.unpack@
+decodeLatin1 :: ByteString -> Text
+decodeLatin1 (PS fp off len) = textP a 0 len
+ where
+  a = A.run (A.new len >>= unsafeIOToST . go)
+  go dest = withForeignPtr fp $ \ptr -> do
+    c_decode_latin1 (A.maBA dest) (ptr `plusPtr` off) (ptr `plusPtr` (off+len))
+    return dest
+
 -- | Decode a 'ByteString' containing UTF-8 encoded text.
 decodeUtf8With :: OnDecodeError -> ByteString -> Text
 decodeUtf8With onErr (PS fp off len) = runText $ \done -> do
@@ -114,7 +130,7 @@
                       Just c -> do
                         destOff <- peek destOffPtr
                         w <- unsafeSTToIO $
-                             unsafeWrite dest (fromIntegral destOff) c
+                             unsafeWrite dest (fromIntegral destOff) (safe c)
                         poke destOffPtr (destOff + fromIntegral w)
                         loop $ curPtr' `plusPtr` 1
           loop (ptr `plusPtr` off)
@@ -141,12 +157,12 @@
 -- If the input contains any invalid UTF-8 data, the relevant
 -- exception will be returned, otherwise the decoded text.
 decodeUtf8' :: ByteString -> Either UnicodeException Text
-decodeUtf8' = unsafePerformIO . try . evaluate . decodeUtf8With strictDecode
+decodeUtf8' = unsafeDupablePerformIO . try . evaluate . decodeUtf8With strictDecode
 {-# INLINE decodeUtf8' #-}
 
 -- | Encode text using UTF-8 encoding.
 encodeUtf8 :: Text -> ByteString
-encodeUtf8 (Text arr off len) = unsafePerformIO $ do
+encodeUtf8 (Text arr off len) = unsafeDupablePerformIO $ do
   let size0 = max len 4
   mallocByteString size0 >>= start size0 off 0
  where
@@ -279,3 +295,6 @@
 foreign import ccall unsafe "_hs_text_decode_utf8" c_decode_utf8
     :: MutableByteArray# s -> Ptr CSize
     -> Ptr Word8 -> Ptr Word8 -> IO (Ptr Word8)
+
+foreign import ccall unsafe "_hs_text_decode_latin1" c_decode_latin1
+    :: MutableByteArray# s -> Ptr Word8 -> Ptr Word8 -> IO ()
diff --git a/Data/Text/Encoding/Error.hs b/Data/Text/Encoding/Error.hs
--- a/Data/Text/Encoding/Error.hs
+++ b/Data/Text/Encoding/Error.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE CPP, DeriveDataTypeable #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.Text.Encoding.Error
 -- Copyright   : (c) Bryan O'Sullivan 2009
@@ -35,6 +38,7 @@
     , replace
     ) where
 
+import Control.DeepSeq (NFData (..))
 #if __GLASGOW_HASKELL__ >= 610
 import Control.Exception (Exception, throw)
 #else
@@ -93,6 +97,10 @@
     show = showUnicodeException
 
 instance Exception UnicodeException
+
+instance NFData UnicodeException where
+    rnf (DecodeError desc w) = rnf desc `seq` rnf w `seq` ()
+    rnf (EncodeError desc c) = rnf desc `seq` rnf c `seq` ()
 
 -- | Throw a 'UnicodeException' if decoding fails.
 strictDecode :: OnDecodeError
diff --git a/Data/Text/Encoding/Fusion.hs b/Data/Text/Encoding/Fusion.hs
--- a/Data/Text/Encoding/Fusion.hs
+++ b/Data/Text/Encoding/Fusion.hs
@@ -44,12 +44,12 @@
 import Data.Word (Word8, Word16, Word32)
 import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
 import Foreign.Storable (pokeByteOff)
-import System.IO.Unsafe (unsafePerformIO)
 import qualified Data.ByteString as B
 import qualified Data.ByteString.Unsafe as B
 import qualified Data.Text.Encoding.Utf8 as U8
 import qualified Data.Text.Encoding.Utf16 as U16
 import qualified Data.Text.Encoding.Utf32 as U32
+import Data.Text.Unsafe (unsafeDupablePerformIO)
 
 streamASCII :: ByteString -> Stream Char
 streamASCII bs = Stream next 0 (maxSize l)
@@ -163,7 +163,7 @@
 
 -- | /O(n)/ Convert a 'Stream' 'Word8' to a 'ByteString'.
 unstream :: Stream Word8 -> ByteString
-unstream (Stream next s0 len) = unsafePerformIO $ do
+unstream (Stream next s0 len) = unsafeDupablePerformIO $ do
     let mlen = upperBound 4 len
     mallocByteString mlen >>= loop mlen 0 s0
     where
diff --git a/Data/Text/Encoding/Fusion/Common.hs b/Data/Text/Encoding/Fusion/Common.hs
--- a/Data/Text/Encoding/Fusion/Common.hs
+++ b/Data/Text/Encoding/Fusion/Common.hs
@@ -21,8 +21,7 @@
     -- * Restreaming
     -- Restreaming is the act of converting from one 'Stream'
     -- representation to another.
-      restreamUtf8
-    , restreamUtf16LE
+      restreamUtf16LE
     , restreamUtf16BE
     , restreamUtf32LE
     , restreamUtf32BE
@@ -34,31 +33,6 @@
 import Data.Text.UnsafeChar (ord)
 import Data.Text.UnsafeShift (shiftR)
 import Data.Word (Word8)
-import qualified Data.Text.Encoding.Utf8 as U8
-
--- | /O(n)/ Convert a Stream Char into a UTF-8 encoded Stream Word8.
-restreamUtf8 :: Stream Char -> Stream Word8
-restreamUtf8 (Stream next0 s0 len) = Stream next (RS0 s0) (len * 2)
-  where
-    next (RS0 s) = case next0 s of
-        Done              -> Done
-        Skip s'           -> Skip (RS0 s')
-        Yield x s'
-            | n <= 0x7F   -> Yield c  (RS0 s')
-            | n <= 0x07FF -> Yield a2 (RS1 s' b2)
-            | n <= 0xFFFF -> Yield a3 (RS2 s' b3 c3)
-            | otherwise   -> Yield a4 (RS3 s' b4 c4 d4)
-          where
-            n  = ord x
-            c  = fromIntegral n
-            (a2,b2) = U8.ord2 x
-            (a3,b3,c3) = U8.ord3 x
-            (a4,b4,c4,d4) = U8.ord4 x
-    next (RS1 s x2)       = Yield x2 (RS0 s)
-    next (RS2 s x2 x3)    = Yield x2 (RS1 s x3)
-    next (RS3 s x2 x3 x4) = Yield x2 (RS2 s x3 x4)
-    {-# INLINE next #-}
-{-# INLINE restreamUtf8 #-}
 
 restreamUtf16BE :: Stream Char -> Stream Word8
 restreamUtf16BE (Stream next0 s0 len) = Stream next (RS0 s0) (len * 2)
diff --git a/Data/Text/Fusion/Common.hs b/Data/Text/Fusion/Common.hs
--- a/Data/Text/Fusion/Common.hs
+++ b/Data/Text/Fusion/Common.hs
@@ -172,7 +172,7 @@
 
 -- | /O(n)/ Adds a character to the front of a Stream Char.
 cons :: Char -> Stream Char -> Stream Char
-cons w (Stream next0 s0 len) = Stream next (C1 s0) (len+1)
+cons !w (Stream next0 s0 len) = Stream next (C1 s0) (len+1)
     where
       next (C1 s) = Yield w (C0 s)
       next (C0 s) = case next0 s of
@@ -218,8 +218,12 @@
       loop_head !s = case next s of
                       Yield x _ -> x
                       Skip s'   -> loop_head s'
-                      Done      -> streamError "head" "Empty stream"
+                      Done      -> head_empty
 {-# INLINE [0] head #-}
+
+head_empty :: a
+head_empty = streamError "head" "Empty stream"
+{-# NOINLINE head_empty #-}
 
 -- | /O(1)/ Returns the first character and remainder of a 'Stream
 -- Char', or 'Nothing' if empty.  Subject to array fusion.
diff --git a/Data/Text/Fusion/Size.hs b/Data/Text/Fusion/Size.hs
--- a/Data/Text/Fusion/Size.hs
+++ b/Data/Text/Fusion/Size.hs
@@ -93,8 +93,8 @@
 
 mul :: Int -> Int -> Int
 mul m n
-    | m <= maxBound `div` n = m * n
-    | otherwise             = overflowError
+    | m <= maxBound `quot` n = m * n
+    | otherwise              = overflowError
 {-# INLINE mul #-}
 
 mulSize :: Size -> Size -> Size
diff --git a/Data/Text/IO.hs b/Data/Text/IO.hs
--- a/Data/Text/IO.hs
+++ b/Data/Text/IO.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE BangPatterns, CPP, RecordWildCards, ScopedTypeVariables #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.Text.IO
 -- Copyright   : (c) 2009, 2010 Bryan O'Sullivan,
@@ -26,6 +29,7 @@
     , appendFile
     -- * Operations on handles
     , hGetContents
+    , hGetChunk
     , hGetLine
     , hPutStr
     , hPutStrLn
@@ -97,6 +101,30 @@
 appendFile :: FilePath -> Text -> IO ()
 appendFile p = withFile p AppendMode . flip hPutStr
 
+catchError :: String -> Handle -> Handle__ -> IOError -> IO Text
+catchError caller h Handle__{..} err
+    | isEOFError err = do
+        buf <- readIORef haCharBuffer
+        return $ if isEmptyBuffer buf
+                 then T.empty
+                 else T.singleton '\r'
+    | otherwise = E.throwIO (augmentIOError err caller h)
+
+-- | /Experimental./ Read a single chunk of strict text from a
+-- 'Handle'. The size of the chunk depends on the amount of input
+-- currently buffered.
+--
+-- This function blocks only if there is no data available, and EOF
+-- has not yet been reached. Once EOF is reached, this function
+-- returns an empty string instead of throwing an exception.
+hGetChunk :: Handle -> IO Text
+hGetChunk h = wantReadableHandle "hGetChunk" h readSingleChunk
+ where
+  readSingleChunk hh@Handle__{..} = do
+    buf <- readIORef haCharBuffer
+    t <- readChunk hh buf `E.catch` catchError "hGetChunk" h hh
+    return (hh, t)
+
 -- | Read the remaining contents of a 'Handle' as a string.  The
 -- 'Handle' is closed once the contents have been read, or if an
 -- exception is thrown.
@@ -117,16 +145,9 @@
   wantReadableHandle "hGetContents" h readAll
  where
   readAll hh@Handle__{..} = do
-    let catchError e
-          | isEOFError e = do
-              buf <- readIORef haCharBuffer
-              return $ if isEmptyBuffer buf
-                       then T.empty
-                       else T.singleton '\r'
-          | otherwise = E.throwIO (augmentIOError e "hGetContents" h)
-        readChunks = do
+    let readChunks = do
           buf <- readIORef haCharBuffer
-          t <- readChunk hh buf `E.catch` catchError
+          t <- readChunk hh buf `E.catch` catchError "hGetContents" h hh
           if T.null t
             then return [t]
             else (t:) `fmap` readChunks
diff --git a/Data/Text/Internal.hs b/Data/Text/Internal.hs
--- a/Data/Text/Internal.hs
+++ b/Data/Text/Internal.hs
@@ -23,6 +23,7 @@
 module Data.Text.Internal
     (
     -- * Types
+    -- $internals
       Text(..)
     -- * Construction
     , text
@@ -47,9 +48,9 @@
 
 -- | A space efficient, packed, unboxed Unicode text type.
 data Text = Text
-    {-# UNPACK #-} !A.Array          -- payload
-    {-# UNPACK #-} !Int              -- offset
-    {-# UNPACK #-} !Int              -- length
+    {-# UNPACK #-} !A.Array          -- payload (Word16 elements)
+    {-# UNPACK #-} !Int              -- offset (units of Word16, not Char)
+    {-# UNPACK #-} !Int              -- length (units of Word16, not Char)
     deriving (Typeable)
 
 -- | Smart constructor.
@@ -90,7 +91,8 @@
 -- scalar values, but are unfortunately admitted as valid 'Char'
 -- values by Haskell.  They cannot be represented in a 'Text'.  This
 -- function remaps those code points to the Unicode replacement
--- character \"&#xfffd;\", and leaves other code points unchanged.
+-- character (U+FFFD, \'&#xfffd;\'), and leaves other code points
+-- unchanged.
 safe :: Char -> Char
 safe c
     | ord c .&. 0x1ff800 /= 0xd800 = c
@@ -101,3 +103,22 @@
 firstf :: (a -> c) -> Maybe (a,b) -> Maybe (c,b)
 firstf f (Just (a, b)) = Just (f a, b)
 firstf _  Nothing      = Nothing
+
+-- $internals
+--
+-- Internally, the 'Text' type is represented as an array of 'Word16'
+-- UTF-16 code units. The offset and length fields in the constructor
+-- are in these units, /not/ units of 'Char'.
+--
+-- Invariants that all functions must maintain:
+--
+-- * Since the 'Text' type uses UTF-16 internally, it cannot represent
+--   characters in the reserved surrogate code point range U+D800 to
+--   U+DFFF. To maintain this invariant, the 'safe' function maps
+--   'Char' values in this range to the replacement character (U+FFFD,
+--   \'&#xfffd;\').
+--
+-- * A leading (or \"high\") surrogate code unit (0xD800–0xDBFF) must
+--   always be followed by a trailing (or \"low\") surrogate code unit
+--   (0xDC00-0xDFFF). A trailing surrogate code unit must always be
+--   preceded by a leading surrogate code unit.
diff --git a/Data/Text/Lazy.hs b/Data/Text/Lazy.hs
--- a/Data/Text/Lazy.hs
+++ b/Data/Text/Lazy.hs
@@ -1,5 +1,8 @@
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 {-# LANGUAGE BangPatterns, MagicHash, CPP #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.Text.Lazy
 -- Copyright   : (c) 2009, 2010, 2012 Bryan O'Sullivan
@@ -190,7 +193,7 @@
 import Prelude (Char, Bool(..), Maybe(..), String,
                 Eq(..), Ord(..), Ordering(..), Read(..), Show(..),
                 (&&), (||), (+), (-), (.), ($), (++),
-                div, error, flip, fmap, fromIntegral, not, otherwise)
+                error, flip, fmap, fromIntegral, not, otherwise, quot)
 import qualified Prelude as P
 #if defined(HAVE_DEEPSEQ)
 import Control.DeepSeq (NFData(..))
@@ -625,7 +628,7 @@
     | otherwise = replicateChar l c `append` t `append` replicateChar r c
   where len = length t
         d   = k - len
-        r   = d `div` 2
+        r   = d `quot` 2
         l   = d - r
 {-# INLINE center #-}
 
diff --git a/Data/Text/Lazy/Builder.hs b/Data/Text/Lazy/Builder.hs
--- a/Data/Text/Lazy/Builder.hs
+++ b/Data/Text/Lazy/Builder.hs
@@ -3,7 +3,8 @@
 -----------------------------------------------------------------------------
 -- |
 -- Module      : Data.Text.Lazy.Builder
--- Copyright   : (c) 2010 Johan Tibell
+-- Copyright   : (c) 2013 Bryan O'Sullivan
+--               (c) 2010 Johan Tibell
 -- License     : BSD3-style (see LICENSE)
 --
 -- Maintainer  : Johan Tibell <johan.tibell@gmail.com>
@@ -15,7 +16,9 @@
 -- @fromLazyText@, which construct new builders, and 'mappend', which
 -- concatenates two builders.
 --
--- To get maximum performance when building lazy @Text@ values using a builder, associate @mappend@ calls to the right.  For example, prefer
+-- To get maximum performance when building lazy @Text@ values using a
+-- builder, associate @mappend@ calls to the right.  For example,
+-- prefer
 --
 -- > singleton 'a' `mappend` (singleton 'b' `mappend` singleton 'c')
 --
@@ -43,270 +46,4 @@
    , flush
    ) where
 
-import Control.Monad.ST (ST, runST)
-import Data.Bits ((.&.))
-import Data.Monoid (Monoid(..))
-import Data.Text.Internal (Text(..))
-import Data.Text.Lazy.Internal (smallChunkSize)
-import Data.Text.Unsafe (inlineInterleaveST)
-import Data.Text.UnsafeChar (ord, unsafeWrite)
-import Data.Text.UnsafeShift (shiftR)
-import Prelude hiding (map, putChar)
-
-import qualified Data.String as String
-import qualified Data.Text as S
-import qualified Data.Text.Array as A
-import qualified Data.Text.Lazy as L
-
-------------------------------------------------------------------------
-
--- | A @Builder@ is an efficient way to build lazy @Text@ values.
--- There are several functions for constructing builders, but only one
--- to inspect them: to extract any data, you have to turn them into
--- lazy @Text@ values using @toLazyText@.
---
--- Internally, a builder constructs a lazy @Text@ by filling arrays
--- piece by piece.  As each buffer is filled, it is \'popped\' off, to
--- become a new chunk of the resulting lazy @Text@.  All this is
--- hidden from the user of the @Builder@.
-newtype Builder = Builder {
-     -- Invariant (from Data.Text.Lazy):
-     --      The lists include no null Texts.
-     runBuilder :: forall s. (Buffer s -> ST s [S.Text])
-                -> Buffer s
-                -> ST s [S.Text]
-   }
-
-instance Monoid Builder where
-   mempty  = empty
-   {-# INLINE mempty #-}
-   mappend = append
-   {-# INLINE mappend #-}
-   mconcat = foldr mappend mempty
-   {-# INLINE mconcat #-}
-
-instance String.IsString Builder where
-    fromString = fromString
-    {-# INLINE fromString #-}
-
-instance Show Builder where
-    show = show . toLazyText
-
-instance Eq Builder where
-    a == b = toLazyText a == toLazyText b
-
-instance Ord Builder where
-    a <= b = toLazyText a <= toLazyText b
-
-------------------------------------------------------------------------
-
--- | /O(1)./ The empty @Builder@, satisfying
---
---  * @'toLazyText' 'empty' = 'L.empty'@
---
-empty :: Builder
-empty = Builder (\ k buf -> k buf)
-{-# INLINE empty #-}
-
--- | /O(1)./ A @Builder@ taking a single character, satisfying
---
---  * @'toLazyText' ('singleton' c) = 'L.singleton' c@
---
-singleton :: Char -> Builder
-singleton c = writeAtMost 2 $ \ marr o ->
-    if n < 0x10000
-    then A.unsafeWrite marr o (fromIntegral n) >> return 1
-    else do
-        A.unsafeWrite marr o lo
-        A.unsafeWrite marr (o+1) hi
-        return 2
-  where n = ord c
-        m = n - 0x10000
-        lo = fromIntegral $ (m `shiftR` 10) + 0xD800
-        hi = fromIntegral $ (m .&. 0x3FF) + 0xDC00
-{-# INLINE singleton #-}
-
-------------------------------------------------------------------------
-
--- | /O(1)./ The concatenation of two builders, an associative
--- operation with identity 'empty', satisfying
---
---  * @'toLazyText' ('append' x y) = 'L.append' ('toLazyText' x) ('toLazyText' y)@
---
-append :: Builder -> Builder -> Builder
-append (Builder f) (Builder g) = Builder (f . g)
-{-# INLINE [0] append #-}
-
--- TODO: Experiment to find the right threshold.
-copyLimit :: Int
-copyLimit = 128
-
--- This function attempts to merge small @Text@ values instead of
--- treating each value as its own chunk.  We may not always want this.
-
--- | /O(1)./ A @Builder@ taking a 'S.Text', satisfying
---
---  * @'toLazyText' ('fromText' t) = 'L.fromChunks' [t]@
---
-fromText :: S.Text -> Builder
-fromText t@(Text arr off l)
-    | S.null t       = empty
-    | l <= copyLimit = writeN l $ \marr o -> A.copyI marr o arr off (l+o)
-    | otherwise      = flush `append` mapBuilder (t :)
-{-# INLINE [1] fromText #-}
-
-{-# RULES
-"fromText/pack" forall s .
-        fromText (S.pack s) = fromString s
- #-}
-
--- | /O(1)./ A Builder taking a @String@, satisfying
---
---  * @'toLazyText' ('fromString' s) = 'L.fromChunks' [S.pack s]@
---
-fromString :: String -> Builder
-fromString str = Builder $ \k (Buffer p0 o0 u0 l0) ->
-    let loop !marr !o !u !l [] = k (Buffer marr o u l)
-        loop marr o u l s@(c:cs)
-            | l <= 1 = do
-                arr <- A.unsafeFreeze marr
-                let !t = Text arr o u
-                marr' <- A.new chunkSize
-                ts <- inlineInterleaveST (loop marr' 0 0 chunkSize s)
-                return $ t : ts
-            | otherwise = do
-                n <- unsafeWrite marr (o+u) c
-                loop marr o (u+n) (l-n) cs
-    in loop p0 o0 u0 l0 str
-  where
-    chunkSize = smallChunkSize
-{-# INLINE fromString #-}
-
--- | /O(1)./ A @Builder@ taking a lazy @Text@, satisfying
---
---  * @'toLazyText' ('fromLazyText' t) = t@
---
-fromLazyText :: L.Text -> Builder
-fromLazyText ts = flush `append` mapBuilder (L.toChunks ts ++)
-{-# INLINE fromLazyText #-}
-
-------------------------------------------------------------------------
-
--- Our internal buffer type
-data Buffer s = Buffer {-# UNPACK #-} !(A.MArray s)
-                       {-# UNPACK #-} !Int  -- offset
-                       {-# UNPACK #-} !Int  -- used units
-                       {-# UNPACK #-} !Int  -- length left
-
-------------------------------------------------------------------------
-
--- | /O(n)./ Extract a lazy @Text@ from a @Builder@ with a default
--- buffer size.  The construction work takes place if and when the
--- relevant part of the lazy @Text@ is demanded.
-toLazyText :: Builder -> L.Text
-toLazyText = toLazyTextWith smallChunkSize
-
--- | /O(n)./ Extract a lazy @Text@ from a @Builder@, using the given
--- size for the initial buffer.  The construction work takes place if
--- and when the relevant part of the lazy @Text@ is demanded.
---
--- If the initial buffer is too small to hold all data, subsequent
--- buffers will be the default buffer size.
-toLazyTextWith :: Int -> Builder -> L.Text
-toLazyTextWith chunkSize m = L.fromChunks (runST $
-  newBuffer chunkSize >>= runBuilder (m `append` flush) (const (return [])))
-
--- | /O(1)./ Pop the strict @Text@ we have constructed so far, if any,
--- yielding a new chunk in the result lazy @Text@.
-flush :: Builder
-flush = Builder $ \ k buf@(Buffer p o u l) ->
-    if u == 0
-    then k buf
-    else do arr <- A.unsafeFreeze p
-            let !b = Buffer p (o+u) 0 l
-                !t = Text arr o u
-            ts <- inlineInterleaveST (k b)
-            return $! t : ts
-
-------------------------------------------------------------------------
-
--- | Sequence an ST operation on the buffer
-withBuffer :: (forall s. Buffer s -> ST s (Buffer s)) -> Builder
-withBuffer f = Builder $ \k buf -> f buf >>= k
-{-# INLINE withBuffer #-}
-
--- | Get the size of the buffer
-withSize :: (Int -> Builder) -> Builder
-withSize f = Builder $ \ k buf@(Buffer _ _ _ l) ->
-    runBuilder (f l) k buf
-{-# INLINE withSize #-}
-
--- | Map the resulting list of texts.
-mapBuilder :: ([S.Text] -> [S.Text]) -> Builder
-mapBuilder f = Builder (fmap f .)
-
-------------------------------------------------------------------------
-
--- | Ensure that there are at least @n@ many elements available.
-ensureFree :: Int -> Builder
-ensureFree !n = withSize $ \ l ->
-    if n <= l
-    then empty
-    else flush `append'` withBuffer (const (newBuffer (max n smallChunkSize)))
-{-# INLINE [0] ensureFree #-}
-
-writeAtMost :: Int -> (forall s. A.MArray s -> Int -> ST s Int) -> Builder
-writeAtMost n f = ensureFree n `append'` withBuffer (writeBuffer f)
-{-# INLINE [0] writeAtMost #-}
-
--- | Ensure that @n@ many elements are available, and then use @f@ to
--- write some elements into the memory.
-writeN :: Int -> (forall s. A.MArray s -> Int -> ST s ()) -> Builder
-writeN n f = writeAtMost n (\ p o -> f p o >> return n)
-{-# INLINE writeN #-}
-
-writeBuffer :: (A.MArray s -> Int -> ST s Int) -> Buffer s -> ST s (Buffer s)
-writeBuffer f (Buffer p o u l) = do
-    n <- f p (o+u)
-    return $! Buffer p o (u+n) (l-n)
-{-# INLINE writeBuffer #-}
-
-newBuffer :: Int -> ST s (Buffer s)
-newBuffer size = do
-    arr <- A.new size
-    return $! Buffer arr 0 0 size
-{-# INLINE newBuffer #-}
-
-------------------------------------------------------------------------
--- Some nice rules for Builder
-
--- This function makes GHC understand that 'writeN' and 'ensureFree'
--- are *not* recursive in the precense of the rewrite rules below.
--- This is not needed with GHC 7+.
-append' :: Builder -> Builder -> Builder
-append' (Builder f) (Builder g) = Builder (f . g)
-{-# INLINE append' #-}
-
-{-# RULES
-
-"append/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
-                           (g::forall s. A.MArray s -> Int -> ST s Int) ws.
-    append (writeAtMost a f) (append (writeAtMost b g) ws) =
-        append (writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
-                                    g marr (o+n) >>= \ m ->
-                                    let s = n+m in s `seq` return s)) ws
-
-"writeAtMost/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
-                           (g::forall s. A.MArray s -> Int -> ST s Int).
-    append (writeAtMost a f) (writeAtMost b g) =
-        writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
-                            g marr (o+n) >>= \ m ->
-                            let s = n+m in s `seq` return s)
-
-"ensureFree/ensureFree" forall a b .
-    append (ensureFree a) (ensureFree b) = ensureFree (max a b)
-
-"flush/flush"
-    append flush flush = flush
-
- #-}
+import Data.Text.Lazy.Builder.Internal
diff --git a/Data/Text/Lazy/Builder/Int.hs b/Data/Text/Lazy/Builder/Int.hs
--- a/Data/Text/Lazy/Builder/Int.hs
+++ b/Data/Text/Lazy/Builder/Int.hs
@@ -1,7 +1,8 @@
-{-# LANGUAGE BangPatterns, CPP, MagicHash, UnboxedTuples #-}
+{-# LANGUAGE BangPatterns, CPP, MagicHash, RankNTypes, UnboxedTuples #-}
 
 -- Module:      Data.Text.Lazy.Builder.Int
--- Copyright:   (c) 2011 MailRank, Inc.
+-- Copyright:   (c) 2013 Bryan O'Sullivan
+--              (c) 2011 MailRank, Inc.
 -- License:     BSD3
 -- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
 -- Stability:   experimental
@@ -17,12 +18,16 @@
 
 import Data.Int (Int8, Int16, Int32, Int64)
 import Data.Monoid (mempty)
+import qualified Data.ByteString.Unsafe as B
 import Data.Text.Lazy.Builder.Functions ((<>), i2d)
-import Data.Text.Lazy.Builder
+import Data.Text.Lazy.Builder.Internal
+import Data.Text.Lazy.Builder.Int.Digits (digits)
+import Data.Text.Array
 import Data.Word (Word, Word8, Word16, Word32, Word64)
 import GHC.Base (quotInt, remInt)
 import GHC.Num (quotRemInteger)
 import GHC.Types (Int(..))
+import Control.Monad.ST
 
 #ifdef  __GLASGOW_HASKELL__
 # if __GLASGOW_HASKELL__ < 611
@@ -54,12 +59,7 @@
 {-# RULES "decimal/Word32" decimal = positive :: Word32 -> Builder #-}
 {-# RULES "decimal/Word64" decimal = positive :: Word64 -> Builder #-}
 {-# RULES "decimal/Integer" decimal = integer 10 :: Integer -> Builder #-}
-decimal i
-  | i < 0     = singleton '-' <>
-                if i <= -128
-                then positive (-(i `quot` 10)) <> digit (-(i `rem` 10))
-                else positive (-i)
-  | otherwise = positive i
+decimal i = decimal' (<= -128) i
 
 boundedDecimal :: (Integral a, Bounded a) => a -> Builder
 {-# SPECIALIZE boundedDecimal :: Int -> Builder #-}
@@ -67,11 +67,23 @@
 {-# SPECIALIZE boundedDecimal :: Int16 -> Builder #-}
 {-# SPECIALIZE boundedDecimal :: Int32 -> Builder #-}
 {-# SPECIALIZE boundedDecimal :: Int64 -> Builder #-}
-boundedDecimal i
-    | i < 0     = singleton '-' <>
-                  if i == minBound
-                  then positive (-(i `quot` 10)) <> digit (-(i `rem` 10))
-                  else positive (-i)
+boundedDecimal i = decimal' (== minBound) i
+
+decimal' :: (Integral a) => (a -> Bool) -> a -> Builder
+{-# INLINE decimal' #-}
+decimal' p i
+    | i < 0 = if p i
+              then let (q, r) = i `quotRem` 10
+                       qq = -q
+                       !n = countDigits qq
+                   in writeN (n + 2) $ \marr off -> do
+                       unsafeWrite marr off minus
+                       posDecimal marr (off+1) n qq
+                       unsafeWrite marr (off+n+1) (i2w (-r))
+              else let j = -i
+                       !n = countDigits j
+                   in writeN (n + 1) $ \marr off ->
+                       unsafeWrite marr off minus >> posDecimal marr (off+1) n j
     | otherwise = positive i
 
 positive :: (Integral a) => a -> Builder
@@ -85,10 +97,58 @@
 {-# SPECIALIZE positive :: Word16 -> Builder #-}
 {-# SPECIALIZE positive :: Word32 -> Builder #-}
 {-# SPECIALIZE positive :: Word64 -> Builder #-}
-positive = go
-  where go n | n < 10    = digit n
-             | otherwise = go (n `quot` 10) <> digit (n `rem` 10)
+positive i
+    | i < 10    = writeN 1 $ \marr off -> unsafeWrite marr off (i2w i)
+    | otherwise = let !n = countDigits i
+                  in writeN n $ \marr off -> posDecimal marr off n i
 
+posDecimal :: (Integral a) =>
+              forall s. MArray s -> Int -> Int -> a -> ST s ()
+{-# INLINE posDecimal #-}
+posDecimal marr off0 ds v0 = go (off0 + ds - 1) v0
+  where go off v
+           | v >= 100 = do
+               let (q, r) = v `quotRem` 100
+               write2 off r
+               go (off - 2) q
+           | v < 10    = unsafeWrite marr off (i2w v)
+           | otherwise = write2 off v
+        write2 off i0 = do
+          let i = fromIntegral i0; j = i + i
+          unsafeWrite marr off $ get (j + 1)
+          unsafeWrite marr (off - 1) $ get j
+        get = fromIntegral . B.unsafeIndex digits
+
+minus, zero :: Word16
+{-# INLINE minus #-}
+{-# INLINE zero #-}
+minus = 45
+zero = 48
+
+i2w :: (Integral a) => a -> Word16
+{-# INLINE i2w #-}
+i2w v = zero + fromIntegral v
+
+countDigits :: (Integral a) => a -> Int
+{-# INLINE countDigits #-}
+countDigits v0 = go 1 (fromIntegral v0 :: Word64)
+  where go !k v
+           | v < 10    = k
+           | v < 100   = k + 1
+           | v < 1000  = k + 2
+           | v < 1000000000000 =
+               k + if v < 100000000
+                   then if v < 1000000
+                        then if v < 10000
+                             then 3
+                             else 4 + fin v 100000
+                        else 6 + fin v 10000000
+                   else if v < 10000000000
+                        then 8 + fin v 1000000000
+                        else 10 + fin v 100000000000
+           | otherwise = go (k + 12) (v `quot` 1000000000000)
+        fin v n = if v >= n then 1 else 0
+
 hexadecimal :: Integral a => a -> Builder
 {-# SPECIALIZE hexadecimal :: Int -> Builder #-}
 {-# SPECIALIZE hexadecimal :: Int8 -> Builder #-}
@@ -100,19 +160,23 @@
 {-# SPECIALIZE hexadecimal :: Word16 -> Builder #-}
 {-# SPECIALIZE hexadecimal :: Word32 -> Builder #-}
 {-# SPECIALIZE hexadecimal :: Word64 -> Builder #-}
-{-# RULES "hexadecimal/Integer" hexadecimal = integer 16 :: Integer -> Builder #-}
+{-# RULES "hexadecimal/Integer"
+    hexadecimal = hexInteger :: Integer -> Builder #-}
 hexadecimal i
-    | i < 0     = error msg
+    | i < 0     = error hexErrMsg
     | otherwise = go i
   where
     go n | n < 16    = hexDigit n
          | otherwise = go (n `quot` 16) <> hexDigit (n `rem` 16)
-    msg = "Data.Text.Lazy.Builder.Int.hexadecimal: applied to negative number"
 
-digit :: Integral a => a -> Builder
-digit n = singleton $! i2d (fromIntegral n)
-{-# INLINE digit #-}
+hexInteger :: Integer -> Builder
+hexInteger i
+    | i < 0     = error hexErrMsg
+    | otherwise = integer 16 i
 
+hexErrMsg :: String
+hexErrMsg = "Data.Text.Lazy.Builder.Int.hexadecimal: applied to negative number"
+
 hexDigit :: Integral a => a -> Builder
 hexDigit n
     | n <= 9    = singleton $! i2d (fromIntegral n)
@@ -184,7 +248,7 @@
     pblock = loop maxDigits
       where
         loop !d !n
-            | d == 1    = digit n
-            | otherwise = loop (d-1) q <> digit r
+            | d == 1    = hexDigit n
+            | otherwise = loop (d-1) q <> hexDigit r
             where q = n `quotInt` base
                   r = n `remInt` base
diff --git a/Data/Text/Lazy/Builder/Int/Digits.hs b/Data/Text/Lazy/Builder/Int/Digits.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Lazy/Builder/Int/Digits.hs
@@ -0,0 +1,22 @@
+{-# LANGUAGE OverloadedStrings #-}
+
+-- Module:      Data.Text.Lazy.Builder.Int.Digits
+-- Copyright:   (c) 2013 Bryan O'Sullivan
+-- License:     BSD3
+-- Maintainer:  Bryan O'Sullivan <bos@serpentine.com>
+-- Stability:   experimental
+-- Portability: portable
+--
+-- This module exists because the C preprocessor does things that we
+-- shall not speak of when confronted with Haskell multiline strings.
+
+module Data.Text.Lazy.Builder.Int.Digits (digits) where
+
+import Data.ByteString.Char8 (ByteString)
+
+digits :: ByteString
+digits = "0001020304050607080910111213141516171819\
+         \2021222324252627282930313233343536373839\
+         \4041424344454647484950515253545556575859\
+         \6061626364656667686970717273747576777879\
+         \8081828384858687888990919293949596979899"
diff --git a/Data/Text/Lazy/Builder/Internal.hs b/Data/Text/Lazy/Builder/Internal.hs
new file mode 100644
--- /dev/null
+++ b/Data/Text/Lazy/Builder/Internal.hs
@@ -0,0 +1,321 @@
+{-# LANGUAGE BangPatterns, CPP, Rank2Types #-}
+
+-----------------------------------------------------------------------------
+-- |
+-- Module      : Data.Text.Lazy.Builder.Internal
+-- Copyright   : (c) 2013 Bryan O'Sullivan
+--               (c) 2010 Johan Tibell
+-- License     : BSD3-style (see LICENSE)
+--
+-- Maintainer  : Johan Tibell <johan.tibell@gmail.com>
+-- Stability   : experimental
+-- Portability : portable to Hugs and GHC
+--
+-- Efficient construction of lazy @Text@ values.  The principal
+-- operations on a @Builder@ are @singleton@, @fromText@, and
+-- @fromLazyText@, which construct new builders, and 'mappend', which
+-- concatenates two builders.
+--
+-- To get maximum performance when building lazy @Text@ values using a
+-- builder, associate @mappend@ calls to the right.  For example,
+-- prefer
+--
+-- > singleton 'a' `mappend` (singleton 'b' `mappend` singleton 'c')
+--
+-- to
+--
+-- > singleton 'a' `mappend` singleton 'b' `mappend` singleton 'c'
+--
+-- as the latter associates @mappend@ to the left.
+--
+-----------------------------------------------------------------------------
+
+module Data.Text.Lazy.Builder.Internal
+   ( -- * Public API
+     -- ** The Builder type
+     Builder
+   , toLazyText
+   , toLazyTextWith
+
+     -- ** Constructing Builders
+   , singleton
+   , fromText
+   , fromLazyText
+   , fromString
+
+     -- ** Flushing the buffer state
+   , flush
+
+     -- * Internal functions
+   , append'
+   , ensureFree
+   , writeN
+   ) where
+
+import Control.Monad.ST (ST, runST)
+import Data.Bits ((.&.))
+import Data.Monoid (Monoid(..))
+import Data.Text.Internal (Text(..))
+import Data.Text.Lazy.Internal (smallChunkSize)
+import Data.Text.Unsafe (inlineInterleaveST)
+import Data.Text.UnsafeChar (ord, unsafeWrite)
+import Data.Text.UnsafeShift (shiftR)
+import Prelude hiding (map, putChar)
+
+import qualified Data.String as String
+import qualified Data.Text as S
+import qualified Data.Text.Array as A
+import qualified Data.Text.Lazy as L
+
+------------------------------------------------------------------------
+
+-- | A @Builder@ is an efficient way to build lazy @Text@ values.
+-- There are several functions for constructing builders, but only one
+-- to inspect them: to extract any data, you have to turn them into
+-- lazy @Text@ values using @toLazyText@.
+--
+-- Internally, a builder constructs a lazy @Text@ by filling arrays
+-- piece by piece.  As each buffer is filled, it is \'popped\' off, to
+-- become a new chunk of the resulting lazy @Text@.  All this is
+-- hidden from the user of the @Builder@.
+newtype Builder = Builder {
+     -- Invariant (from Data.Text.Lazy):
+     --      The lists include no null Texts.
+     runBuilder :: forall s. (Buffer s -> ST s [S.Text])
+                -> Buffer s
+                -> ST s [S.Text]
+   }
+
+instance Monoid Builder where
+   mempty  = empty
+   {-# INLINE mempty #-}
+   mappend = append
+   {-# INLINE mappend #-}
+   mconcat = foldr mappend mempty
+   {-# INLINE mconcat #-}
+
+instance String.IsString Builder where
+    fromString = fromString
+    {-# INLINE fromString #-}
+
+instance Show Builder where
+    show = show . toLazyText
+
+instance Eq Builder where
+    a == b = toLazyText a == toLazyText b
+
+instance Ord Builder where
+    a <= b = toLazyText a <= toLazyText b
+
+------------------------------------------------------------------------
+
+-- | /O(1)./ The empty @Builder@, satisfying
+--
+--  * @'toLazyText' 'empty' = 'L.empty'@
+--
+empty :: Builder
+empty = Builder (\ k buf -> k buf)
+{-# INLINE empty #-}
+
+-- | /O(1)./ A @Builder@ taking a single character, satisfying
+--
+--  * @'toLazyText' ('singleton' c) = 'L.singleton' c@
+--
+singleton :: Char -> Builder
+singleton c = writeAtMost 2 $ \ marr o ->
+    if n < 0x10000
+    then A.unsafeWrite marr o (fromIntegral n) >> return 1
+    else do
+        A.unsafeWrite marr o lo
+        A.unsafeWrite marr (o+1) hi
+        return 2
+  where n = ord c
+        m = n - 0x10000
+        lo = fromIntegral $ (m `shiftR` 10) + 0xD800
+        hi = fromIntegral $ (m .&. 0x3FF) + 0xDC00
+{-# INLINE singleton #-}
+
+------------------------------------------------------------------------
+
+-- | /O(1)./ The concatenation of two builders, an associative
+-- operation with identity 'empty', satisfying
+--
+--  * @'toLazyText' ('append' x y) = 'L.append' ('toLazyText' x) ('toLazyText' y)@
+--
+append :: Builder -> Builder -> Builder
+append (Builder f) (Builder g) = Builder (f . g)
+{-# INLINE [0] append #-}
+
+-- TODO: Experiment to find the right threshold.
+copyLimit :: Int
+copyLimit = 128
+
+-- This function attempts to merge small @Text@ values instead of
+-- treating each value as its own chunk.  We may not always want this.
+
+-- | /O(1)./ A @Builder@ taking a 'S.Text', satisfying
+--
+--  * @'toLazyText' ('fromText' t) = 'L.fromChunks' [t]@
+--
+fromText :: S.Text -> Builder
+fromText t@(Text arr off l)
+    | S.null t       = empty
+    | l <= copyLimit = writeN l $ \marr o -> A.copyI marr o arr off (l+o)
+    | otherwise      = flush `append` mapBuilder (t :)
+{-# INLINE [1] fromText #-}
+
+{-# RULES
+"fromText/pack" forall s .
+        fromText (S.pack s) = fromString s
+ #-}
+
+-- | /O(1)./ A Builder taking a @String@, satisfying
+--
+--  * @'toLazyText' ('fromString' s) = 'L.fromChunks' [S.pack s]@
+--
+fromString :: String -> Builder
+fromString str = Builder $ \k (Buffer p0 o0 u0 l0) ->
+    let loop !marr !o !u !l [] = k (Buffer marr o u l)
+        loop marr o u l s@(c:cs)
+            | l <= 1 = do
+                arr <- A.unsafeFreeze marr
+                let !t = Text arr o u
+                marr' <- A.new chunkSize
+                ts <- inlineInterleaveST (loop marr' 0 0 chunkSize s)
+                return $ t : ts
+            | otherwise = do
+                n <- unsafeWrite marr (o+u) c
+                loop marr o (u+n) (l-n) cs
+    in loop p0 o0 u0 l0 str
+  where
+    chunkSize = smallChunkSize
+{-# INLINE fromString #-}
+
+-- | /O(1)./ A @Builder@ taking a lazy @Text@, satisfying
+--
+--  * @'toLazyText' ('fromLazyText' t) = t@
+--
+fromLazyText :: L.Text -> Builder
+fromLazyText ts = flush `append` mapBuilder (L.toChunks ts ++)
+{-# INLINE fromLazyText #-}
+
+------------------------------------------------------------------------
+
+-- Our internal buffer type
+data Buffer s = Buffer {-# UNPACK #-} !(A.MArray s)
+                       {-# UNPACK #-} !Int  -- offset
+                       {-# UNPACK #-} !Int  -- used units
+                       {-# UNPACK #-} !Int  -- length left
+
+------------------------------------------------------------------------
+
+-- | /O(n)./ Extract a lazy @Text@ from a @Builder@ with a default
+-- buffer size.  The construction work takes place if and when the
+-- relevant part of the lazy @Text@ is demanded.
+toLazyText :: Builder -> L.Text
+toLazyText = toLazyTextWith smallChunkSize
+
+-- | /O(n)./ Extract a lazy @Text@ from a @Builder@, using the given
+-- size for the initial buffer.  The construction work takes place if
+-- and when the relevant part of the lazy @Text@ is demanded.
+--
+-- If the initial buffer is too small to hold all data, subsequent
+-- buffers will be the default buffer size.
+toLazyTextWith :: Int -> Builder -> L.Text
+toLazyTextWith chunkSize m = L.fromChunks (runST $
+  newBuffer chunkSize >>= runBuilder (m `append` flush) (const (return [])))
+
+-- | /O(1)./ Pop the strict @Text@ we have constructed so far, if any,
+-- yielding a new chunk in the result lazy @Text@.
+flush :: Builder
+flush = Builder $ \ k buf@(Buffer p o u l) ->
+    if u == 0
+    then k buf
+    else do arr <- A.unsafeFreeze p
+            let !b = Buffer p (o+u) 0 l
+                !t = Text arr o u
+            ts <- inlineInterleaveST (k b)
+            return $! t : ts
+
+------------------------------------------------------------------------
+
+-- | Sequence an ST operation on the buffer
+withBuffer :: (forall s. Buffer s -> ST s (Buffer s)) -> Builder
+withBuffer f = Builder $ \k buf -> f buf >>= k
+{-# INLINE withBuffer #-}
+
+-- | Get the size of the buffer
+withSize :: (Int -> Builder) -> Builder
+withSize f = Builder $ \ k buf@(Buffer _ _ _ l) ->
+    runBuilder (f l) k buf
+{-# INLINE withSize #-}
+
+-- | Map the resulting list of texts.
+mapBuilder :: ([S.Text] -> [S.Text]) -> Builder
+mapBuilder f = Builder (fmap f .)
+
+------------------------------------------------------------------------
+
+-- | Ensure that there are at least @n@ many elements available.
+ensureFree :: Int -> Builder
+ensureFree !n = withSize $ \ l ->
+    if n <= l
+    then empty
+    else flush `append'` withBuffer (const (newBuffer (max n smallChunkSize)))
+{-# INLINE [0] ensureFree #-}
+
+writeAtMost :: Int -> (forall s. A.MArray s -> Int -> ST s Int) -> Builder
+writeAtMost n f = ensureFree n `append'` withBuffer (writeBuffer f)
+{-# INLINE [0] writeAtMost #-}
+
+-- | Ensure that @n@ many elements are available, and then use @f@ to
+-- write some elements into the memory.
+writeN :: Int -> (forall s. A.MArray s -> Int -> ST s ()) -> Builder
+writeN n f = writeAtMost n (\ p o -> f p o >> return n)
+{-# INLINE writeN #-}
+
+writeBuffer :: (A.MArray s -> Int -> ST s Int) -> Buffer s -> ST s (Buffer s)
+writeBuffer f (Buffer p o u l) = do
+    n <- f p (o+u)
+    return $! Buffer p o (u+n) (l-n)
+{-# INLINE writeBuffer #-}
+
+newBuffer :: Int -> ST s (Buffer s)
+newBuffer size = do
+    arr <- A.new size
+    return $! Buffer arr 0 0 size
+{-# INLINE newBuffer #-}
+
+------------------------------------------------------------------------
+-- Some nice rules for Builder
+
+-- This function makes GHC understand that 'writeN' and 'ensureFree'
+-- are *not* recursive in the precense of the rewrite rules below.
+-- This is not needed with GHC 7+.
+append' :: Builder -> Builder -> Builder
+append' (Builder f) (Builder g) = Builder (f . g)
+{-# INLINE append' #-}
+
+{-# RULES
+
+"append/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
+                           (g::forall s. A.MArray s -> Int -> ST s Int) ws.
+    append (writeAtMost a f) (append (writeAtMost b g) ws) =
+        append (writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
+                                    g marr (o+n) >>= \ m ->
+                                    let s = n+m in s `seq` return s)) ws
+
+"writeAtMost/writeAtMost" forall a b (f::forall s. A.MArray s -> Int -> ST s Int)
+                           (g::forall s. A.MArray s -> Int -> ST s Int).
+    append (writeAtMost a f) (writeAtMost b g) =
+        writeAtMost (a+b) (\marr o -> f marr o >>= \ n ->
+                            g marr (o+n) >>= \ m ->
+                            let s = n+m in s `seq` return s)
+
+"ensureFree/ensureFree" forall a b .
+    append (ensureFree a) (ensureFree b) = ensureFree (max a b)
+
+"flush/flush"
+    append flush flush = flush
+
+ #-}
diff --git a/Data/Text/Lazy/Encoding.hs b/Data/Text/Lazy/Encoding.hs
--- a/Data/Text/Lazy/Encoding.hs
+++ b/Data/Text/Lazy/Encoding.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE BangPatterns,CPP #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.Text.Lazy.Encoding
 -- Copyright   : (c) 2009, 2010 Bryan O'Sullivan
@@ -20,6 +23,7 @@
     -- * Decoding ByteStrings to Text
     -- $strict
       decodeASCII
+    , decodeLatin1
     , decodeUtf8
     , decodeUtf16LE
     , decodeUtf16BE
@@ -48,7 +52,6 @@
 import Data.Bits ((.&.))
 import Data.Text.Encoding.Error (OnDecodeError, UnicodeException, strictDecode)
 import Data.Text.Lazy.Internal (Text(..), chunk, empty, foldrChunks)
-import System.IO.Unsafe (unsafePerformIO)
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Lazy as B
 import qualified Data.ByteString.Lazy.Internal as B
@@ -57,6 +60,7 @@
 import qualified Data.Text.Encoding as TE
 import qualified Data.Text.Lazy.Encoding.Fusion as E
 import qualified Data.Text.Lazy.Fusion as F
+import Data.Text.Unsafe (unsafeDupablePerformIO)
 
 -- $strict
 --
@@ -74,11 +78,15 @@
 -- | /Deprecated/.  Decode a 'ByteString' containing 7-bit ASCII
 -- encoded text.
 --
--- This function is deprecated.  Use 'decodeUtf8' instead.
+-- This function is deprecated.  Use 'decodeLatin1' instead.
 decodeASCII :: B.ByteString -> Text
 decodeASCII = decodeUtf8
 {-# DEPRECATED decodeASCII "Use decodeUtf8 instead" #-}
 
+-- | Decode a 'ByteString' containing Latin-1 (aka ISO-8859-1) encoded text.
+decodeLatin1 :: B.ByteString -> Text
+decodeLatin1 = foldr (chunk . TE.decodeLatin1) empty . B.toChunks
+
 -- | Decode a 'ByteString' containing UTF-8 encoded text.
 decodeUtf8With :: OnDecodeError -> B.ByteString -> Text
 decodeUtf8With onErr bs0 = fast bs0
@@ -141,7 +149,7 @@
 -- input before it can return a result.  If you need lazy (streaming)
 -- decoding, use 'decodeUtf8With' in lenient mode.
 decodeUtf8' :: B.ByteString -> Either UnicodeException Text
-decodeUtf8' bs = unsafePerformIO $ do
+decodeUtf8' bs = unsafeDupablePerformIO $ do
                    let t = decodeUtf8 bs
                    try (evaluate (rnf t `seq` t))
   where
diff --git a/Data/Text/Lazy/Encoding/Fusion.hs b/Data/Text/Lazy/Encoding/Fusion.hs
--- a/Data/Text/Lazy/Encoding/Fusion.hs
+++ b/Data/Text/Lazy/Encoding/Fusion.hs
@@ -42,7 +42,7 @@
 import qualified Data.Text.Encoding.Utf8 as U8
 import qualified Data.Text.Encoding.Utf16 as U16
 import qualified Data.Text.Encoding.Utf32 as U32
-import System.IO.Unsafe (unsafePerformIO)
+import Data.Text.Unsafe (unsafeDupablePerformIO)
 import Foreign.ForeignPtr (withForeignPtr, ForeignPtr)
 import Foreign.Storable (pokeByteOff)
 import Data.ByteString.Internal (mallocByteString, memcpy)
@@ -272,7 +272,7 @@
 -- | /O(n)/ Convert a 'Stream' 'Word8' to a lazy 'ByteString'.
 unstreamChunks :: Int -> Stream Word8 -> ByteString
 unstreamChunks chunkSize (Stream next s0 len0) = chunk s0 (upperBound 4 len0)
-  where chunk s1 len1 = unsafePerformIO $ do
+  where chunk s1 len1 = unsafeDupablePerformIO $ do
           let len = max 4 (min len1 chunkSize)
           mallocByteString len >>= loop len 0 s1
           where
diff --git a/Data/Text/Lazy/Fusion.hs b/Data/Text/Lazy/Fusion.hs
--- a/Data/Text/Lazy/Fusion.hs
+++ b/Data/Text/Lazy/Fusion.hs
@@ -24,6 +24,7 @@
 
 import Prelude hiding (length)
 import qualified Data.Text.Fusion.Common as S
+import Control.Monad.ST (runST)
 import Data.Text.Fusion.Internal
 import Data.Text.Fusion.Size (isEmpty, unknownSize)
 import Data.Text.Lazy.Internal
@@ -47,38 +48,39 @@
         where Iter c d = iter t i
 {-# INLINE [0] stream #-}
 
-data UC s = UC s {-# UNPACK #-} !Int
-
 -- | /O(n)/ Convert a 'Stream Char' into a 'Text', using the given
 -- chunk size.
 unstreamChunks :: Int -> Stream Char -> Text
-unstreamChunks chunkSize (Stream next s0 len0)
+unstreamChunks !chunkSize (Stream next s0 len0)
   | isEmpty len0 = Empty
   | otherwise    = outer s0
   where
-    outer s = {-# SCC "unstreamChunks/outer" #-}
-              case next s of
+    outer so = {-# SCC "unstreamChunks/outer" #-}
+              case next so of
                 Done       -> Empty
                 Skip s'    -> outer s'
-                Yield x s' -> I.Text arr 0 len `chunk` outer s''
-                  where (arr, UC s'' len) = A.run2 fill
-                        fill = do a <- A.new unknownLength
-                                  unsafeWrite a 0 x >>= inner a unknownLength s'
-                        unknownLength = 4
-    inner marr len s !i
-        | i + 1 >= chunkSize = return (marr, UC s i)
-        | i + 1 >= len       = {-# SCC "unstreamChunks/resize" #-} do
-            let newLen = min (len `shiftL` 1) chunkSize
-            marr' <- A.new newLen
-            A.copyM marr' 0 marr 0 len
-            inner marr' newLen s i
-        | otherwise =
-            {-# SCC "unstreamChunks/inner" #-}
-            case next s of
-              Done        -> return (marr, UC s i)
-              Skip s'     -> inner marr len s' i
-              Yield x s'  -> do d <- unsafeWrite marr i x
-                                inner marr len s' (i+d)
+                Yield x s' -> runST $ do
+                                a <- A.new unknownLength
+                                unsafeWrite a 0 x >>= inner a unknownLength s'
+                    where unknownLength = 4
+      where
+        inner marr !len s !i
+            | i + 1 >= chunkSize = finish marr i s
+            | i + 1 >= len       = {-# SCC "unstreamChunks/resize" #-} do
+                let newLen = min (len `shiftL` 1) chunkSize
+                marr' <- A.new newLen
+                A.copyM marr' 0 marr 0 len
+                inner marr' newLen s i
+            | otherwise =
+                {-# SCC "unstreamChunks/inner" #-}
+                case next s of
+                  Done        -> finish marr i s
+                  Skip s'     -> inner marr len s' i
+                  Yield x s'  -> do d <- unsafeWrite marr i x
+                                    inner marr len s' (i+d)
+        finish marr len s' = do
+          arr <- A.unsafeFreeze marr
+          return (I.Text arr 0 len `Chunk` outer s')
 {-# INLINE [0] unstreamChunks #-}
 
 -- | /O(n)/ Convert a 'Stream Char' into a 'Text', using
diff --git a/Data/Text/Lazy/IO.hs b/Data/Text/Lazy/IO.hs
--- a/Data/Text/Lazy/IO.hs
+++ b/Data/Text/Lazy/IO.hs
@@ -1,4 +1,7 @@
 {-# LANGUAGE BangPatterns, CPP, RecordWildCards #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 -- |
 -- Module      : Data.Text.Lazy.IO
 -- Copyright   : (c) 2009, 2010 Bryan O'Sullivan,
diff --git a/Data/Text/Lazy/Read.hs b/Data/Text/Lazy/Read.hs
--- a/Data/Text/Lazy/Read.hs
+++ b/Data/Text/Lazy/Read.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE OverloadedStrings, CPP #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 
 -- |
 -- Module      : Data.Text.Lazy.Read
diff --git a/Data/Text/Read.hs b/Data/Text/Read.hs
--- a/Data/Text/Read.hs
+++ b/Data/Text/Read.hs
@@ -1,4 +1,7 @@
-{-# LANGUAGE OverloadedStrings, UnboxedTuples #-}
+{-# LANGUAGE OverloadedStrings, UnboxedTuples, CPP #-}
+#if __GLASGOW_HASKELL__ >= 702
+{-# LANGUAGE Trustworthy #-}
+#endif
 
 -- |
 -- Module      : Data.Text.Read
diff --git a/Data/Text/Search.hs b/Data/Text/Search.hs
--- a/Data/Text/Search.hs
+++ b/Data/Text/Search.hs
@@ -86,4 +86,4 @@
         where loop !i | i >= hlen     = []
                       | hindex i == c = i : loop (i+1)
                       | otherwise     = loop (i+1)
-{- INLINE indices #-}
+{-# INLINE indices #-}
diff --git a/Data/Text/Unsafe.hs b/Data/Text/Unsafe.hs
--- a/Data/Text/Unsafe.hs
+++ b/Data/Text/Unsafe.hs
@@ -14,6 +14,7 @@
     (
       inlineInterleaveST
     , inlinePerformIO
+    , unsafeDupablePerformIO
     , Iter(..)
     , iter
     , iter_
@@ -33,6 +34,11 @@
 import Data.Text.Unsafe.Base (inlineInterleaveST, inlinePerformIO)
 import Data.Text.UnsafeChar (unsafeChr)
 import qualified Data.Text.Array as A
+#if __GLASGOW_HASKELL__ >= 611
+import GHC.IO (unsafeDupablePerformIO)
+#else
+import GHC.IOBase (unsafeDupablePerformIO)
+#endif
 
 -- | /O(1)/ A variant of 'head' for non-empty 'Text'. 'unsafeHead'
 -- omits the check for the empty case, so there is an obligation on
diff --git a/benchmarks/haskell/Benchmarks.hs b/benchmarks/haskell/Benchmarks.hs
--- a/benchmarks/haskell/Benchmarks.hs
+++ b/benchmarks/haskell/Benchmarks.hs
@@ -49,7 +49,8 @@
         , Equality.benchmark (tf "japanese.txt")
         , FileRead.benchmark (tf "russian.txt")
         , FoldLines.benchmark (tf "russian.txt")
-        , Pure.benchmark (tf "japanese.txt")
+        , Pure.benchmark "tiny "(tf "tiny.txt")
+        , Pure.benchmark "japanese" (tf "japanese.txt")
         , ReadNumbers.benchmark (tf "numbers.txt")
         , Replace.benchmark (tf "russian.txt") "принимая" "своем"
         , Search.benchmark (tf "russian.txt") "принимая"
diff --git a/benchmarks/haskell/Benchmarks/Builder.hs b/benchmarks/haskell/Benchmarks/Builder.hs
--- a/benchmarks/haskell/Benchmarks/Builder.hs
+++ b/benchmarks/haskell/Benchmarks/Builder.hs
@@ -12,7 +12,7 @@
 import Criterion (Benchmark, bgroup, bench, nf)
 import Data.Binary.Builder as B
 import Data.ByteString.Char8 ()
-import Data.Monoid (mconcat)
+import Data.Monoid (mconcat, mempty)
 import qualified Blaze.ByteString.Builder as Blaze
 import qualified Blaze.ByteString.Builder.Char.Utf8 as Blaze
 import qualified Data.ByteString as SB
@@ -20,18 +20,45 @@
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as LT
 import qualified Data.Text.Lazy.Builder as LTB
+import qualified Data.Text.Lazy.Builder.Int as Int
+import Data.Int (Int64)
 
 benchmark :: IO Benchmark
 benchmark = return $ bgroup "Builder"
-    [ bench "LazyText" $ nf
-        (LT.length . LTB.toLazyText . mconcat . map LTB.fromText) texts
-    , bench "Binary" $ nf
-        (LB.length . B.toLazyByteString . mconcat . map B.fromByteString)
-        byteStrings
-    , bench "Blaze" $ nf
-        (LB.length . Blaze.toLazyByteString . mconcat . map Blaze.fromString)
-        strings
+    [ bgroup "Comparison"
+      [ bench "LazyText" $ nf
+          (LT.length . LTB.toLazyText . mconcat . map LTB.fromText) texts
+      , bench "Binary" $ nf
+          (LB.length . B.toLazyByteString . mconcat . map B.fromByteString)
+          byteStrings
+      , bench "Blaze" $ nf
+          (LB.length . Blaze.toLazyByteString . mconcat . map Blaze.fromString)
+          strings
+      ]
+    , bgroup "Int"
+      [ bgroup "Decimal"
+        [ bgroup "Positive" .
+          flip map numbers $ \n ->
+          (bench (show (length (show n))) $ nf (LTB.toLazyText . Int.decimal) n)
+        , bgroup "Negative" .
+          flip map numbers $ \m ->
+          let n = negate m in
+          (bench (show (length (show n))) $ nf (LTB.toLazyText . Int.decimal) n)
+        , bench "Empty" $ nf LTB.toLazyText mempty
+        , bgroup "Show" .
+          flip map numbers $ \n ->
+          (bench (show (length (show n))) $ nf show n)
+        ]
+      ]
     ]
+  where
+    numbers :: [Int64]
+    numbers = [
+      6, 14, 500, 9688, 10654, 620735, 5608880, 37010612,
+      731223504, 5061580596, 24596952933, 711732309084, 2845910093839,
+      54601756118340, 735159434806159, 3619097625502435, 95777227510267124,
+      414944309510675693, 8986407456998704019
+     ]
 
 texts :: [T.Text]
 texts = take 200000 $ cycle ["foo", "λx", "由の"]
diff --git a/benchmarks/haskell/Benchmarks/DecodeUtf8.hs b/benchmarks/haskell/Benchmarks/DecodeUtf8.hs
--- a/benchmarks/haskell/Benchmarks/DecodeUtf8.hs
+++ b/benchmarks/haskell/Benchmarks/DecodeUtf8.hs
@@ -18,7 +18,7 @@
     ( benchmark
     ) where
 
-import Foreign.C.Types (CInt, CSize)
+import Foreign.C.Types
 import Data.ByteString.Internal (ByteString(..))
 import Foreign.Ptr (Ptr, plusPtr)
 import Foreign.ForeignPtr (withForeignPtr)
diff --git a/benchmarks/haskell/Benchmarks/Pure.hs b/benchmarks/haskell/Benchmarks/Pure.hs
--- a/benchmarks/haskell/Benchmarks/Pure.hs
+++ b/benchmarks/haskell/Benchmarks/Pure.hs
@@ -4,7 +4,7 @@
 --
 -- * Most pure functions defined the string types
 --
-{-# LANGUAGE BangPatterns, GADTs, MagicHash #-}
+{-# LANGUAGE BangPatterns, CPP, GADTs, MagicHash #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 module Benchmarks.Pure
     ( benchmark
@@ -27,8 +27,8 @@
 import qualified Data.Text.Lazy.Builder as TB
 import qualified Data.Text.Lazy.Encoding as TL
 
-benchmark :: FilePath -> IO Benchmark
-benchmark fp = do
+benchmark :: String -> FilePath -> IO Benchmark
+benchmark kind fp = do
     -- Evaluate stuff before actually running the benchmark, we don't want to
     -- count it here.
 
@@ -100,6 +100,10 @@
             , benchBSL $ nf BL.unpack bla
             , benchS   $ nf UTF8.toString bsa
             ]
+        , bgroup "decode'"
+            [ benchT   $ nf T.decodeUtf8' bsa
+            , benchTL  $ nf TL.decodeUtf8' bla
+            ]
         , bgroup "drop"
             [ benchT   $ nf (T.drop (ta_len `div` 3)) ta
             , benchTL  $ nf (TL.drop (tla_len `div` 3)) tla
@@ -405,11 +409,11 @@
             ]
         ]
   where
-    benchS   = bench "String"
-    benchT   = bench "Text"
-    benchTL  = bench "LazyText"
-    benchBS  = bench "ByteString"
-    benchBSL = bench "LazyByteString"
+    benchS   = bench ("String+" ++ kind)
+    benchT   = bench ("Text+" ++ kind)
+    benchTL  = bench ("LazyText+" ++ kind)
+    benchBS  = bench ("ByteString+" ++ kind)
+    benchBSL = bench ("LazyByteString+" ++ kind)
 
     c  = 'й'
     p0 = (== c)
@@ -425,11 +429,13 @@
     replicat n = concat . L.replicate n
     short = T.pack "short"
 
+#if !MIN_VERSION_bytestring(0,10,0)
 instance NFData BS.ByteString
 
 instance NFData BL.ByteString where
     rnf BL.Empty        = ()
     rnf (BL.Chunk _ ts) = rnf ts
+#endif
 
 data B where
     B :: NFData a => a -> B
diff --git a/benchmarks/haskell/Benchmarks/Stream.hs b/benchmarks/haskell/Benchmarks/Stream.hs
--- a/benchmarks/haskell/Benchmarks/Stream.hs
+++ b/benchmarks/haskell/Benchmarks/Stream.hs
@@ -86,7 +86,6 @@
             ]
 
         -- Encoding.Fusion.Common
-        , bench "restreamUtf8"    $ nf F.restreamUtf8 s
         , bench "restreamUtf16LE" $ nf F.restreamUtf16LE s
         , bench "restreamUtf16BE" $ nf F.restreamUtf16BE s
         , bench "restreamUtf32LE" $ nf F.restreamUtf32LE s
diff --git a/benchmarks/text-benchmarks.cabal b/benchmarks/text-benchmarks.cabal
--- a/benchmarks/text-benchmarks.cabal
+++ b/benchmarks/text-benchmarks.cabal
@@ -15,13 +15,19 @@
 
 cabal-version:       >=1.2
 
+flag llvm
+  description: use LLVM
+  default: False
+
 executable text-benchmarks
   hs-source-dirs: haskell ..
   c-sources:      ../cbits/cbits.c
                   cbits/time_iconv.c
   main-is:        Benchmarks.hs
-  ghc-options:    -Wall -O2 -fllvm
-  cpp-options:    -DHAVE_DEEPSEQ
+  ghc-options:    -Wall -O2
+  if flag(llvm)
+    ghc-options:  -fllvm
+  cpp-options:    -DHAVE_DEEPSEQ -DINTEGER_GMP
   build-depends:  base == 4.*,
                   binary,
                   blaze-builder,
@@ -33,13 +39,14 @@
                   directory,
                   filepath,
                   ghc-prim,
+                  integer-gmp,
                   stringsearch,
                   utf8-string
 
 executable text-multilang
   hs-source-dirs: haskell
   main-is:        Multilang.hs
-  ghc-options:    -Wall -O2 -fllvm
+  ghc-options:    -Wall -O2
   build-depends:  base == 4.*,
                   bytestring,
                   text,
diff --git a/cbits/cbits.c b/cbits/cbits.c
--- a/cbits/cbits.c
+++ b/cbits/cbits.c
@@ -62,6 +62,44 @@
 }
 
 /*
+ * The ISO 8859-1 (aka latin-1) code points correspond exactly to the first 256 unicode
+ * code-points, therefore we can trivially convert from a latin-1 encoded bytestring to
+ * an UTF16 array
+ */
+void
+_hs_text_decode_latin1(uint16_t *dest, const uint8_t const *src,
+                       const uint8_t const *srcend)
+{
+  const uint8_t *p = src;
+
+#if defined(__i386__) || defined(__x86_64__)
+  /* This optimization works on a little-endian systems by using
+     (aligned) 32-bit loads instead of 8-bit loads
+   */
+
+  /* consume unaligned prefix */
+  while (p != srcend && (uintptr_t)p & 0x3)
+    *dest++ = *p++;
+
+  /* iterate over 32-bit aligned loads */
+  while (p < srcend - 3) {
+    const uint32_t w = *((const uint32_t *)p);
+
+    *dest++ =  w        & 0xff;
+    *dest++ = (w >> 8)  & 0xff;
+    *dest++ = (w >> 16) & 0xff;
+    *dest++ = (w >> 24) & 0xff;
+
+    p += 4;
+  }
+#endif
+
+  /* handle unaligned suffix */
+  while (p != srcend)
+    *dest++ = *p++;
+}
+
+/*
  * A best-effort decoder. Runs until it hits either end of input or
  * the start of an invalid byte sequence.
  *
diff --git a/tests/Tests/Properties.hs b/tests/Tests/Properties.hs
--- a/tests/Tests/Properties.hs
+++ b/tests/Tests/Properties.hs
@@ -32,6 +32,7 @@
 import Test.Framework.Providers.QuickCheck2 (testProperty)
 import qualified Data.Bits as Bits (shiftL, shiftR)
 import qualified Data.ByteString as B
+import qualified Data.ByteString.Lazy as BL
 import qualified Data.List as L
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as E
@@ -65,10 +66,20 @@
 tl_chunk_unchunk    = (TL.fromChunks . TL.toChunks) `eq` id
 tl_from_to_strict   = (TL.fromStrict . TL.toStrict) `eq` id
 
+-- Note: this silently truncates code-points > 255 to 8-bit due to 'B.pack'
+encodeL1 :: T.Text -> B.ByteString
+encodeL1 = B.pack . map (fromIntegral . fromEnum) . T.unpack
+encodeLazyL1 :: TL.Text -> BL.ByteString
+encodeLazyL1 = BL.fromChunks . map encodeL1 . TL.toChunks
+
 t_ascii t    = E.decodeASCII (E.encodeUtf8 a) == a
     where a  = T.map (\c -> chr (ord c `mod` 128)) t
 tl_ascii t   = EL.decodeASCII (EL.encodeUtf8 a) == a
     where a  = TL.map (\c -> chr (ord c `mod` 128)) t
+t_latin1 t   = E.decodeLatin1 (encodeL1 a) == a
+    where a  = T.map (\c -> chr (ord c `mod` 256)) t
+tl_latin1 t  = EL.decodeLatin1 (encodeLazyL1 a) == a
+    where a  = TL.map (\c -> chr (ord c `mod` 256)) t
 t_utf8       = forAll genUnicode $ (E.decodeUtf8 . E.encodeUtf8) `eq` id
 t_utf8'      = forAll genUnicode $ (E.decodeUtf8' . E.encodeUtf8) `eq` (id . Right)
 tl_utf8      = forAll genUnicode $ (EL.decodeUtf8 . EL.encodeUtf8) `eq` id
@@ -722,6 +733,8 @@
   where n = small m
 t_use_from t = monadicIO $ assert . (==t) =<< run (useAsPtr t fromPtr)
 
+t_copy t = T.copy t == t
+
 -- Regression tests.
 s_filter_eq s = S.filter p t == S.streamList (filter p s)
     where p = (/= S.last t)
@@ -754,6 +767,8 @@
     testGroup "transcoding" [
       testProperty "t_ascii" t_ascii,
       testProperty "tl_ascii" tl_ascii,
+      testProperty "t_latin1" t_latin1,
+      testProperty "tl_latin1" tl_latin1,
       testProperty "t_utf8" t_utf8,
       testProperty "t_utf8'" t_utf8',
       testProperty "tl_utf8" tl_utf8,
@@ -1184,6 +1199,7 @@
       testProperty "t_dropWord16" t_dropWord16,
       testProperty "t_takeWord16" t_takeWord16,
       testProperty "t_take_drop_16" t_take_drop_16,
-      testProperty "t_use_from" t_use_from
+      testProperty "t_use_from" t_use_from,
+      testProperty "t_copy" t_copy
     ]
   ]
diff --git a/tests/Tests/Regressions.hs b/tests/Tests/Regressions.hs
--- a/tests/Tests/Regressions.hs
+++ b/tests/Tests/Regressions.hs
@@ -8,10 +8,12 @@
 
 import Control.Exception (SomeException, handle)
 import System.IO
-import Test.HUnit (assertFailure)
+import Test.HUnit (assertBool, assertFailure)
 import qualified Data.ByteString as B
+import Data.ByteString.Char8 ()
 import qualified Data.ByteString.Lazy as LB
 import qualified Data.Text as T
+import qualified Data.Text.Encoding as TE
 import qualified Data.Text.IO as T
 import qualified Data.Text.Lazy as LT
 import qualified Data.Text.Lazy.Encoding as LE
@@ -49,9 +51,18 @@
     power | maxBound == (2147483647::Int) = 28
           | otherwise                     = 60 :: Int
 
+-- Reported by John Millikin: a UTF-8 decode error handler could
+-- return a bogus substitution character, which we would write without
+-- checking.
+utf8_decode_unsafe :: IO ()
+utf8_decode_unsafe = do
+  let t = TE.decodeUtf8With (\_ _ -> Just '\xdc00') "\x80"
+  assertBool "broken error recovery shouldn't break us" (t == "\xfffd")
+
 tests :: F.Test
 tests = F.testGroup "Regressions"
     [ F.testCase "hGetContents_crash" hGetContents_crash
     , F.testCase "lazy_encode_crash" lazy_encode_crash
     , F.testCase "replicate_crash" replicate_crash
+    , F.testCase "utf8_decode_unsafe" utf8_decode_unsafe
     ]
diff --git a/tests/text-tests.cabal b/tests/text-tests.cabal
--- a/tests/text-tests.cabal
+++ b/tests/text-tests.cabal
@@ -87,6 +87,8 @@
     Data.Text.Lazy.Builder
     Data.Text.Lazy.Builder.Functions
     Data.Text.Lazy.Builder.Int
+    Data.Text.Lazy.Builder.Int.Digits
+    Data.Text.Lazy.Builder.Internal
     Data.Text.Lazy.Builder.RealFloat
     Data.Text.Lazy.Builder.RealFloat.Functions
     Data.Text.Lazy.Encoding
diff --git a/text.cabal b/text.cabal
--- a/text.cabal
+++ b/text.cabal
@@ -1,5 +1,5 @@
 name:           text
-version:        0.11.2.3
+version:        0.11.3.0
 homepage:       https://github.com/bos/text
 bug-reports:    https://github.com/bos/text/issues
 synopsis:       An efficient packed Unicode text type.
@@ -109,6 +109,8 @@
     Data.Text.Fusion.Size
     Data.Text.IO.Internal
     Data.Text.Lazy.Builder.Functions
+    Data.Text.Lazy.Builder.Int.Digits
+    Data.Text.Lazy.Builder.Internal
     Data.Text.Lazy.Builder.RealFloat.Functions
     Data.Text.Lazy.Encoding.Fusion
     Data.Text.Lazy.Fusion
@@ -131,7 +133,7 @@
     cpp-options: -DHAVE_DEEPSEQ
   else
     build-depends: extensible-exceptions
-    extensions: PatternSignatures
+    extensions: ScopedTypeVariables
 
   ghc-options: -Wall -funbox-strict-fields -O2
   if impl(ghc >= 6.8)
@@ -149,16 +151,15 @@
       cpp-options: -DINTEGER_GMP
       build-depends: integer-gmp >= 0.2
 
-
   if impl(ghc >= 6.9) && impl(ghc < 6.11)
     cpp-options: -DINTEGER_GMP
     build-depends: integer >= 0.1 && < 0.2
 
 test-suite tests
   type:           exitcode-stdio-1.0
-  hs-source-dirs: tests
+  hs-source-dirs: tests .
   main-is:        Tests.hs
-  -- c-sources:      cbits/cbits.c
+  c-sources:      cbits/cbits.c
 
   ghc-options:
     -Wall -threaded -O0 -rtsopts
@@ -169,6 +170,7 @@
   build-depends:
     HUnit >= 1.2,
     QuickCheck >= 2.4,
+    array,
     base,
     bytestring,
     deepseq,
@@ -177,8 +179,19 @@
     random,
     test-framework >= 0.4,
     test-framework-hunit >= 0.2,
-    test-framework-quickcheck2 >= 0.2,
-    text
+    test-framework-quickcheck2 >= 0.2
+
+  if impl(ghc >= 6.11)
+    if flag(integer-simple)
+      cpp-options: -DINTEGER_SIMPLE
+      build-depends: integer-simple >= 0.1 && < 0.5
+    else
+      cpp-options: -DINTEGER_GMP
+      build-depends: integer-gmp >= 0.2
+
+  if impl(ghc >= 6.9) && impl(ghc < 6.11)
+    cpp-options: -DINTEGER_GMP
+    build-depends: integer >= 0.1 && < 0.2
 
 source-repository head
   type:     git
