diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,23 @@
 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.15]
+
+### Added
+
+* Incremental Flattening generates fewer redundant code versions.
+
+* Better simplification of slices. (#2125)
+
+### Fixed
+
+* Ignore type suffixes when unifying expressions (#2124).
+
+* In the C API, opaque types that correspond to an array of an opaque
+  type are now once again named `futhark_opaque_arr_...`.
+
+* `cuda` backend did not correctly profile CPU-to-GPU scalar copies.
+
 ## [0.25.14]
 
 ### Added
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.14
+version:        0.25.15
 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/rts/c/backends/cuda.h b/rts/c/backends/cuda.h
--- a/rts/c/backends/cuda.h
+++ b/rts/c/backends/cuda.h
@@ -942,7 +942,7 @@
   }
   CUDA_SUCCEED_OR_RETURN(cuMemcpyHtoD(dst + offset, src, size));
   if (event != NULL) {
-    CUDA_SUCCEED_FATAL(cuEventRecord(event->start, ctx->stream));
+    CUDA_SUCCEED_FATAL(cuEventRecord(event->end, ctx->stream));
   }
   return FUTHARK_SUCCESS;
 }
diff --git a/rts/c/event_list.h b/rts/c/event_list.h
--- a/rts/c/event_list.h
+++ b/rts/c/event_list.h
@@ -41,8 +41,9 @@
   l->num_events++;
 }
 
-static void report_events_in_list(struct event_list *l,
-                                  struct str_builder* sb) {
+static int report_events_in_list(struct event_list *l,
+                                 struct str_builder* sb) {
+  int ret = 0;
   for (int i = 0; i < l->num_events; i++) {
     if (i != 0) {
       str_builder_str(sb, ",");
@@ -52,11 +53,15 @@
     str_builder_str(sb, ",\"description\":");
     str_builder_json_str(sb, l->events[i].description);
     free(l->events[i].description);
-    l->events[i].f(sb, l->events[i].data);
+    if (l->events[i].f(sb, l->events[i].data) != 0) {
+      ret = 1;
+      break;
+    }
     str_builder(sb, "}");
   }
   event_list_free(l);
   event_list_init(l);
+  return ret;
 }
 
 // End of event_list.h
diff --git a/rts/c/server.h b/rts/c/server.h
--- a/rts/c/server.h
+++ b/rts/c/server.h
@@ -553,7 +553,17 @@
 void cmd_report(struct server_state *s, const char *args[]) {
   (void)args;
   char *report = futhark_context_report(s->ctx);
-  puts(report);
+  if (report) {
+    puts(report);
+  } else {
+    failure();
+    report = futhark_context_get_error(s->ctx);
+    if (report) {
+      puts(report);
+    } else {
+      puts("Failed to produce profiling report.\n");
+    }
+  }
   free(report);
 }
 
diff --git a/src/Futhark/CodeGen/Backends/GenericC.hs b/src/Futhark/CodeGen/Backends/GenericC.hs
--- a/src/Futhark/CodeGen/Backends/GenericC.hs
+++ b/src/Futhark/CodeGen/Backends/GenericC.hs
@@ -650,9 +650,13 @@
                  str_builder_str(&builder, "\"memory\":{");
                  $items:(L.intersperse comma memreport)
                  str_builder_str(&builder, "},\"events\":[");
-                 report_events_in_list(&ctx->event_list, &builder);
-                 str_builder_str(&builder, "]}");
-                 return builder.str;
+                 if (report_events_in_list(&ctx->event_list, &builder) != 0) {
+                   free(builder.str);
+                   return NULL;
+                 } else {
+                   str_builder_str(&builder, "]}");
+                   return builder.str;
+                 }
                }|]
     )
 
diff --git a/src/Futhark/CodeGen/Backends/SimpleRep.hs b/src/Futhark/CodeGen/Backends/SimpleRep.hs
--- a/src/Futhark/CodeGen/Backends/SimpleRep.hs
+++ b/src/Futhark/CodeGen/Backends/SimpleRep.hs
@@ -40,7 +40,7 @@
 import Data.Text qualified as T
 import Futhark.CodeGen.ImpCode
 import Futhark.CodeGen.RTS.C (scalarF16H, scalarH)
-import Futhark.Util (hashText, zEncodeText)
+import Futhark.Util (hashText, showText, zEncodeText)
 import Language.C.Quote.C qualified as C
 import Language.C.Syntax qualified as C
 
@@ -123,18 +123,34 @@
   | isValidCName v = v
   | otherwise = zEncodeText v
 
+-- | Valid C identifier name?
+valid :: T.Text -> Bool
+valid s =
+  T.head s /= '_'
+    && not (isDigit $ T.head s)
+    && T.all ok s
+  where
+    ok c = isAlphaNum c || c == '_'
+
+isArrayName :: T.Text -> (Int, T.Text)
+isArrayName s =
+  if "[]" `T.isPrefixOf` s
+    then
+      let (k, s') = isArrayName (T.drop 2 s)
+       in (k + 1, s')
+    else (0, s)
+
 -- | The name of exposed opaque types.
 opaqueName :: Name -> T.Text
 opaqueName "()" = "opaque_unit" -- Hopefully this ad-hoc convenience won't bite us.
 opaqueName s
-  | valid = "opaque_" <> s'
+  | (k, s'') <- isArrayName s',
+    k > 0,
+    valid s'' =
+      "opaque_arr_" <> s'' <> "_" <> showText k <> "d"
+  | valid s' = "opaque_" <> s'
   where
     s' = nameToText s
-    valid =
-      T.head s' /= '_'
-        && not (isDigit $ T.head s')
-        && T.all ok s'
-    ok c = isAlphaNum c || c == '_'
 opaqueName s = "opaque_" <> hashText (nameToText s)
 
 -- | The 'PrimType' (and sign) correspond to a human-readable scalar
diff --git a/src/Futhark/IR/SegOp.hs b/src/Futhark/IR/SegOp.hs
--- a/src/Futhark/IR/SegOp.hs
+++ b/src/Futhark/IR/SegOp.hs
@@ -1230,11 +1230,7 @@
   when (kres == kres') cannotSimplify
 
   kbody <- mkKernelBodyM kstms kres'
-  addStm $
-    Let (Pat kpes') dec $
-      Op $
-        segOp $
-          SegMap lvl space ts' kbody
+  addStm $ Let (Pat kpes') dec $ Op $ segOp $ SegMap lvl space ts' kbody
   where
     isInvariant Constant {} = True
     isInvariant (Var v) = isJust $ ST.lookup v vtable
diff --git a/src/Futhark/Internalise/Entry.hs b/src/Futhark/Internalise/Entry.hs
--- a/src/Futhark/Internalise/Entry.hs
+++ b/src/Futhark/Internalise/Entry.hs
@@ -51,6 +51,7 @@
 rootType (E.TEApply te E.TypeArgExpSize {} _) = rootType te
 rootType (E.TEUnique te _) = rootType te
 rootType (E.TEDim _ te _) = rootType te
+rootType (E.TEParens te _) = rootType te
 rootType te = te
 
 typeExpOpaqueName :: E.TypeExp E.Exp VName -> Name
diff --git a/src/Futhark/Optimise/Simplify/Rules/Index.hs b/src/Futhark/Optimise/Simplify/Rules/Index.hs
--- a/src/Futhark/Optimise/Simplify/Rules/Index.hs
+++ b/src/Futhark/Optimise/Simplify/Rules/Index.hs
@@ -7,6 +7,7 @@
   )
 where
 
+import Control.Monad (guard)
 import Data.List.NonEmpty (NonEmpty (..))
 import Data.Maybe
 import Futhark.Analysis.PrimExp.Convert
@@ -31,6 +32,14 @@
   = IndexResult Certs VName (Slice SubExp)
   | SubExpResult Certs SubExp
 
+-- Fake expressions that we can recognise.
+fakeIndices :: [TPrimExp Int64 VName]
+fakeIndices = map f [0 :: Int ..]
+  where
+    f i = isInt64 $ LeafExp (VName v (negate i)) $ IntType Int64
+      where
+        v = nameFromText ("fake_" <> showText i)
+
 -- | Try to simplify an index operation.
 simplifyIndexing ::
   (MonadBuilder m) =>
@@ -60,6 +69,22 @@
           Just $
             IndexResult cs arr . Slice . map DimFix
               <$> mapM (toSubExp "index_primexp") inds''
+      | Just (ST.IndexedArray cs arr inds'') <-
+          ST.index' idd (fixSlice (pe64 <$> Slice inds) (map fst matches)) vtable,
+        all (worthInlining . untyped) inds'',
+        arr `ST.available` vtable,
+        all (`ST.elem` vtable) (unCerts cs),
+        Just inds''' <- mapM okIdx inds'' -> do
+          Just $ IndexResult cs arr . Slice <$> sequence inds'''
+      where
+        matches = zip fakeIndices $ sliceDims $ Slice inds
+        okIdx i =
+          case lookup i matches of
+            Just w ->
+              Just $ pure $ DimSlice (constant (0 :: Int64)) w (constant (1 :: Int64))
+            Nothing -> do
+              guard $ not $ any ((`namesIntersect` freeIn i) . freeIn . fst) matches
+              Just $ DimFix <$> toSubExp "index_primexp" i
     Nothing -> Nothing
     Just (SubExp (Var v), cs) ->
       Just $ pure $ IndexResult cs v $ Slice inds
diff --git a/src/Futhark/Pass/ExtractKernels.hs b/src/Futhark/Pass/ExtractKernels.hs
--- a/src/Futhark/Pass/ExtractKernels.hs
+++ b/src/Futhark/Pass/ExtractKernels.hs
@@ -619,34 +619,34 @@
 -- | A lambda is worth sequentialising if it contains enough nested
 -- parallelism of an interesting kind.
 worthSequentialising :: Lambda SOACS -> Bool
-worthSequentialising lam = bodyInterest (lambdaBody lam) > 1
+worthSequentialising lam = bodyInterest (0 :: Int) (lambdaBody lam) > 1
   where
-    bodyInterest body =
-      sum $ interest <$> bodyStms body
-    interest stm
+    bodyInterest depth body =
+      sum $ interest depth <$> bodyStms body
+    interest depth stm
       | "sequential" `inAttrs` attrs =
           0 :: Int
       | Op (Screma _ _ form@(ScremaForm _ _ lam')) <- stmExp stm,
         isJust $ isMapSOAC form =
           if sequential_inner
             then 0
-            else bodyInterest (lambdaBody lam')
+            else bodyInterest (depth + 1) (lambdaBody lam')
       | Op Scatter {} <- stmExp stm =
           0 -- Basically a map.
       | Loop _ ForLoop {} body <- stmExp stm =
-          bodyInterest body * 10
+          bodyInterest (depth + 1) body * 10
       | WithAcc _ withacc_lam <- stmExp stm =
-          bodyInterest (lambdaBody withacc_lam)
+          bodyInterest (depth + 1) (lambdaBody withacc_lam)
       | Op (Screma _ _ form@(ScremaForm _ _ lam')) <- stmExp stm =
           1
-            + bodyInterest (lambdaBody lam')
+            + bodyInterest (depth + 1) (lambdaBody lam')
             +
-            -- Give this a bigger score if it's a redomap, as these
-            -- are often tileable and thus benefit more from
-            -- sequentialisation.
-            case isRedomapSOAC form of
-              Just _ -> 1
-              Nothing -> 0
+            -- Give this a bigger score if it's a redomap just inside
+            -- the the outer lambda, as these are often tileable and
+            -- thus benefit more from sequentialisation.
+            case (isRedomapSOAC form, depth) of
+              (Just _, 0) -> 1
+              _ -> 0
       | otherwise =
           0
       where
diff --git a/src/Language/Futhark/Prop.hs b/src/Language/Futhark/Prop.hs
--- a/src/Language/Futhark/Prop.hs
+++ b/src/Language/Futhark/Prop.hs
@@ -1376,12 +1376,20 @@
 
 -- | If these two expressions are structurally similar at top level as
 -- sizes, produce their subexpressions (which are not necessarily
--- similar, but you can check for that!).  This is the machinery
--- underlying expresssion unification.
+-- similar, but you can check for that!). This is the machinery
+-- underlying expresssion unification. We assume that the expressions
+-- have the same type.
 similarExps :: Exp -> Exp -> Maybe [(Exp, Exp)]
 similarExps e1 e2 | bareExp e1 == bareExp e2 = Just []
 similarExps e1 e2 | Just e1' <- stripExp e1 = similarExps e1' e2
 similarExps e1 e2 | Just e2' <- stripExp e2 = similarExps e1 e2'
+similarExps (IntLit x _ _) (Literal v _) =
+  case v of
+    SignedValue (Int8Value y) | x == toInteger y -> Just []
+    SignedValue (Int16Value y) | x == toInteger y -> Just []
+    SignedValue (Int32Value y) | x == toInteger y -> Just []
+    SignedValue (Int64Value y) | x == toInteger y -> Just []
+    _ -> Nothing
 similarExps
   (AppExp (BinOp (op1, _) _ (x1, _) (y1, _) _) _)
   (AppExp (BinOp (op2, _) _ (x2, _) (y2, _) _) _)
diff --git a/src/Language/Futhark/TypeChecker/Unify.hs b/src/Language/Futhark/TypeChecker/Unify.hs
--- a/src/Language/Futhark/TypeChecker/Unify.hs
+++ b/src/Language/Futhark/TypeChecker/Unify.hs
@@ -1152,8 +1152,10 @@
   pure (t, M.elems seen)
   where
     r = RigidCond t1 t2
+    same (e1, e2) =
+      maybe False (all same) $ similarExps e1 e2
     onDims _ d1 d2
-      | d1 == d2 = pure d1
+      | same (d1, d2) = pure d1
       | otherwise = do
           -- Remember mismatches we have seen before and reuse the
           -- same new size.
