diff --git a/gcc/c-family/c-omp.c b/gcc/c-family/c-omp.c
index fb1a0d6041ec7cd215b83ac6f48bdfdae332f314..0945a1e5cc45372e2e61a1e539f5ac78f8d7878e 100644
--- a/gcc/c-family/c-omp.c
+++ b/gcc/c-family/c-omp.c
@@ -1733,10 +1733,21 @@ c_omp_split_clauses (location_t loc, enum tree_code code,
 		{
 		  /* This must be #pragma omp target simd.  */
 		  s = C_OMP_CLAUSE_SPLIT_TARGET;
+		  OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (clauses) = 1;
+		  OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT_TARGET (clauses) = 1;
 		  break;
 		}
 	      c = build_omp_clause (OMP_CLAUSE_LOCATION (clauses),
 				    OMP_CLAUSE_FIRSTPRIVATE);
+	      /* firstprivate should not be applied to target if it is
+		 also lastprivate or on the combined/composite construct,
+		 or if it is mentioned in map clause.  OMP_CLAUSE_DECLs
+		 may need to go through FE handling though (instantiation,
+		 C++ non-static data members, array section lowering), so
+		 add the clause with OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT and
+		 let *finish_omp_clauses and the gimplifier handle it
+		 right.  */
+	      OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c) = 1;
 	      OMP_CLAUSE_DECL (c) = OMP_CLAUSE_DECL (clauses);
 	      OMP_CLAUSE_CHAIN (c) = cclauses[C_OMP_CLAUSE_SPLIT_TARGET];
 	      cclauses[C_OMP_CLAUSE_SPLIT_TARGET] = c;
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index c3b85da11250a07993b00cc6042ef8edc924ab51..fc64ef96fb8c3eb7fac0cfc4880b575a086874ee 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -13936,6 +13936,7 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
      has been seen, -2 if mixed inscan/normal reduction diagnosed.  */
   int reduction_seen = 0;
   bool allocate_seen = false;
+  bool firstprivate_implicit_moved = false;
 
   bitmap_obstack_initialize (NULL);
   bitmap_initialize (&generic_head, &bitmap_default_obstack);
@@ -14377,6 +14378,29 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	  break;
 
 	case OMP_CLAUSE_FIRSTPRIVATE:
+	  if (OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c)
+	      && !firstprivate_implicit_moved)
+	    {
+	      firstprivate_implicit_moved = true;
+	      /* Move firstprivate clauses with
+		 OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT set to the end of
+		 clauses chain.  */
+	      tree cl = NULL, *pc1 = pc, *pc2 = &cl;
+	      while (*pc1)
+		if (OMP_CLAUSE_CODE (*pc1) == OMP_CLAUSE_FIRSTPRIVATE
+		    && OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (*pc1))
+		  {
+		    *pc2 = *pc1;
+		    pc2 = &OMP_CLAUSE_CHAIN (*pc2);
+		    *pc1 = OMP_CLAUSE_CHAIN (*pc1);
+		  }
+		else
+		  pc1 = &OMP_CLAUSE_CHAIN (*pc1);
+	      *pc2 = NULL;
+	      *pc1 = cl;
+	      if (pc1 != pc)
+		continue;
+	    }
 	  t = OMP_CLAUSE_DECL (c);
 	  need_complete = true;
 	  need_implicitly_determined = true;
@@ -14398,6 +14422,9 @@ c_finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	      if (ort == C_ORT_ACC)
 		error_at (OMP_CLAUSE_LOCATION (c),
 			  "%qD appears more than once in data clauses", t);
+	      else if (OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c)
+		       && !OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT_TARGET (c))
+		/* Silently drop the clause.  */;
 	      else
 		error_at (OMP_CLAUSE_LOCATION (c),
 			  "%qD appears both in data and map clauses", t);
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index 0d590c318fb0d3a5832959eae85c289a53a2b80d..fffbe40613e904e970cadbff513b0c9ae05654fe 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -6519,6 +6519,7 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
   bool allocate_seen = false;
   tree detach_seen = NULL_TREE;
   bool mergeable_seen = false;
+  bool firstprivate_implicit_moved = false;
 
   bitmap_obstack_initialize (NULL);
   bitmap_initialize (&generic_head, &bitmap_default_obstack);
@@ -6843,6 +6844,29 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	  break;
 
 	case OMP_CLAUSE_FIRSTPRIVATE:
+	  if (OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c)
+	      && !firstprivate_implicit_moved)
+	    {
+	      firstprivate_implicit_moved = true;
+	      /* Move firstprivate clauses with
+		 OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT set to the end of
+		 clauses chain.  */
+	      tree cl = NULL, *pc1 = pc, *pc2 = &cl;
+	      while (*pc1)
+		if (OMP_CLAUSE_CODE (*pc1) == OMP_CLAUSE_FIRSTPRIVATE
+		    && OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (*pc1))
+		  {
+		    *pc2 = *pc1;
+		    pc2 = &OMP_CLAUSE_CHAIN (*pc2);
+		    *pc1 = OMP_CLAUSE_CHAIN (*pc1);
+		  }
+		else
+		  pc1 = &OMP_CLAUSE_CHAIN (*pc1);
+	      *pc2 = NULL;
+	      *pc1 = cl;
+	      if (pc1 != pc)
+		continue;
+	    }
 	  t = omp_clause_decl_field (OMP_CLAUSE_DECL (c));
 	  if (t)
 	    omp_note_field_privatization (t, OMP_CLAUSE_DECL (c));
@@ -6884,6 +6908,9 @@ finish_omp_clauses (tree clauses, enum c_omp_region_type ort)
 	      if (ort == C_ORT_ACC)
 		error_at (OMP_CLAUSE_LOCATION (c),
 			  "%qD appears more than once in data clauses", t);
+	      else if (OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c)
+		       && !OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT_TARGET (c))
+		/* Silently drop the clause.  */;
 	      else
 		error_at (OMP_CLAUSE_LOCATION (c),
 			  "%qD appears both in data and map clauses", t);
diff --git a/gcc/gimplify.c b/gcc/gimplify.c
index 28bf1b0ba25d2037b0a5e6c4fbcbd6841c98d4ab..b62ea0efc1cb589c0d254f32deefcd197ca8eb5a 100644
--- a/gcc/gimplify.c
+++ b/gcc/gimplify.c
@@ -126,7 +126,10 @@ enum gimplify_omp_var_data
 
   /* Flag for GOVD_MAP: (struct) vars that have pointer attachments for
      fields.  */
-  GOVD_MAP_HAS_ATTACHMENTS = 8388608,
+  GOVD_MAP_HAS_ATTACHMENTS = 0x4000000,
+
+  /* Flag for GOVD_FIRSTPRIVATE: OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT.  */
+  GOVD_FIRSTPRIVATE_IMPLICIT = 0x8000000,
 
   GOVD_DATA_SHARE_CLASS = (GOVD_SHARED | GOVD_PRIVATE | GOVD_FIRSTPRIVATE
 			   | GOVD_LASTPRIVATE | GOVD_REDUCTION | GOVD_LINEAR
@@ -8586,13 +8589,24 @@ omp_lastprivate_for_combined_outer_constructs (struct gimplify_omp_ctx *octx,
 	  omp_add_variable (octx, decl, GOVD_LASTPRIVATE | GOVD_SEEN);
 	  continue;
 	}
-      if (octx->region_type == ORT_COMBINED_TARGET
-	  && splay_tree_lookup (octx->variables,
-				(splay_tree_key) decl) == NULL)
+      if (octx->region_type == ORT_COMBINED_TARGET)
 	{
-	  omp_add_variable (octx, decl, GOVD_MAP | GOVD_SEEN);
-	  octx = octx->outer_context;
-	  break;
+	  splay_tree_node n = splay_tree_lookup (octx->variables,
+						 (splay_tree_key) decl);
+	  if (n == NULL)
+	    {
+	      omp_add_variable (octx, decl, GOVD_MAP | GOVD_SEEN);
+	      octx = octx->outer_context;
+	    }
+	  else if (!implicit_p
+		   && (n->value & GOVD_FIRSTPRIVATE_IMPLICIT))
+	    {
+	      n->value &= ~(GOVD_FIRSTPRIVATE
+			    | GOVD_FIRSTPRIVATE_IMPLICIT
+			    | GOVD_EXPLICIT);
+	      omp_add_variable (octx, decl, GOVD_MAP | GOVD_SEEN);
+	      octx = octx->outer_context;
+	    }
 	}
       break;
     }
@@ -8673,6 +8687,11 @@ gimplify_scan_omp_clauses (tree *list_p, gimple_seq *pre_p,
 	case OMP_CLAUSE_FIRSTPRIVATE:
 	  flags = GOVD_FIRSTPRIVATE | GOVD_EXPLICIT;
 	  check_non_private = "firstprivate";
+	  if (OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c))
+	    {
+	      gcc_assert (code == OMP_TARGET);
+	      flags |= GOVD_FIRSTPRIVATE_IMPLICIT;
+	    }
 	  goto do_add;
 	case OMP_CLAUSE_LASTPRIVATE:
 	  if (OMP_CLAUSE_LASTPRIVATE_CONDITIONAL (c))
@@ -10532,6 +10551,18 @@ gimplify_adjust_omp_clauses (gimple_seq *pre_p, gimple_seq body, tree *list_p,
 	      remove = true;
 	      break;
 	    }
+	  if (OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c))
+	    {
+	      decl = OMP_CLAUSE_DECL (c);
+	      n = splay_tree_lookup (ctx->variables, (splay_tree_key) decl);
+	      if ((n->value & GOVD_MAP) != 0)
+		{
+		  remove = true;
+		  break;
+		}
+	      OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT_TARGET (c) = 0;
+	      OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT (c) = 0;
+	    }
 	  /* FALLTHRU */
 	case OMP_CLAUSE_PRIVATE:
 	case OMP_CLAUSE_SHARED:
diff --git a/gcc/testsuite/c-c++-common/gomp/pr99928-15.c b/gcc/testsuite/c-c++-common/gomp/pr99928-15.c
new file mode 100644
index 0000000000000000000000000000000000000000..f0c6232d27c6fb1b4e72b4552148d1c7e268a02f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/gomp/pr99928-15.c
@@ -0,0 +1,26 @@
+/* PR middle-end/99928 */
+
+int v;
+
+void
+foo (void)
+{
+  #pragma omp target parallel firstprivate (v) map(tofrom: v)	/* { dg-bogus "'v' appears both in data and map clauses" } */
+  v++;
+}
+
+void
+bar (void)
+{
+  #pragma omp target firstprivate (v) map (tofrom: v)	/* { dg-error "'v' appears both in data and map clauses" } */
+  v++;
+}
+
+void
+baz (void)
+{
+  int j;
+  #pragma omp target simd firstprivate (v) map (tofrom: v) private (j)	/* { dg-error "'v' appears both in data and map clauses" } */
+  for (int i = 0; i < 1; i++)
+    j = v;
+}
diff --git a/gcc/testsuite/c-c++-common/gomp/pr99928-3.c b/gcc/testsuite/c-c++-common/gomp/pr99928-3.c
index 67f590bb7ef4a40679de965d57ff46876defbdf2..cf9c72d75b88f06c9df740d04e5915f75fae70ab 100644
--- a/gcc/testsuite/c-c++-common/gomp/pr99928-3.c
+++ b/gcc/testsuite/c-c++-common/gomp/pr99928-3.c
@@ -82,8 +82,8 @@ bar (void)
     #pragma omp section
     l07 = 2;
   }
-  /* { dg-final { scan-tree-dump "omp target\[^\n\r]*map\\(tofrom:l08" "gimple" { xfail *-*-* } } } */
-  /* { dg-final { scan-tree-dump-not "omp target\[^\n\r]*firstprivate\\(l08\\)" "gimple" { xfail *-*-* } } } */
+  /* { dg-final { scan-tree-dump "omp target\[^\n\r]*map\\(tofrom:l08" "gimple" } } */
+  /* { dg-final { scan-tree-dump-not "omp target\[^\n\r]*firstprivate\\(l08\\)" "gimple" } } */
   /* { dg-final { scan-tree-dump "omp parallel\[^\n\r]*firstprivate\\(l08\\)" "gimple" } } *//* FIXME: This should be on for instead.  */
   /* { dg-final { scan-tree-dump "omp parallel\[^\n\r]*lastprivate\\(l08\\)" "gimple" } } *//* FIXME: This should be on for instead.  */
   /* { dg-final { scan-tree-dump-not "omp for\[^\n\r]*firstprivate\\(l08\\)" "gimple" } } *//* FIXME.  */
@@ -91,8 +91,8 @@ bar (void)
   #pragma omp target parallel for firstprivate (l08) lastprivate (l08)
   for (int i = 0; i < 64; i++)
     l08 = i;
-  /* { dg-final { scan-tree-dump "omp target\[^\n\r]*map\\(tofrom:l09" "gimple" { xfail *-*-* } } } */
-  /* { dg-final { scan-tree-dump-not "omp target\[^\n\r]*firstprivate\\(l09\\)" "gimple" { xfail *-*-* } } } */
+  /* { dg-final { scan-tree-dump "omp target\[^\n\r]*map\\(tofrom:l09" "gimple" } } */
+  /* { dg-final { scan-tree-dump-not "omp target\[^\n\r]*firstprivate\\(l09\\)" "gimple" } } */
   /* { dg-final { scan-tree-dump "omp parallel\[^\n\r]*firstprivate\\(l09\\)" "gimple" } } *//* FIXME: This should be on for instead.  */
   /* { dg-final { scan-tree-dump "omp parallel\[^\n\r]*lastprivate\\(l09\\)" "gimple" } } *//* FIXME: This should be on for instead.  */
   /* { dg-final { scan-tree-dump-not "omp for\[^\n\r]*firstprivate\\(l09\\)" "gimple" } } *//* FIXME.  */
@@ -102,8 +102,8 @@ bar (void)
   #pragma omp target parallel for simd firstprivate (l09) lastprivate (l09)
   for (int i = 0; i < 64; i++)
     l09 = i;
-  /* { dg-final { scan-tree-dump "omp target\[^\n\r]*map\\(tofrom:l10" "gimple" { xfail *-*-* } } } */
-  /* { dg-final { scan-tree-dump-not "omp target\[^\n\r]*firstprivate\\(l10\\)" "gimple" { xfail *-*-* } } } */
+  /* { dg-final { scan-tree-dump "omp target\[^\n\r]*map\\(tofrom:l10" "gimple" } } */
+  /* { dg-final { scan-tree-dump-not "omp target\[^\n\r]*firstprivate\\(l10\\)" "gimple" } } */
   /* { dg-final { scan-tree-dump-not "omp simd\[^\n\r]*firstprivate\\(l10\\)" "gimple" } } */
   /* { dg-final { scan-tree-dump "omp simd\[^\n\r]*lastprivate\\(l10\\)" "gimple" } } */
   #pragma omp target simd firstprivate (l10) lastprivate (l10)
diff --git a/gcc/tree.h b/gcc/tree.h
index 64612cfa3680b497c3cffe360132ba0f35aa6af0..37aca8963fe4e61a99f6276b86ef6b5beea287ca 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -1538,6 +1538,11 @@ class auto_suppress_location_wrappers
 #define OMP_CLAUSE_FIRSTPRIVATE_NO_REFERENCE(NODE) \
   TREE_PRIVATE (OMP_CLAUSE_SUBCODE_CHECK (NODE, OMP_CLAUSE_FIRSTPRIVATE))
 
+/* True on a FIRSTPRIVATE clause with OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT also
+   set if target construct is the only one that accepts the clause.  */
+#define OMP_CLAUSE_FIRSTPRIVATE_IMPLICIT_TARGET(NODE) \
+  TREE_PROTECTED (OMP_CLAUSE_SUBCODE_CHECK (NODE, OMP_CLAUSE_FIRSTPRIVATE))
+
 /* True on a LASTPRIVATE clause if a FIRSTPRIVATE clause for the same
    decl is present in the chain.  */
 #define OMP_CLAUSE_LASTPRIVATE_FIRSTPRIVATE(NODE) \