diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,25 @@
 The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
 and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
 
+## [0.25.20]
+
+### Added
+
+* Better error message when in-place updates fail at runtime due to a
+  shape mismatch.
+
+### Fixed
+
+* `#[unroll]` on an outer loop now no longer causes unrolling of all
+  loops nested inside the loop body.
+
+* Obscure issue related to replications of constants in complex
+  intrablock kernels.
+
+* Interpreter no longer crashes on attributes in patterns.
+
+* Fixes to array indexing through C API when using GPU backends.
+
 ## [0.25.19]
 
 ### Added
diff --git a/docs/c-api.rst b/docs/c-api.rst
--- a/docs/c-api.rst
+++ b/docs/c-api.rst
@@ -392,7 +392,7 @@
 
 The projection and construction functions are equivalent in
 functionality to writing entry points by hand, and so serve only to
-cut down on boilerplate.  Important things to be aware of:
+cut down on boilerplate. Important things to be aware of:
 
 1. The objects constructed though these functions have their own
    lifetime (like any objects returned from an entry point) and must
@@ -404,6 +404,8 @@
    passing them to entry points that consume their arguments.  As
    always, you don't have to worry about this if you never write entry
    points that consume their arguments.
+
+3. You must synchronise before using any scalar results.
 
 The precise functions generated depend on the fields of the record.
 The following functions assume a record with Futhark-level type ``type
diff --git a/futhark.cabal b/futhark.cabal
--- a/futhark.cabal
+++ b/futhark.cabal
@@ -1,6 +1,6 @@
 cabal-version: 2.4
 name:           futhark
-version:        0.25.19
+version:        0.25.20
 synopsis:       An optimising compiler for a functional, array-oriented language.
 
 description:    Futhark is a small programming language designed to be compiled to
diff --git a/src/Futhark/AD/Rev/Hist.hs b/src/Futhark/AD/Rev/Hist.hs
--- a/src/Futhark/AD/Rev/Hist.hs
+++ b/src/Futhark/AD/Rev/Hist.hs
@@ -36,7 +36,12 @@
 withinBounds [(q, i)] = (le64 i .<. pe64 q) .&&. (pe64 (intConst Int64 (-1)) .<. le64 i)
 withinBounds (qi : qis) = withinBounds [qi] .&&. withinBounds qis
 
-elseIf :: PrimType -> [(ADM (Exp SOACS), ADM (Exp SOACS))] -> [ADM (Body SOACS)] -> ADM (Exp SOACS)
+elseIf ::
+  (MonadBuilder m, BranchType (Rep m) ~ ExtType) =>
+  PrimType ->
+  [(m (Exp (Rep m)), m (Exp (Rep m)))] ->
+  [m (Body (Rep m))] ->
+  m (Exp (Rep m))
 elseIf t [(c1, c2)] [bt, bf] =
   eIf
     (eCmpOp (CmpEq t) c1 c2)
@@ -51,7 +56,7 @@
     $ elseIf t cs bs
 elseIf _ _ _ = error "In elseIf, Hist.hs: input not supported"
 
-bindSubExpRes :: String -> [SubExpRes] -> ADM [VName]
+bindSubExpRes :: (MonadBuilder m) => String -> [SubExpRes] -> m [VName]
 bindSubExpRes s =
   traverse
     ( \(SubExpRes cs se) -> do
diff --git a/src/Futhark/CodeGen/Backends/GenericC/Types.hs b/src/Futhark/CodeGen/Backends/GenericC/Types.hs
--- a/src/Futhark/CodeGen/Backends/GenericC/Types.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC/Types.hs
@@ -552,7 +552,7 @@
           -- variable.
           copy
             CopyNoBarrier
-            [C.cexp|&v->$id:(tupleField j)|]
+            [C.cexp|(unsigned char*)&v->$id:(tupleField j)|]
             [C.cexp|0|]
             DefaultSpace
             [C.cexp|arr->$id:(tupleField j)->mem.mem|]
@@ -571,7 +571,7 @@
             CopyNoBarrier
             [C.cexp|v->$id:(tupleField j)->mem.mem|]
             [C.cexp|0|]
-            DefaultSpace
+            space
             [C.cexp|arr->$id:(tupleField j)->mem.mem|]
             (indexExp pt r [C.cexp|arr->$id:(tupleField j)->shape|])
             space
diff --git a/src/Futhark/IR/Syntax/Core.hs b/src/Futhark/IR/Syntax/Core.hs
--- a/src/Futhark/IR/Syntax/Core.hs
+++ b/src/Futhark/IR/Syntax/Core.hs
@@ -498,6 +498,12 @@
 instance IsString (ErrorMsg a) where
   fromString = ErrorMsg . pure . fromString
 
+instance Monoid (ErrorMsg a) where
+  mempty = ErrorMsg mempty
+
+instance Semigroup (ErrorMsg a) where
+  ErrorMsg x <> ErrorMsg y = ErrorMsg $ x <> y
+
 -- | A part of an error message.
 data ErrorMsgPart a
   = -- | A literal string.
diff --git a/src/Futhark/Internalise/Exps.hs b/src/Futhark/Internalise/Exps.hs
--- a/src/Futhark/Internalise/Exps.hs
+++ b/src/Futhark/Internalise/Exps.hs
@@ -445,11 +445,19 @@
   where
     sparams' = map (`TypeParamDim` mempty) sparams
 
+    -- Attributes that apply to loops.
+    loopAttrs = oneAttr "unroll"
+    -- Remove those attributes from the attribute set that apply to
+    -- the loop itself.
+    noLoopAttrs env = env {envAttrs = envAttrs env `withoutAttrs` loopAttrs}
+
+    loopBody = local noLoopAttrs $ internaliseExp "loopres" loopbody
+
     forLoop mergepat' shapepat mergeinit i loopvars form' =
       bodyFromStms . localScope (scopeOfLoopForm form') $ do
         forM_ loopvars $ \(p, arr) ->
           letBindNames [I.paramName p] =<< eIndex arr [eSubExp (I.Var i)]
-        ses <- internaliseExp "loopres" loopbody
+        ses <- loopBody
         sets <- mapM subExpType ses
         shapeargs <- argShapes (map I.paramName shapepat) mergepat' sets
         pure
@@ -520,7 +528,7 @@
         addStms init_loop_cond_stms
 
         bodyFromStms $ do
-          ses <- internaliseExp "loopres" loopbody
+          ses <- loopBody
           sets <- mapM subExpType ses
           loop_while <- newParam "loop_while" $ I.Prim I.Bool
           shapeargs <- argShapes (map I.paramName shapepat) mergepat' sets
@@ -730,22 +738,28 @@
 internaliseExp desc (E.Update src slice ve loc) = do
   ves <- internaliseExp "lw_val" ve
   srcs <- internaliseExpToVars "src" src
-  dims <- case srcs of
-    [] -> pure [] -- Will this happen?
-    v : _ -> I.arrayDims <$> lookupType v
-  (idxs', cs) <- internaliseSlice loc dims slice
+  (src_dims, ve_dims) <- case (srcs, ves) of
+    (src_v : _, ve_v : _) ->
+      (,)
+        <$> (I.arrayDims <$> lookupType src_v)
+        <*> (I.arrayDims <$> subExpType ve_v)
+    _ -> pure ([], []) -- Will this happen?
+  (idxs', cs) <- internaliseSlice loc src_dims slice
+  let src_dims' = sliceDims (Slice idxs')
+      rank = length src_dims'
+      errormsg =
+        "Shape "
+          <> errorShape src_dims'
+          <> " of slice does not match shape "
+          <> errorShape (take rank ve_dims)
+          <> " of value."
 
   let comb sname ve' = do
         sname_t <- lookupType sname
         let full_slice = fullSlice sname_t idxs'
             rowtype = sname_t `setArrayDims` sliceDims full_slice
         ve'' <-
-          ensureShape
-            "shape of value does not match shape of source array"
-            loc
-            rowtype
-            "lw_val_correct_shape"
-            ve'
+          ensureShape errormsg loc rowtype "lw_val_correct_shape" ve'
         letInPlace desc sname full_slice $ BasicOp $ SubExp ve''
   certifying cs $ map I.Var <$> zipWithM comb srcs ves
 internaliseExp desc (E.RecordUpdate src fields ve _ _) = do
@@ -2227,3 +2241,9 @@
     compact (ErrorString x : ErrorString y : parts) =
       compact (ErrorString (x <> y) : parts)
     compact (x : y) = x : compact y
+
+errorShape :: [a] -> ErrorMsg a
+errorShape dims =
+  "["
+    <> mconcat (intersperse "][" $ map (ErrorMsg . pure . ErrorVal int64) dims)
+    <> "]"
diff --git a/src/Futhark/Internalise/Monad.hs b/src/Futhark/Internalise/Monad.hs
--- a/src/Futhark/Internalise/Monad.hs
+++ b/src/Futhark/Internalise/Monad.hs
@@ -196,10 +196,7 @@
   InternaliseM Certs
 assert desc se msg loc = assertingOne $ do
   attrs <- asks $ attrsForAssert . envAttrs
-  attributing attrs $
-    letExp desc $
-      BasicOp $
-        Assert se msg (loc, mempty)
+  attributing attrs $ letExp desc $ BasicOp $ Assert se msg (loc, mempty)
 
 -- | Execute the given action if 'envDoBoundsChecks' is true, otherwise
 -- just return an empty list.
diff --git a/src/Futhark/Pass/ExplicitAllocations.hs b/src/Futhark/Pass/ExplicitAllocations.hs
--- a/src/Futhark/Pass/ExplicitAllocations.hs
+++ b/src/Futhark/Pass/ExplicitAllocations.hs
@@ -387,7 +387,7 @@
   AllocM fromrep torep a
 allocInLoopParams merge m = do
   ((valparams, valargs, handle_loop_subexps), (mem_params, ctx_params)) <-
-    runWriterT $ unzip3 <$> mapM allocInMergeParam merge
+    runWriterT $ unzip3 <$> mapM allocInLoopParam merge
   let mergeparams' = mem_params <> ctx_params <> valparams
       summary = scopeOfFParams mergeparams'
 
@@ -417,7 +417,7 @@
       pure $ Var res'
     scalarRes _ _ _ se = pure se
 
-    allocInMergeParam ::
+    allocInLoopParam ::
       (Allocable fromrep torep inner) =>
       (Param DeclType, SubExp) ->
       WriterT
@@ -427,7 +427,7 @@
           SubExp,
           SubExp -> WriterT ([SubExp], [SubExp]) (AllocM fromrep torep) SubExp
         )
-    allocInMergeParam (mergeparam, Var v)
+    allocInLoopParam (mergeparam, Var v)
       | param_t@(Array pt shape u) <- paramDeclType mergeparam = do
           (v_mem, v_lmad) <- lift $ lookupArraySummary v
           v_mem_space <- lift $ lookupMemSpace v_mem
@@ -443,7 +443,7 @@
                   -- space, so copy them elsewhere and try again.
                   space <- lift askDefaultSpace
                   (_, v') <- lift $ allocLinearArray space (baseString v) v
-                  allocInMergeParam (mergeparam, Var v')
+                  allocInLoopParam (mergeparam, Var v')
                 else do
                   p <- newParam "mem_param" $ MemMem v_mem_space
                   tell ([p], [])
@@ -479,7 +479,7 @@
                   Var v',
                   ensureArrayIn v_mem_space'
                 )
-    allocInMergeParam (mergeparam, se) = doDefault mergeparam se =<< lift askDefaultSpace
+    allocInLoopParam (mergeparam, se) = doDefault mergeparam se =<< lift askDefaultSpace
 
     doDefault mergeparam se space = do
       mergeparam' <- allocInFParam mergeparam space
diff --git a/src/Futhark/Pass/ExtractKernels/DistributeNests.hs b/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
--- a/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
+++ b/src/Futhark/Pass/ExtractKernels/DistributeNests.hs
@@ -584,19 +584,18 @@
         -- Move the replicated dimensions back where they belong.
         letBind outerpat . BasicOp $
           Rearrange ([res_r - nest_r .. res_r - 1] ++ [0 .. res_r - nest_r - 1]) arr_tr_rep
-maybeDistributeStm (Let (Pat [pe]) aux (BasicOp (Replicate (Shape (d : ds)) v))) acc = do
-  tmp <- newVName "tmp"
-  let rowt = rowType $ patElemType pe
-      newstm = Let (Pat [pe]) aux $ Op $ Screma d [] $ mapSOAC lam
-      tmpstm =
-        Let (Pat [PatElem tmp rowt]) aux $ BasicOp $ Replicate (Shape ds) v
-      lam =
-        Lambda
-          { lambdaReturnType = [rowt],
-            lambdaParams = [],
-            lambdaBody = mkBody (oneStm tmpstm) [varRes tmp]
-          }
-  maybeDistributeStm newstm acc
+maybeDistributeStm stm@(Let _ aux (BasicOp (Replicate shape v))) acc = do
+  distributeSingleStm acc stm >>= \case
+    Just (kernels, _, nest, acc')
+      | boundInKernelNest nest == mempty -> do
+          addPostStms kernels
+          let outerpat = loopNestingPat $ fst nest
+              nest_shape = Shape $ kernelNestWidths nest
+          localScope (typeEnvFromDistAcc acc') $ do
+            postStm <=< runBuilder_ . auxing aux . letBind outerpat $
+              BasicOp (Replicate (nest_shape <> shape) v)
+            pure acc'
+    _ -> addStmToAcc stm acc
 -- Opaques are applied to the full array, because otherwise they can
 -- drastically inhibit parallelisation in some cases.
 maybeDistributeStm stm@(Let (Pat [pe]) aux (BasicOp (Opaque _ (Var stm_arr)))) acc
diff --git a/src/Language/Futhark/Interpreter.hs b/src/Language/Futhark/Interpreter.hs
--- a/src/Language/Futhark/Interpreter.hs
+++ b/src/Language/Futhark/Interpreter.hs
@@ -431,6 +431,8 @@
     Just env' -> pure env'
 
 patternMatch :: Env -> Pat (TypeBase Size u) -> Value -> MaybeT EvalM Env
+patternMatch env (PatAttr _ p _) val =
+  patternMatch env p val
 patternMatch env (Id v (Info t) _) val =
   lift $
     pure $
