diff --git a/gcc/c-family/c-gimplify.cc b/gcc/c-family/c-gimplify.cc
index ef5c7d919fc..82c88bd70e1 100644
--- a/gcc/c-family/c-gimplify.cc
+++ b/gcc/c-family/c-gimplify.cc
@@ -506,6 +506,7 @@ c_genericize_control_stmt (tree *stmt_p, int *walk_subtrees, void *data,
     case OMP_DISTRIBUTE:
     case OMP_LOOP:
     case OMP_TASKLOOP:
+    case OMP_LOOP_TRANS:
     case OACC_LOOP:
       genericize_omp_for_stmt (stmt_p, walk_subtrees, data, func, lh);
       break;
diff --git a/gcc/c-family/c-omp.cc b/gcc/c-family/c-omp.cc
index f72ca4c6acd..85ba9c528c8 100644
--- a/gcc/c-family/c-omp.cc
+++ b/gcc/c-family/c-omp.cc
@@ -3212,9 +3212,9 @@ const struct c_omp_directive c_omp_directives[] = {
   { "teams", nullptr, nullptr, PRAGMA_OMP_TEAMS,
     C_OMP_DIR_CONSTRUCT, true },
   { "threadprivate", nullptr, nullptr, PRAGMA_OMP_THREADPRIVATE,
-    C_OMP_DIR_DECLARATIVE, false }
-  /* { "unroll", nullptr, nullptr, PRAGMA_OMP_UNROLL,
-    C_OMP_DIR_CONSTRUCT, false },  */
+    C_OMP_DIR_DECLARATIVE, false },
+ { "unroll", nullptr, nullptr, PRAGMA_OMP_UNROLL,
+    C_OMP_DIR_CONSTRUCT, false },
 };

 /* Find (non-combined/composite) OpenMP directive (if any) which starts
diff --git a/gcc/c-family/c-pragma.cc b/gcc/c-family/c-pragma.cc
index 0d2b333cebb..96a28ac1b0c 100644
--- a/gcc/c-family/c-pragma.cc
+++ b/gcc/c-family/c-pragma.cc
@@ -1593,6 +1593,7 @@ static const struct omp_pragma_def omp_pragmas_simd[] = {
   { "target", PRAGMA_OMP_TARGET },
   { "taskloop", PRAGMA_OMP_TASKLOOP },
   { "teams", PRAGMA_OMP_TEAMS },
+  { "unroll", PRAGMA_OMP_UNROLL },
 };

 void
diff --git a/gcc/c-family/c-pragma.h b/gcc/c-family/c-pragma.h
index 9cc95ab3ee3..6686abdc94d 100644
--- a/gcc/c-family/c-pragma.h
+++ b/gcc/c-family/c-pragma.h
@@ -81,8 +81,9 @@ enum pragma_kind {
   PRAGMA_OMP_TASKYIELD,
   PRAGMA_OMP_THREADPRIVATE,
   PRAGMA_OMP_TEAMS,
+  PRAGMA_OMP_UNROLL,
   /* PRAGMA_OMP__LAST_ should be equal to the last PRAGMA_OMP_* code.  */
-  PRAGMA_OMP__LAST_ = PRAGMA_OMP_TEAMS,
+  PRAGMA_OMP__LAST_ = PRAGMA_OMP_UNROLL,

   PRAGMA_GCC_PCH_PREPROCESS,
   PRAGMA_IVDEP,
@@ -118,6 +119,7 @@ enum pragma_omp_clause {
   PRAGMA_OMP_CLAUSE_FIRSTPRIVATE,
   PRAGMA_OMP_CLAUSE_FOR,
   PRAGMA_OMP_CLAUSE_FROM,
+  PRAGMA_OMP_CLAUSE_FULL,
   PRAGMA_OMP_CLAUSE_GRAINSIZE,
   PRAGMA_OMP_CLAUSE_HAS_DEVICE_ADDR,
   PRAGMA_OMP_CLAUSE_HINT,
@@ -140,6 +142,7 @@ enum pragma_omp_clause {
   PRAGMA_OMP_CLAUSE_ORDER,
   PRAGMA_OMP_CLAUSE_ORDERED,
   PRAGMA_OMP_CLAUSE_PARALLEL,
+  PRAGMA_OMP_CLAUSE_PARTIAL,
   PRAGMA_OMP_CLAUSE_PRIORITY,
   PRAGMA_OMP_CLAUSE_PRIVATE,
   PRAGMA_OMP_CLAUSE_PROC_BIND,
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 21bc3167ce2..9d875befccc 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -13471,6 +13471,8 @@ c_parser_omp_clause_name (c_parser *parser)
            result = PRAGMA_OMP_CLAUSE_FIRSTPRIVATE;
          else if (!strcmp ("from", p))
            result = PRAGMA_OMP_CLAUSE_FROM;
+         else if (!strcmp ("full", p))
+           result = PRAGMA_OMP_CLAUSE_FULL;
          break;
        case 'g':
          if (!strcmp ("gang", p))
@@ -13545,6 +13547,8 @@ c_parser_omp_clause_name (c_parser *parser)
        case 'p':
          if (!strcmp ("parallel", p))
            result = PRAGMA_OMP_CLAUSE_PARALLEL;
+         else if (!strcmp ("partial", p))
+           result = PRAGMA_OMP_CLAUSE_PARTIAL;
          else if (!strcmp ("present", p))
            result = PRAGMA_OACC_CLAUSE_PRESENT;
          /* As of OpenACC 2.5, these are now aliases of the non-present_or
@@ -13639,12 +13643,15 @@ c_parser_omp_clause_name (c_parser *parser)

 /* Validate that a clause of the given type does not already exist.  */

-static void
+static bool
 check_no_duplicate_clause (tree clauses, enum omp_clause_code code,
                           const char *name)
 {
-  if (tree c = omp_find_clause (clauses, code))
+  tree c = omp_find_clause (clauses, code);
+  if (c)
     error_at (OMP_CLAUSE_LOCATION (c), "too many %qs clauses", name);
+
+  return c == NULL_TREE;
 }

 /* OpenACC 2.0
@@ -17448,6 +17455,65 @@ c_parser_omp_clause_uniform (c_parser *parser, tree list)
   return list;
 }

+/* OpenMP 5.1
+   full */
+
+static tree
+c_parser_omp_clause_unroll_full (c_parser *parser, tree list)
+{
+  if (!check_no_duplicate_clause (list, OMP_CLAUSE_UNROLL_FULL, "full"))
+    return list;
+
+  location_t loc = c_parser_peek_token (parser)->location;
+  tree c = build_omp_clause (loc, OMP_CLAUSE_UNROLL_FULL);
+  OMP_CLAUSE_CHAIN (c) = list;
+  return c;
+}
+
+/* OpenMP 5.1
+   partial ( constant-expression ) */
+
+static tree
+c_parser_omp_clause_unroll_partial (c_parser *parser, tree list)
+{
+  if (!check_no_duplicate_clause (list, OMP_CLAUSE_UNROLL_PARTIAL, "partial"))
+    return list;
+
+  tree c, num = error_mark_node;
+  HOST_WIDE_INT n;
+  location_t loc;
+
+  loc = c_parser_peek_token (parser)->location;
+  c = build_omp_clause (loc, OMP_CLAUSE_UNROLL_PARTIAL);
+  OMP_CLAUSE_UNROLL_PARTIAL_EXPR (c) = NULL_TREE;
+  OMP_CLAUSE_CHAIN (c) = list;
+
+  if (!c_parser_next_token_is (parser, CPP_OPEN_PAREN))
+    return c;
+
+  matching_parens parens;
+  parens.consume_open (parser);
+  num = c_parser_expr_no_commas (parser, NULL).value;
+  parens.skip_until_found_close (parser);
+
+  if (num == error_mark_node)
+    return list;
+
+  mark_exp_read (num);
+  num = c_fully_fold (num, false, NULL);
+  if (!INTEGRAL_TYPE_P (TREE_TYPE (num)) || !tree_fits_shwi_p (num)
+      || (n = tree_to_shwi (num)) <= 0 || (int)n != n)
+    {
+      error_at (loc,
+               "partial argument needs positive constant integer expression");
+      return list;
+   }
+
+  OMP_CLAUSE_UNROLL_PARTIAL_EXPR (c) = num;
+
+  return c;
+}
+
 /* OpenMP 5.0:
    detach ( event-handle ) */

@@ -18042,6 +18108,14 @@ c_parser_omp_all_clauses (c_parser *parser, omp_clause_mask mask,
                                            clauses);
          c_name = "enter";
          break;
+       case PRAGMA_OMP_CLAUSE_FULL:
+         c_name = "full";
+         clauses = c_parser_omp_clause_unroll_full (parser, clauses);
+         break;
+       case PRAGMA_OMP_CLAUSE_PARTIAL:
+         c_name = "partial";
+         clauses = c_parser_omp_clause_unroll_partial (parser, clauses);
+         break;
        default:
          c_parser_error (parser, "expected %<#pragma omp%> clause");
          goto saw_error;
@@ -20169,6 +20243,8 @@ c_parser_omp_scan_loop_body (c_parser *parser, bool open_brace_parsed)
                             "expected %<}%>");
 }

+static bool c_parser_nested_omp_unroll_clauses (c_parser *, tree &);
+
 /* Parse the restricted form of loop statements allowed by OpenACC and OpenMP.
    The real trick here is to determine the loop control variable early
    so that we can push a new decl if necessary to make it private.
@@ -20227,6 +20303,13 @@ c_parser_omp_for_loop (location_t loc, c_parser *parser, enum tree_code code,
   condv = make_tree_vec (count);
   incrv = make_tree_vec (count);

+  if (c_parser_nested_omp_unroll_clauses (parser, clauses)
+      && count > 1)
+    {
+      error_at (loc, "collapse cannot be larger than 1 on an unrolled loop");
+      return NULL;
+    }
+
   if (!c_parser_next_token_is_keyword (parser, RID_FOR))
     {
       c_parser_error (parser, "for statement expected");
@@ -23858,6 +23941,76 @@ c_parser_omp_taskloop (location_t loc, c_parser *parser,
   return ret;
 }

+#define OMP_UNROLL_CLAUSE_MASK                                 \
+       ( (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_PARTIAL)      \
+         | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_FULL) )
+
+/* Parse zero or more '#pragma omp unroll' that follow
+   another directive that requires a canonical loop nest. */
+
+static bool
+c_parser_nested_omp_unroll_clauses (c_parser *parser, tree &clauses)
+{
+  static const char *p_name = "#pragma omp unroll";
+  c_token *tok;
+  bool found_unroll = false;
+  while (c_parser_next_token_is (parser, CPP_PRAGMA)
+        && (tok = c_parser_peek_token (parser),
+            tok->pragma_kind == PRAGMA_OMP_UNROLL))
+    {
+      c_parser_consume_pragma (parser);
+      tree c = c_parser_omp_all_clauses (parser, OMP_UNROLL_CLAUSE_MASK,
+                                        p_name, true);
+      if (c)
+       {
+         gcc_assert (!TREE_CHAIN (c));
+         found_unroll = true;
+         if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_UNROLL_FULL)
+           {
+             error_at (tok->location, "%<full%> clause is invalid here; "
+                       "turns loop into non-loop");
+             continue;
+           }
+       }
+      else
+       {
+         error_at (tok->location, "%<#pragma omp unroll%> without "
+                                  "%<partial%> clause is invalid here; "
+                                  "turns loop into non-loop");
+         continue;
+       }
+
+      clauses = chainon (clauses, c);
+    }
+
+  return found_unroll;
+}
+
+static tree
+c_parser_omp_unroll (location_t loc, c_parser *parser, bool *if_p)
+{
+  tree block, ret;
+  static const char *p_name = "#pragma omp unroll";
+  omp_clause_mask mask = OMP_UNROLL_CLAUSE_MASK;
+
+  tree clauses = c_parser_omp_all_clauses (parser, mask, p_name, false);
+  c_parser_nested_omp_unroll_clauses (parser, clauses);
+
+  if (!clauses)
+    {
+      tree c = build_omp_clause (loc, OMP_CLAUSE_UNROLL_NONE);
+      OMP_CLAUSE_CHAIN (c) = clauses;
+      clauses = c;
+    }
+
+  block = c_begin_compound_stmt (true);
+  ret = c_parser_omp_for_loop (loc, parser, OMP_LOOP_TRANS, clauses, NULL, if_p);
+  block = c_end_compound_stmt (loc, block, true);
+  add_stmt (block);
+
+  return ret;
+}
+
 /* OpenMP 5.1
    #pragma omp nothing new-line  */

@@ -24249,6 +24402,7 @@ c_parser_omp_construct (c_parser *parser, bool *if_p)
   p_kind = c_parser_peek_token (parser)->pragma_kind;
   c_parser_consume_pragma (parser);

+  gcc_assert (parser->in_pragma);
   switch (p_kind)
     {
     case PRAGMA_OACC_ATOMIC:
@@ -24342,6 +24496,9 @@ c_parser_omp_construct (c_parser *parser, bool *if_p)
     case PRAGMA_OMP_ASSUME:
       c_parser_omp_assume (parser, if_p);
       return;
+    case PRAGMA_OMP_UNROLL:
+      stmt = c_parser_omp_unroll (loc, parser, if_p);
+      break;
     default:
       gcc_unreachable ();
     }
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 45bacc06c47..bffea79b441 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -15916,6 +15916,14 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
          pc = &OMP_CLAUSE_CHAIN (c);
          continue;

+       case OMP_CLAUSE_UNROLL_FULL:
+         pc = &OMP_CLAUSE_CHAIN (c);
+         continue;
+
+       case OMP_CLAUSE_UNROLL_PARTIAL:
+         pc = &OMP_CLAUSE_CHAIN (c);
+         continue;
+
        case OMP_CLAUSE_INBRANCH:
        case OMP_CLAUSE_NOTINBRANCH:
          if (branch_seen)
diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 4fecd5616bd..bf81097d780 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -638,6 +638,7 @@ cp_gimplify_expr (tree *expr_p, gimple_seq *pre_p, gimple_seq *post_p)
     case OMP_DISTRIBUTE:
     case OMP_LOOP:
     case OMP_TASKLOOP:
+    case OMP_LOOP_TRANS:
       ret = cp_gimplify_omp_for (expr_p, pre_p);
       break;

@@ -1097,6 +1098,7 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_)
     case OMP_DISTRIBUTE:
     case OMP_LOOP:
     case OMP_TASKLOOP:
+    case OMP_LOOP_TRANS:
     case OACC_LOOP:
       cp_walk_tree (&OMP_FOR_BODY (stmt), cp_fold_r, data, NULL);
       cp_walk_tree (&OMP_FOR_CLAUSES (stmt), cp_fold_r, data, NULL);
@@ -1855,6 +1857,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
     case OMP_FOR:
     case OMP_SIMD:
     case OMP_LOOP:
+    case OMP_LOOP_TRANS:
     case OACC_LOOP:
     case STATEMENT_LIST:
       /* These cases are handled by shared code.  */
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index a277003ea58..7034fdf49a4 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -37204,6 +37204,8 @@ cp_parser_omp_clause_name (cp_parser *parser)
            result = PRAGMA_OMP_CLAUSE_FIRSTPRIVATE;
          else if (!strcmp ("from", p))
            result = PRAGMA_OMP_CLAUSE_FROM;
+         else if (!strcmp ("full", p))
+           result = PRAGMA_OMP_CLAUSE_FULL;
          break;
        case 'g':
          if (!strcmp ("gang", p))
@@ -37278,6 +37280,8 @@ cp_parser_omp_clause_name (cp_parser *parser)
        case 'p':
          if (!strcmp ("parallel", p))
            result = PRAGMA_OMP_CLAUSE_PARALLEL;
+         if (!strcmp ("partial", p))
+           result = PRAGMA_OMP_CLAUSE_PARTIAL;
          else if (!strcmp ("present", p))
            result = PRAGMA_OACC_CLAUSE_PRESENT;
          else if (!strcmp ("present_or_copy", p)
@@ -37368,12 +37372,15 @@ cp_parser_omp_clause_name (cp_parser *parser)

 /* Validate that a clause of the given type does not already exist.  */

-static void
+static bool
 check_no_duplicate_clause (tree clauses, enum omp_clause_code code,
                           const char *name, location_t location)
 {
-  if (omp_find_clause (clauses, code))
+  bool found = omp_find_clause (clauses, code);
+  if (found)
     error_at (location, "too many %qs clauses", name);
+
+  return !found;
 }

 /* OpenMP 2.5:
@@ -39459,6 +39466,56 @@ cp_parser_omp_clause_thread_limit (cp_parser *parser, tree list,
   return c;
 }

+/* OpenMP 5.1
+   full */
+
+static tree
+cp_parser_omp_clause_unroll_full (tree list, location_t loc)
+{
+  if (!check_no_duplicate_clause (list, OMP_CLAUSE_UNROLL_FULL, "full", loc))
+    return list;
+
+  tree c = build_omp_clause (loc, OMP_CLAUSE_UNROLL_FULL);
+  OMP_CLAUSE_CHAIN (c) = list;
+  return c;
+}
+
+/* OpenMP 5.1
+   partial ( constant-expression ) */
+
+static tree
+cp_parser_omp_clause_unroll_partial (cp_parser *parser, tree list,
+                                    location_t loc)
+{
+  if (!check_no_duplicate_clause (list, OMP_CLAUSE_UNROLL_PARTIAL, "partial",
+                                 loc))
+    return list;
+
+  tree c, num = error_mark_node;
+  c = build_omp_clause (loc, OMP_CLAUSE_UNROLL_PARTIAL);
+  OMP_CLAUSE_UNROLL_PARTIAL_EXPR (c) = NULL_TREE;
+  OMP_CLAUSE_CHAIN (c) = list;
+
+  if (!cp_lexer_next_token_is (parser->lexer, CPP_OPEN_PAREN))
+    return c;
+
+  matching_parens parens;
+  parens.consume_open (parser);
+  num = cp_parser_constant_expression (parser);
+  cp_parser_skip_to_closing_parenthesis (parser, /*recovering=*/true,
+                                        /*or_comma=*/false,
+                                        /*consume_paren=*/true);
+
+  if (num == error_mark_node)
+    return list;
+
+  mark_exp_read (num);
+  num = fold_non_dependent_expr (num);
+
+  OMP_CLAUSE_UNROLL_PARTIAL_EXPR (c) = num;
+  return c;
+}
+
 /* OpenMP 4.0:
    aligned ( variable-list )
    aligned ( variable-list : constant-expression )  */
@@ -41441,6 +41498,15 @@ cp_parser_omp_all_clauses (cp_parser *parser, omp_clause_mask mask,
                                            clauses);
          c_name = "enter";
          break;
+       case PRAGMA_OMP_CLAUSE_PARTIAL:
+         clauses = cp_parser_omp_clause_unroll_partial (parser, clauses,
+                                                        token->location);
+         c_name = "partial";
+         break;
+       case PRAGMA_OMP_CLAUSE_FULL:
+         clauses = cp_parser_omp_clause_unroll_full(clauses, token->location);
+         c_name = "full";
+         break;
        default:
          cp_parser_error (parser, "expected %<#pragma omp%> clause");
          goto saw_error;
@@ -43565,6 +43631,8 @@ cp_parser_omp_scan_loop_body (cp_parser *parser)
   braces.require_close (parser);
 }

+static bool cp_parser_nested_omp_unroll_clauses (cp_parser *, tree &);
+
 /* Parse the restricted form of the for statement allowed by OpenMP.  */

 static tree
@@ -43622,6 +43690,15 @@ cp_parser_omp_for_loop (cp_parser *parser, enum tree_code code, tree clauses,

   loc_first = cp_lexer_peek_token (parser->lexer)->location;

+  if (cp_parser_nested_omp_unroll_clauses (parser, clauses)
+      && count > 1)
+    {
+      error_at (loc_first,
+               "collapse cannot be larger than 1 on an unrolled loop");
+      return NULL;
+    }
+
+
   for (i = 0; i < count; i++)
     {
       int bracecount = 0;
@@ -45657,6 +45734,79 @@ cp_parser_omp_target (cp_parser *parser, cp_token *pragma_tok,
   return true;
 }

+#define OMP_UNROLL_CLAUSE_MASK                                 \
+       ( (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_PARTIAL)      \
+         | (OMP_CLAUSE_MASK_1 << PRAGMA_OMP_CLAUSE_FULL) )
+
+/* Parse zero or more '#pragma omp unroll' that follow
+   another directive that requires a canonical loop nest. */
+
+static bool
+cp_parser_nested_omp_unroll_clauses (cp_parser *parser, tree &clauses)
+{
+  static const char *p_name = "#pragma omp unroll";
+  cp_token *tok;
+  bool unroll_found = false;
+  while (cp_lexer_next_token_is (parser->lexer, CPP_PRAGMA)
+        && (tok = cp_lexer_peek_token (parser->lexer),
+            cp_parser_pragma_kind (tok) == PRAGMA_OMP_UNROLL))
+    {
+      cp_lexer_consume_token (parser->lexer);
+      gcc_assert (tok->type == CPP_PRAGMA);
+      parser->lexer->in_pragma = true;
+      tree c = cp_parser_omp_all_clauses (parser, OMP_UNROLL_CLAUSE_MASK,
+                                         p_name, tok);
+      if (c)
+       {
+         gcc_assert (!TREE_CHAIN (c));
+         unroll_found = true;
+         if (OMP_CLAUSE_CODE (c) == OMP_CLAUSE_UNROLL_FULL)
+           {
+             error_at (tok->location, "%<full%> clause is invalid here; "
+                       "turns loop into non-loop");
+             continue;
+           }
+
+         c = finish_omp_clauses (c, C_ORT_OMP);
+       }
+      else
+       {
+         error_at (tok->location, "%<#pragma omp unroll%> without "
+                                  "%<partial%> clause is invalid here; "
+                                  "turns loop into non-loop");
+         continue;
+       }
+      clauses = chainon (clauses, c);
+    }
+  return unroll_found;
+}
+
+static tree
+cp_parser_omp_unroll (cp_parser *parser, cp_token *tok, bool *if_p)
+{
+  tree block, ret;
+  static const char *p_name = "#pragma omp unroll";
+  omp_clause_mask mask = OMP_UNROLL_CLAUSE_MASK;
+
+  tree clauses = cp_parser_omp_all_clauses (parser, mask, p_name, tok, false);
+
+  if (!clauses)
+    {
+      tree c = build_omp_clause (tok->location, OMP_CLAUSE_UNROLL_NONE);
+      OMP_CLAUSE_CHAIN (c) = clauses;
+      clauses = c;
+    }
+
+  cp_parser_nested_omp_unroll_clauses (parser, clauses);
+
+  block = begin_omp_structured_block ();
+  ret = cp_parser_omp_for_loop (parser, OMP_LOOP_TRANS, clauses, NULL, if_p);
+  block = finish_omp_structured_block (block);
+  add_stmt (block);
+
+  return ret;
+}
+
 /* OpenACC 2.0:
    # pragma acc cache (variable-list) new-line
 */
@@ -48750,6 +48900,9 @@ cp_parser_omp_construct (cp_parser *parser, cp_token *pragma_tok, bool *if_p)
     case PRAGMA_OMP_ASSUME:
       cp_parser_omp_assume (parser, pragma_tok, if_p);
       return;
+    case PRAGMA_OMP_UNROLL:
+      stmt = cp_parser_omp_unroll (parser, pragma_tok, if_p);
+      break;
     default:
       gcc_unreachable ();
     }
@@ -49376,6 +49529,13 @@ cp_parser_pragma (cp_parser *parser, enum pragma_context context, bool *if_p)
       cp_parser_omp_construct (parser, pragma_tok, if_p);
       pop_omp_privatization_clauses (stmt);
       return true;
+    case PRAGMA_OMP_UNROLL:
+      if (context != pragma_stmt && context != pragma_compound)
+       goto bad_stmt;
+      stmt = push_omp_privatization_clauses (false);
+      cp_parser_omp_construct (parser, pragma_tok, if_p);
+      pop_omp_privatization_clauses (stmt);
+      return true;

     case PRAGMA_OMP_REQUIRES:
       if (context != pragma_external)
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 40deedc9ba9..63b2d1f7a45 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -18086,6 +18086,7 @@ tsubst_omp_clauses (tree clauses, enum c_omp_region_type ort,
        case OMP_CLAUSE_ASYNC:
        case OMP_CLAUSE_WAIT:
        case OMP_CLAUSE_DETACH:
+       case OMP_CLAUSE_UNROLL_PARTIAL:
          OMP_CLAUSE_OPERAND (nc, 0)
            = tsubst_expr (OMP_CLAUSE_OPERAND (oc, 0), args, complain, in_decl);
          break;
@@ -18169,6 +18170,8 @@ tsubst_omp_clauses (tree clauses, enum c_omp_region_type ort,
        case OMP_CLAUSE_IF_PRESENT:
        case OMP_CLAUSE_FINALIZE:
        case OMP_CLAUSE_NOHOST:
+       case OMP_CLAUSE_UNROLL_FULL:
+       case OMP_CLAUSE_UNROLL_NONE:
          break;
        default:
          gcc_unreachable ();
@@ -19437,6 +19440,7 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl)
     case OMP_SIMD:
     case OMP_DISTRIBUTE:
     case OMP_TASKLOOP:
+    case OMP_LOOP_TRANS:
     case OACC_LOOP:
       {
        tree clauses, body, pre_body;
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 99a76e3ed65..ac49502eea4 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -6779,6 +6779,7 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
   bool mergeable_seen = false;
   bool implicit_moved = false;
   bool target_in_reduction_seen = false;
+  bool unroll_full_seen = false;

   bitmap_obstack_initialize (NULL);
   bitmap_initialize (&generic_head, &bitmap_default_obstack);
@@ -8822,6 +8823,61 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
            }
          break;

+       case OMP_CLAUSE_UNROLL_FULL:
+         if (unroll_full_seen)
+           {
+             error_at (OMP_CLAUSE_LOCATION (c),
+                       "%<full%> appears more than once");
+             remove = true;
+           }
+         unroll_full_seen = true;
+         break;
+
+       case OMP_CLAUSE_UNROLL_PARTIAL:
+         {
+
+           tree t = OMP_CLAUSE_UNROLL_PARTIAL_EXPR (c);
+
+           if (!t)
+             break;
+
+           if (t == error_mark_node)
+             remove = true;
+           else if (!type_dependent_expression_p (t)
+                    && !INTEGRAL_TYPE_P (TREE_TYPE (t)))
+             {
+               error_at (OMP_CLAUSE_LOCATION (c),
+                         "partial argument needs integral type");
+               remove = true;
+             }
+           else
+             {
+               t = mark_rvalue_use (t);
+               if (!processing_template_decl)
+                 {
+                   t = maybe_constant_value (t);
+
+                   int n;
+                   if (!INTEGRAL_TYPE_P (TREE_TYPE (t))
+                       || !tree_fits_shwi_p (t)
+                       || (n = tree_to_shwi (t)) <= 0 || (int)n != n)
+                     {
+                       error_at (OMP_CLAUSE_LOCATION (c),
+                                 "partial argument needs positive constant "
+                                 "integer expression");
+                       remove = true;
+                     }
+                   t = fold_build_cleanup_point_expr (TREE_TYPE (t), t);
+                 }
+             }
+
+           OMP_CLAUSE_UNROLL_PARTIAL_EXPR (c) = t;
+         }
+         break;
+
+       case OMP_CLAUSE_UNROLL_NONE:
+         break;
+
        default:
          gcc_unreachable ();
        }
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-1.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-1.c
new file mode 100644
index 00000000000..d496dc29053
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-1.c
@@ -0,0 +1,133 @@
+extern void dummy (int);
+
+void
+test1 ()
+{
+#pragma omp unroll partial
+  for (int i = 0; i < 100; ++i)
+    dummy (i);
+}
+
+void
+test2 ()
+{
+#pragma omp unroll partial(10)
+  for (int i = 0; i < 100; ++i)
+    dummy (i);
+}
+
+void
+test3 ()
+{
+#pragma omp unroll full
+  for (int i = 0; i < 100; ++i)
+    dummy (i);
+}
+
+void
+test4 ()
+{
+#pragma omp unroll full
+  for (int i = 0; i > 100; ++i)
+    dummy (i);
+}
+
+void
+test5 ()
+{
+#pragma omp unroll full
+  for (int i = 1; i <= 100; ++i)
+    dummy (i);
+}
+
+void
+test6 ()
+{
+#pragma omp unroll full
+  for (int i = 200; i >= 100; i--)
+    dummy (i);
+}
+
+void
+test7 ()
+{
+#pragma omp unroll full
+  for (int i = -100; i > 100; ++i)
+    dummy (i);
+}
+
+void
+test8 ()
+{
+#pragma omp unroll full
+  for (int i = 100; i > -200; --i)
+    dummy (i);
+}
+
+void
+test9 ()
+{
+#pragma omp unroll full
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+}
+
+void
+test10 ()
+{
+#pragma omp unroll full
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+}
+
+void
+test12 ()
+{
+#pragma omp unroll full
+#pragma omp unroll partial
+#pragma omp unroll partial
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+}
+
+void
+test13 ()
+{
+  for (int i = 0; i < 100; ++i)
+#pragma omp unroll full
+#pragma omp unroll partial
+#pragma omp unroll partial
+  for (int j = -300; j != 100; ++j)
+    dummy (i);
+}
+
+void
+test14 ()
+{
+  #pragma omp for
+  for (int i = 0; i < 100; ++i)
+#pragma omp unroll full
+#pragma omp unroll partial
+#pragma omp unroll partial
+  for (int j = -300; j != 100; ++j)
+    dummy (i);
+}
+
+void
+test15 ()
+{
+  #pragma omp for
+  for (int i = 0; i < 100; ++i)
+    {
+
+    dummy (i);
+
+#pragma omp unroll full
+#pragma omp unroll partial
+#pragma omp unroll partial
+  for (int j = -300; j != 100; ++j)
+    dummy (j);
+
+  dummy (i);
+    }
+ }
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-2.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-2.c
new file mode 100644
index 00000000000..8f7c3088a2e
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-2.c
@@ -0,0 +1,99 @@
+/* { dg-prune-output "error: invalid controlling predicate" } */
+/* { dg-additional-options "-std=c++11" { target c++} } */
+
+extern void dummy (int);
+
+void
+test ()
+{
+#pragma omp unroll partial
+#pragma omp unroll full /* { dg-error {'full' clause is invalid here; turns loop into non-loop} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll full /* { dg-error {'full' clause is invalid here; turns loop into non-loop} } */
+#pragma omp unroll partial
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll full /* { dg-error {'full' clause is invalid here; turns loop into non-loop} } */
+#pragma omp unroll full /* { dg-error {'full' clause is invalid here; turns loop into non-loop} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll partial partial /* { dg-error {too many 'partial' clauses} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp unroll full full /* { dg-error {too many 'full' clauses} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp unroll partial
+#pragma omp unroll /* { dg-error {'#pragma omp unroll' without 'partial' clause is invalid here; turns loop into non-loop} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll /* { dg-error {'#pragma omp unroll' without 'partial' clause is invalid here; turns loop into non-loop} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+  int i;
+#pragma omp for
+#pragma omp unroll( /* { dg-error {expected '#pragma omp' clause before '\(' token} } */
+  /* { dg-error {'#pragma omp unroll' without 'partial' clause is invalid here; turns loop into non-loop} "" { target *-*-* } .-1 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll foo /* { dg-error {expected '#pragma omp' clause before 'foo'} } */
+  /* { dg-error {'#pragma omp unroll' without 'partial' clause is invalid here; turns loop into non-loop} "" { target *-*-* } .-1 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp unroll partial( /* { dg-error {expected expression before end of line} "" { target c } } */
+  /* { dg-error {expected primary-expression before end of line} "" { target c++ } .-1 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp unroll partial() /* { dg-error {expected expression before '\)' token} "" { target c } } */
+  /* { dg-error {expected primary-expression before '\)' token} "" { target c++ } .-1 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp unroll partial(i)
+ /* { dg-error {the value of 'i' is not usable in a constant expression} "" { target c++ } .-1 } */
+ /* { dg-error {partial argument needs positive constant integer expression} "" { target c } .-2 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp unroll parti /* { dg-error {expected '#pragma omp' clause before 'parti'} } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll partial(1)
+#pragma omp unroll parti /* { dg-error {expected '#pragma omp' clause before 'parti'} } */
+  /* { dg-error {'#pragma omp unroll' without 'partial' clause is invalid here; turns loop into non-loop} "" { target *-*-* } .-1 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll partial(1)
+#pragma omp unroll parti /* { dg-error {expected '#pragma omp' clause before 'parti'} } */
+  /* { dg-error {'#pragma omp unroll' without 'partial' clause is invalid here; turns loop into non-loop} "" { target *-*-* } .-1 } */
+  for (int i = -300; i != 100; ++i)
+    dummy (i);
+
+int sum = 0;
+#pragma omp parallel for reduction(+ : sum) collapse(2) /* { dg-error {collapse cannot be larger than 1 on an unrolled loop} "" { target c } } */
+#pragma omp unroll partial(1) /* { dg-error {collapse cannot be larger than 1 on an unrolled loop} "" { target c++ } } */
+  for (int i = 3; i < 10; ++i)
+    for (int j = -2; j < 7; ++j)
+      sum++;
+}
+
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-3.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-3.c
new file mode 100644
index 00000000000..7ace5657b26
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-3.c
@@ -0,0 +1,18 @@
+/* { dg-additional-options "-fdump-tree-omp_transform_loops" }
+ * { dg-additional-options "-fdump-tree-original" } */
+
+extern void dummy (int);
+
+void
+test1 ()
+{
+  int i;
+#pragma omp unroll full
+  for (int i = 0; i < 10; i++)
+    dummy (i);
+}
+
+ /* Loop should be removed with 10 copies of the body remaining
+  * { dg-final { scan-tree-dump-times "dummy" 10 "omp_transform_loops" } }
+  * { dg-final { scan-tree-dump "#pragma omp loop_transform" "original" } }
+  * { dg-final { scan-tree-dump-not "#pragma omp" "omp_transform_loops" } } */
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-4.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-4.c
new file mode 100644
index 00000000000..5e473a099d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-4.c
@@ -0,0 +1,19 @@
+/* { dg-additional-options "-fdump-tree-omp_transform_loops" }
+ * { dg-additional-options "-fdump-tree-original" } */
+
+extern void dummy (int);
+
+void
+test1 ()
+{
+  int i;
+#pragma omp unroll
+  for (int i = 0; i < 100; i++)
+    dummy (i);
+}
+
+/* Loop should not be unrolled, but the internal representation should be lowered
+ * { dg-final { scan-tree-dump "#pragma omp loop_transform" "original" } }
+ * { dg-final { scan-tree-dump-not "#pragma omp" "omp_transform_loops" } }
+ * { dg-final { scan-tree-dump-times "dummy" 1 "omp_transform_loops" } }
+ * { dg-final { scan-tree-dump-times {if \(i < .+?.+goto.+else goto.*?$} 1 "omp_transform_loops" } } */
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-5.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-5.c
new file mode 100644
index 00000000000..9d5101bdc60
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-5.c
@@ -0,0 +1,19 @@
+/* { dg-additional-options "-fdump-tree-omp_transform_loops -fopt-info-omp-optimized-missed" }
+ * { dg-additional-options "-fdump-tree-original" } */
+
+extern void dummy (int);
+
+void
+test1 ()
+{
+  int i;
+#pragma omp unroll partial  /* { dg-optimized {'partial' clause without unrolling factor turned into 'partial\(5\)' clause} } */
+  for (int i = 0; i < 100; i++)
+    dummy (i);
+}
+
+/* Loop should be unrolled 5 times and the internal representation should be lowered
+ * { dg-final { scan-tree-dump "#pragma omp loop_transform" "original" } }
+ * { dg-final { scan-tree-dump-not "#pragma omp" "omp_transform_loops" } }
+ * { dg-final { scan-tree-dump-times "dummy" 5 "omp_transform_loops" } }
+ * { dg-final { scan-tree-dump-times {if \(i < .+?.+goto.+else goto.*?$} 1 "omp_transform_loops" } } */
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-6.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-6.c
new file mode 100644
index 00000000000..ee2d000239d
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-6.c
@@ -0,0 +1,20 @@
+/* { dg-additional-options "--param=omp-unroll-default-factor=100" }
+ * { dg-additional-options "-fdump-tree-omp_transform_loops -fopt-info-omp-optimized-missed" }
+ * { dg-additional-options "-fdump-tree-original" } */
+
+extern void dummy (int);
+
+void
+test1 ()
+{
+  int i;
+#pragma omp unroll /* { dg-optimized {added 'partial\(100\)' clause to 'omp unroll' directive} } */
+  for (int i = 0; i < 100; i++)
+    dummy (i);
+}
+
+/* Loop should be unrolled 5 times and the internal representation should be lowered
+ * { dg-final { scan-tree-dump "#pragma omp loop_transform unroll_none" "original" } }
+ * { dg-final { scan-tree-dump-not "#pragma omp" "omp_transform_loops" } }
+ * { dg-final { scan-tree-dump-times "dummy" 100 "omp_transform_loops" } }
+ * { dg-final { scan-tree-dump-times {if \(i < .+?.+goto.+else goto.*?$} 1 "omp_transform_loops" } } */
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-7.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-7.c
new file mode 100644
index 00000000000..0458cb030a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-7.c
@@ -0,0 +1,144 @@
+/* { dg-do run } */
+/* { dg-options "-O0 -fopenmp-simd" } */
+
+#include <stdio.h>
+
+#define ASSERT_EQ(var, val) if (var != val) { fprintf (stderr, "%s:%d: Unexpected value %d\n", __FILE__, __LINE__, var); \
+    __builtin_abort (); }
+
+#define ASSERT_EQ_PTR(var, ptr) if (var != ptr) { fprintf (stderr, "%s:%d: Unexpected value %p\n", __FILE__, __LINE__, var); \
+    __builtin_abort (); }
+
+int
+test1 (int data[10])
+{
+  int iter = 0;
+  int *i;
+  #pragma omp unroll partial(8)
+  for (i = data; i < data + 10 ; i++)
+       {
+         ASSERT_EQ (*i, data[iter]);
+         ASSERT_EQ_PTR (i, data + iter);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+test2 (int data[10])
+{
+  int iter = 0;
+  int *i;
+  #pragma omp unroll partial(8)
+  for (i = data; i < data + 10 ; i=i+2)
+       {
+         ASSERT_EQ_PTR (i, data + 2 * iter);
+         ASSERT_EQ (*i, data[2 * iter]);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+test3 (int data[10])
+{
+  int iter = 0;
+  int *i;
+  #pragma omp unroll partial(8)
+  for (i = data; i <= data + 9 ; i=i+2)
+       {
+         ASSERT_EQ (*i, data[2 * iter]);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+test4 (int data[10])
+{
+  int iter = 0;
+  int *i;
+  #pragma omp unroll partial(8)
+  for (i = data; i != data + 10 ; i=i+1)
+       {
+         ASSERT_EQ (*i, data[iter]);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+test5 (int data[10])
+{
+  int iter = 0;
+  int *i;
+  #pragma omp unroll partial(7)
+  for (i = data + 9; i >= data ; i--)
+       {
+         ASSERT_EQ (*i, data[9 - iter]);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+test6 (int data[10])
+{
+  int iter = 0;
+  int *i;
+  #pragma omp unroll partial(7)
+  for (i = data + 9; i > data - 1 ; i--)
+       {
+         ASSERT_EQ (*i, data[9 - iter]);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+test7 (int data[10])
+{
+  int iter = 0;
+  #pragma omp unroll partial(7)
+  for (int *i = data + 9; i != data - 1 ; i--)
+       {
+         ASSERT_EQ (*i, data[9 - iter]);
+         iter++;
+       }
+
+  return iter;
+}
+
+int
+main ()
+{
+  int iter_count;
+  int data[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+
+  iter_count = test1 (data);
+  ASSERT_EQ (iter_count, 10);
+
+  iter_count = test2 (data);
+  ASSERT_EQ (iter_count, 5);
+
+  iter_count = test3 (data);
+  ASSERT_EQ (iter_count, 5);
+
+  iter_count = test4 (data);
+  ASSERT_EQ (iter_count, 10);
+
+  iter_count = test5 (data);
+  ASSERT_EQ (iter_count, 10);
+
+  iter_count = test6 (data);
+  ASSERT_EQ (iter_count, 10);
+
+  iter_count = test7 (data);
+  ASSERT_EQ (iter_count, 10);
+}
diff --git a/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-simd-1.c b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-simd-1.c
new file mode 100644
index 00000000000..1cd4d6e7322
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/loop-transforms/unroll-simd-1.c
@@ -0,0 +1,84 @@
+/* { dg-options "-fno-openmp -fopenmp-simd" } */
+/* { dg-do run } */
+/* { dg-additional-options "-fdump-tree-original" } */
+/* { dg-additional-options "-fdump-tree-omp_transform_loops" } */
+
+#include <stdio.h>
+
+int compute_sum1 ()
+{
+  int sum = 0;
+  int i,j;
+
+#pragma omp simd reduction(+:sum)
+  for (i = 3; i < 10; ++i)
+  #pragma omp unroll full
+  for (j = -2; j < 7; ++j)
+    sum++;
+
+  if (j != 7)
+    __builtin_abort;
+
+  return sum;
+}
+
+int compute_sum2()
+{
+  int sum = 0;
+  int i,j;
+#pragma omp simd reduction(+:sum)
+#pragma omp unroll partial(5)
+  for (i = 3; i < 10; ++i)
+  for (j = -2; j < 7; ++j)
+    sum++;
+
+  if (j != 7)
+    __builtin_abort;
+
+  return sum;
+}
+
+int compute_sum3()
+{
+  int sum = 0;
+  int i,j;
+#pragma omp simd reduction(+:sum)
+#pragma omp unroll partial(1)
+  for (i = 3; i < 10; ++i)
+  for (j = -2; j < 7; ++j)
+    sum++;
+
+  if (j != 7)
+    __builtin_abort;
+
+  return sum;
+}
+
+int main ()
+{
+  int result = compute_sum1 ();
+  if (result != 7 * 9)
+    {
+      fprintf (stderr, "%d: Wrong result %d\n", __LINE__, result);
+      __builtin_abort ();
+    }
+
+  result = compute_sum1 ();
+  if (result != 7 * 9)
+    {
+      fprintf (stderr, "%d: Wrong result %d\n", __LINE__, result);
+      __builtin_abort ();
+    }
+
+  result = compute_sum3 ();
+  if (result != 7 * 9)
+    {
+      fprintf (stderr, "%d: Wrong result %d\n", __LINE__, result);
+      __builtin_abort ();
+    }
+
+  return 0;
+}
+
+/* { dg-final { scan-tree-dump {omp loop_transform} "original" } } */
+/* { dg-final { scan-tree-dump-not {omp loop_transform} "omp_transform_loops" } } */
diff --git a/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-1.C b/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-1.C
new file mode 100644
index 00000000000..cba37c88ebe
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-1.C
@@ -0,0 +1,42 @@
+// { dg-do compile }
+// { dg-additional-options "-std=c++11" }
+#include <vector>
+
+extern void dummy (int);
+
+void
+test1 ()
+{
+  std::vector<int> v;
+
+  for (unsigned i = 0; i < 1000; i++)
+    v.push_back (i);
+
+#pragma omp for
+  for (int i : v)
+    dummy (i);
+
+#pragma omp unroll partial(5)
+  for (int i : v)
+    dummy (i);
+}
+
+void
+test2 ()
+{
+  std::vector<std::vector<int>> v;
+
+  for (unsigned i = 0; i < 10; i++)
+    {
+      std::vector<int> u;
+      for (unsigned j = 0; j < 10; j++)
+       u.push_back (j);
+      v.push_back (u);
+    }
+
+#pragma omp for
+#pragma omp unroll partial(5)
+  for (auto u : v)
+    for (int i : u)
+      dummy (i);
+}
diff --git a/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-2.C b/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-2.C
new file mode 100644
index 00000000000..f606f3de757
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-2.C
@@ -0,0 +1,47 @@
+// { dg-do link }
+// { dg-additional-options "-std=c++11" }
+#include <vector>
+
+extern void dummy (int);
+
+template<class T, int U1, int U2, int U3> void
+test_template ()
+{
+  std::vector<int> v;
+
+  for (unsigned i = 0; i < 1000; i++)
+    v.push_back (i);
+
+#pragma omp for
+  for (int i : v)
+    dummy (i);
+
+#pragma omp unroll partial(U1)
+  for (T i : v)
+    dummy (i);
+
+#pragma omp unroll partial(U2) // { dg-error {partial argument needs positive constant integer expression} }
+  for (T i : v)
+    dummy (i);
+
+#pragma omp unroll partial(U3) // { dg-error {partial argument needs positive constant integer expression} }
+  for (T i : v)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll partial(U1)
+  for (T i : v)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll partial(U2) // { dg-error {partial argument needs positive constant integer expression} }
+  for (T i : v)
+    dummy (i);
+
+#pragma omp for
+#pragma omp unroll partial(U3) // { dg-error {partial argument needs positive constant integer expression} }
+  for (T i : v)
+    dummy (i);
+}
+
+void test () { test_template <long, 5,-2, 0> (); };
diff --git a/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-3.C b/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-3.C
new file mode 100644
index 00000000000..ae9f5500360
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gomp/loop-transforms/unroll-3.C
@@ -0,0 +1,37 @@
+// { dg-do compile }
+// { dg-additional-options "-std=c++11" }
+// { dg-additional-options "-fdump-tree-omp_transform_loops -fopt-info-omp-optimized-missed" }
+// { dg-additional-options "-fdump-tree-original" }
+#include <vector>
+
+extern void dummy (int);
+
+constexpr unsigned fib (unsigned n)
+{
+  return n <= 2 ? 1 : fib (n-1) + fib (n-2);
+}
+
+void
+test1 ()
+{
+  std::vector<int> v;
+
+  for (unsigned i = 0; i < 1000; i++)
+    v.push_back (i);
+
+#pragma omp unroll partial(fib(10))
+  for (int i : v)
+    dummy (i);
+}
+
+
+// Loop should be unrolled fib(10) = 55 times
+// ! { dg-final { scan-tree-dump {#pragma omp loop_transform unroll_partial\(55\)} "original" } }
+// ! { dg-final { scan-tree-dump-not "#pragma omp" "omp_transform_loops" } }
+// ! { dg-final { scan-tree-dump-times "dummy" 55 "omp_transform_loops" } }
+
+// There should be one loop that fills the vector ...
+// ! { dg-final { scan-tree-dump-times {if \(i.*? <= .+?.+goto.+else goto.*?$} 1 "omp_transform_loops" } }
+
+// ... and one resulting from the lowering of the unrolled loop
+// ! { dg-final { scan-tree-dump-times {if \(D\.[0-9]+ < retval.+?.+goto.+else goto.*?$} 1 "omp_transform_loops" } }
diff --git a/libgomp/testsuite/libgomp.c++/loop-transforms/unroll-1.C b/libgomp/testsuite/libgomp.c++/loop-transforms/unroll-1.C
new file mode 100644
index 00000000000..004eef91649
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/loop-transforms/unroll-1.C
@@ -0,0 +1,73 @@
+// { dg-additional-options "-std=c++11" }
+// { dg-additional-options "-O0" }
+
+#include <vector>
+#include <stdio.h>
+
+constexpr unsigned fib (unsigned n)
+{
+  return n <= 2 ? 1 : fib (n-1) + fib (n-2);
+}
+
+int
+test1 ()
+{
+  std::vector<int> v;
+
+  for (unsigned i = 0; i <= 9; i++)
+    v.push_back (1);
+
+  int sum = 0;
+  for (int k = 0; k < 10; k++)
+#pragma omp unroll partial(fib(3))
+    for (int i : v) {
+      for (int j = 8; j != -2; --j)
+       sum = sum + i;
+    }
+
+  return sum;
+}
+
+int
+test2 ()
+{
+  std::vector<int> v;
+
+  for (unsigned i = 0; i <= 10; i++)
+    v.push_back (i);
+
+  int sum = 0;
+#pragma omp parallel for reduction(+:sum)
+  for (int k = 0; k < 10; k++)
+#pragma omp unroll
+#pragma omp unroll partial(fib(4))
+  for (int i : v)
+    {
+      #pragma omp unroll full
+      for (int j = 8; j != -2; --j)
+       sum = sum + i;
+    }
+
+  return sum;
+}
+
+int
+main ()
+{
+  int result = test1 ();
+
+  if (result != 1000)
+    {
+      fprintf (stderr, "Wrong result: %d\n", result);
+    __builtin_abort ();
+    }
+
+  result = test2 ();
+  if (result != 5500)
+    {
+      fprintf (stderr, "Wrong result: %d\n", result);
+    __builtin_abort ();
+    }
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c++/loop-transforms/unroll-2.C b/libgomp/testsuite/libgomp.c++/loop-transforms/unroll-2.C
new file mode 100644
index 00000000000..90d2775c95b
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c++/loop-transforms/unroll-2.C
@@ -0,0 +1,34 @@
+// { dg-do run }
+// { dg-additional-options "-std=c++11" }
+#include <vector>
+#include <iostream>
+
+int
+main ()
+{
+  std::vector<std::vector<int>> v;
+  std::vector<int> w;
+
+  for (unsigned i = 0; i < 10; i++)
+    {
+      std::vector<int> u;
+      for (unsigned j = 0; j < 10; j++)
+       u.push_back (j);
+      v.push_back (u);
+    }
+
+#pragma omp for
+#pragma omp unroll partial(7)
+  for (auto u : v)
+    for (int x : u)
+      w.push_back (x);
+
+  std::size_t l = w.size ();
+  for (std::size_t i = 0; i < l; i++)
+    {
+      if (w[i] != i % 10)
+       __builtin_abort ();
+    }
+
+  return 0;
+}
diff --git a/libgomp/testsuite/libgomp.c-c++-common/loop-transforms/unroll-1.c b/libgomp/testsuite/libgomp.c-c++-common/loop-transforms/unroll-1.c
new file mode 100644
index 00000000000..2ac0fff16af
--- /dev/null
+++ b/libgomp/testsuite/libgomp.c-c++-common/loop-transforms/unroll-1.c
@@ -0,0 +1,76 @@
+#include <stdio.h>
+
+int compute_sum1 ()
+{
+  int sum = 0;
+  int i,j;
+#pragma omp parallel for reduction(+:sum) lastprivate(j)
+#pragma omp unroll partial
+  for (i = 3; i < 10; ++i)
+  for (j = -2; j < 7; ++j)
+    sum++;
+
+  if (j != 7)
+    __builtin_abort;
+
+  return sum;
+}
+
+int compute_sum2()
+{
+  int sum = 0;
+  int i,j;
+#pragma omp parallel for reduction(+:sum) lastprivate(j)
+#pragma omp unroll partial(5)
+  for (i = 3; i < 10; ++i)
+  for (j = -2; j < 7; ++j)
+    sum++;
+
+  if (j != 7)
+    __builtin_abort;
+
+  return sum;
+}
+
+int compute_sum3()
+{
+  int sum = 0;
+  int i,j;
+#pragma omp parallel for reduction(+:sum)
+#pragma omp unroll partial(1)
+  for (i = 3; i < 10; ++i)
+  for (j = -2; j < 7; ++j)
+    sum++;
+
+  if (j != 7)
+    __builtin_abort;
+
+  return sum;
+}
+
+int main ()
+{
+  int result;
+  result = compute_sum1 ();
+  if (result != 7 * 9)
+    {
+      fprintf (stderr, "%d: Wrong result %d\n", __LINE__, result);
+      __builtin_abort ();
+    }
+
+  result = compute_sum2 ();
+  if (result != 7 * 9)
+    {
+      fprintf (stderr, "%d: Wrong result %d\n", __LINE__, result);
+      __builtin_abort ();
+    }
+
+  result = compute_sum3 ();
+  if (result != 7 * 9)
+    {
+      fprintf (stderr, "%d: Wrong result %d\n", __LINE__, result);
+      __builtin_abort ();
+    }
+
+  return 0;
+}
