diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 571e9c28e29..846f44f24fa 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -1296,6 +1296,7 @@ OBJS = \
 	insn-recog.o \
 	insn-enums.o \
 	ggc-page.o \
+	addr-space.o \
 	adjust-alignment.o \
 	alias.o \
 	alloc-pool.o \
@@ -2655,6 +2656,7 @@ GTFILES = $(CPPLIB_H) $(srcdir)/input.h $(srcdir)/coretypes.h \
   $(srcdir)/symtab-thunks.h $(srcdir)/symtab-thunks.cc \
   $(srcdir)/symtab-clones.h \
   $(srcdir)/reload.h $(srcdir)/caller-save.c $(srcdir)/symtab.c \
+  $(srcdir)/addr-space.cc \
   $(srcdir)/alias.c $(srcdir)/bitmap.c $(srcdir)/cselib.c $(srcdir)/cgraph.c \
   $(srcdir)/ipa-prop.c $(srcdir)/ipa-cp.c $(srcdir)/ipa-utils.h \
   $(srcdir)/ipa-param-manipulation.h $(srcdir)/ipa-sra.c $(srcdir)/dbxout.c \
diff --git a/gcc/addr-space.cc b/gcc/addr-space.cc
new file mode 100644
index 00000000000..ebb2829171f
--- /dev/null
+++ b/gcc/addr-space.cc
@@ -0,0 +1,177 @@
+/* Support for managing address spaces (both target-specific and custom).
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "addr-space.h"
+#include "target.h"
+
+/* If AS is a custom address space, return a built-in address space
+   that's equivalent to it at the RTL level.
+   Otherwise, return AS.  */
+
+addr_space_t
+ensure_builtin_addr_space (addr_space_t as)
+{
+  /* For now, map all custom address spaces to the generic address space.  */
+  if (g_addr_space_mgr)
+    if (g_addr_space_mgr->custom_p (as))
+      return ADDR_SPACE_GENERIC;
+  return as;
+}
+
+/* Various functions to act on addr_space_t.
+   These handle custom address spaces, and otherwise call into the
+   corresponding target hook targetm.addr_space.NAME.  */
+
+scalar_int_mode
+addr_space_pointer_mode (addr_space_t address_space)
+{
+  address_space = ensure_builtin_addr_space (address_space);
+  return targetm.addr_space.pointer_mode (address_space);
+}
+
+scalar_int_mode
+addr_space_address_mode (addr_space_t address_space)
+{
+  address_space = ensure_builtin_addr_space (address_space);
+  return targetm.addr_space.address_mode (address_space);
+}
+
+bool
+addr_space_valid_pointer_mode (scalar_int_mode mode,
+			       addr_space_t as)
+{
+  as = ensure_builtin_addr_space (as);
+  return targetm.addr_space.valid_pointer_mode (mode, as);
+}
+
+bool
+addr_space_legitimate_address_p (machine_mode mode, rtx exp,
+				 bool strict, addr_space_t as)
+{
+  as = ensure_builtin_addr_space (as);
+  return targetm.addr_space.legitimate_address_p (mode, exp, strict, as);
+}
+
+rtx
+addr_space_legitimize_address (rtx x, rtx oldx, machine_mode mode,
+			       addr_space_t as)
+{
+  as = ensure_builtin_addr_space (as);
+  return targetm.addr_space.legitimize_address (x, oldx, mode, as);
+}
+
+bool
+addr_space_subset_p (addr_space_t subset, addr_space_t superset)
+{
+  if (subset == superset)
+    return true;
+  if (g_addr_space_mgr)
+    {
+      /* For now, assume all custom address spaces are disjoint
+	 from each other and from builtin address spaces.  */
+      if (g_addr_space_mgr->custom_p (subset)
+	  || g_addr_space_mgr->custom_p (superset))
+	return false;
+    }
+  /* We have a pair of target-defined implicit address spaces.  */
+  return targetm.addr_space.subset_p (subset, superset);
+}
+
+bool
+addr_space_zero_address_valid (addr_space_t as)
+{
+  as = ensure_builtin_addr_space (as);
+  return targetm.addr_space.zero_address_valid (as);
+}
+
+rtx
+addr_space_convert (rtx /*op*/, tree /*from_type*/, tree /*to_type*/)
+{
+  gcc_unreachable (); // TODO
+}
+
+int
+addr_space_debug (addr_space_t as)
+{
+  as = ensure_builtin_addr_space (as);
+  return targetm.addr_space.debug (as);
+}
+
+void
+addr_space_diagnose_usage (addr_space_t as, location_t loc)
+{
+  as = ensure_builtin_addr_space (as);
+  return targetm.addr_space.diagnose_usage (as, loc);
+}
+
+/* class addr_space_manager.  */
+
+addr_space_manager::addr_space_manager ()
+: m_custom_addr_spaces (NULL),
+  m_max_static_addr_space (0),
+  m_last_addr_space (0)
+{
+}
+
+/* Hook to be called when a built-in address space is registered.  */
+
+void
+addr_space_manager::on_builtin_addr_space (addr_space_t as)
+{
+  /* All builtin addr spaces should have been created before creating
+     any custom address spaces.  */
+  gcc_assert (m_custom_addr_spaces == NULL);
+
+  m_max_static_addr_space = MAX (m_max_static_addr_space, as);
+  m_last_addr_space = m_max_static_addr_space;
+}
+
+/* Attempt to populate *OUT with a previously unused value.
+   Return true if successful, false otherwise.  */
+
+bool
+addr_space_manager::assign_dynamic_addr_space_t (addr_space_t *out)
+{
+  if (m_last_addr_space == ADDR_SPACE_T_MAX)
+    return false;
+
+  *out = ++m_last_addr_space;
+  return true;
+}
+
+/* Create a new custom_addr_space in the GC heap and stash a pointer to it.  */
+
+custom_addr_space *
+addr_space_manager::create_custom_addr_space (tree name,
+					      addr_space_t as,
+					      location_t loc)
+{
+  custom_addr_space *result
+    = new (ggc_alloc<custom_addr_space> ()) custom_addr_space (name, as, loc);
+  vec_safe_push (m_custom_addr_spaces, result);
+  return result;
+}
+
+/* The singleton instance of addr_space_manager.  */
+
+addr_space_manager *g_addr_space_mgr;
diff --git a/gcc/addr-space.h b/gcc/addr-space.h
new file mode 100644
index 00000000000..3da9a70c5d7
--- /dev/null
+++ b/gcc/addr-space.h
@@ -0,0 +1,122 @@
+/* Support for managing address spaces (both target-specific and custom).
+   Copyright (C) 2021 Free Software Foundation, Inc.
+
+This file is part of GCC.
+
+GCC is free software; you can redistribute it and/or modify it under
+the terms of the GNU General Public License as published by the Free
+Software Foundation; either version 3, or (at your option) any later
+version.
+
+GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or
+FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+for more details.
+
+You should have received a copy of the GNU General Public License
+along with GCC; see the file COPYING3.  If not see
+<http://www.gnu.org/licenses/>.  */
+
+#ifndef GCC_ADDR_SPACE_H
+#define GCC_ADDR_SPACE_H
+
+extern addr_space_t ensure_builtin_addr_space (addr_space_t as);
+
+/* Various functions to act on addr_space_t.
+   These handle custom address spaces, and otherwise call into the
+   corresponding target hook targetm.addr_space.NAME.  */
+
+/* MODE to use for a pointer into another address space.  */
+extern scalar_int_mode addr_space_pointer_mode (addr_space_t address_space);
+
+/* MODE to use for an address in another address space.  */
+extern scalar_int_mode addr_space_address_mode (addr_space_t address_space);
+
+/* True if MODE is valid for a pointer in __attribute__((mode("MODE")))
+   in another address space.  */
+extern bool addr_space_valid_pointer_mode (scalar_int_mode mode,
+					   addr_space_t as);
+
+/* True if an address is a valid memory address to a given named address
+   space for a given mode.  */
+extern bool addr_space_legitimate_address_p (machine_mode mode, rtx exp,
+					     bool strict, addr_space_t as);
+
+/* Return an updated address to convert an invalid pointer to a named
+   address space to a valid one.  If NULL_RTX is returned use machine
+   independent methods to make the address valid.  */
+extern rtx addr_space_legitimize_address (rtx x, rtx oldx, machine_mode mode,
+					  addr_space_t as);
+
+/* True if one named address space is a subset of another named address. */
+extern bool addr_space_subset_p (addr_space_t subset, addr_space_t superset);
+
+/* True if 0 is a valid address in the address space, or false if
+   0 is a NULL in the address space.  */
+extern bool addr_space_zero_address_valid (addr_space_t as);
+
+/* Function to convert an rtl expression from one address space to another.  */
+extern rtx addr_space_convert (rtx op, tree from_type, tree to_type);
+
+/* Function to encode an address space into dwarf.  */
+extern int addr_space_debug (addr_space_t as);
+
+/* Function to emit custom diagnostic if an address space is used.  */
+extern void addr_space_diagnose_usage (addr_space_t as, location_t loc);
+
+
+/* Data structures for managing custom address spaces.  */
+
+/* These are GC-managed so that custom address spaces are preserved in
+   PCH files.  */
+
+/* A custom address space.  */
+
+struct GTY(()) custom_addr_space
+{
+  custom_addr_space () {}
+  custom_addr_space (tree id, addr_space_t as, location_t pragma_loc)
+  : m_id (id), m_as (as), m_pragma_loc (pragma_loc)
+  {
+  }
+
+  tree m_id;
+  addr_space_t m_as;
+  /* The location of the #pragma declaring this object.  */
+  location_t m_pragma_loc;
+
+  /* TODO: additional properties of the address space.  */
+};
+
+/* A class to manage addr_space_t IDs and custom_addr_space instances.
+
+   Targets have statically-assigned address space IDs, which are used
+   e.g. as cases in switch statements so we need to do a two-phase
+   allocation: all statically-assigned addr_space_t IDs, then any
+   dynamically-assigned addr_space_t IDs.  */
+
+class GTY(()) addr_space_manager
+{
+ public:
+  addr_space_manager ();
+  void on_builtin_addr_space (addr_space_t);
+  bool assign_dynamic_addr_space_t (addr_space_t *out);
+  custom_addr_space *create_custom_addr_space (tree name,
+					       addr_space_t as,
+					       location_t loc);
+  bool custom_p (addr_space_t as) const
+  {
+    return as > m_max_static_addr_space;
+  }
+
+ private:
+  vec<custom_addr_space *, va_gc> *m_custom_addr_spaces;
+  addr_space_t m_max_static_addr_space;
+  addr_space_t m_last_addr_space;
+};
+
+extern GTY(()) addr_space_manager *g_addr_space_mgr;
+
+/* TODO: test coverage for PCH.  */
+
+#endif /* GCC_ADDR_SPACE_H */
diff --git a/gcc/auto-inc-dec.c b/gcc/auto-inc-dec.c
index c531df8815c..04c7480c520 100644
--- a/gcc/auto-inc-dec.c
+++ b/gcc/auto-inc-dec.c
@@ -37,6 +37,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "dbgcnt.h"
 #include "print-rtl.h"
 #include "valtrack.h"
+#include "addr-space.h"
 
 /* This pass was originally removed from flow.c. However there is
    almost nothing that remains of that code.
@@ -1172,7 +1173,7 @@ find_inc (bool first_try)
 		     the inc must be a valid addressing reg.  */
 		  addr_space_t as = MEM_ADDR_SPACE (*mem_insn.mem_loc);
 		  if (GET_MODE (inc_insn.reg_res)
-		      != targetm.addr_space.address_mode (as))
+		      != addr_space_address_mode (as))
 		    {
 		      if (dump_file)
 			fprintf (dump_file, "base reg mode failure.\n");
@@ -1223,7 +1224,7 @@ find_inc (bool first_try)
 	     must be a valid addressing reg.  */
 	  addr_space_t as = MEM_ADDR_SPACE (*mem_insn.mem_loc);
 	  if (GET_MODE (inc_insn.reg_res)
-	      != targetm.addr_space.address_mode (as))
+	      != addr_space_address_mode (as))
 	    {
 	      if (dump_file)
 		fprintf (dump_file, "base reg mode failure.\n");
diff --git a/gcc/builtins.c b/gcc/builtins.c
index 384864bfb3a..213785703fd 100644
--- a/gcc/builtins.c
+++ b/gcc/builtins.c
@@ -81,6 +81,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "demangle.h"
 #include "gimple-range.h"
 #include "pointer-query.h"
+#include "addr-space.h"
 
 struct target_builtins default_target_builtins;
 #if SWITCHABLE_TARGET
@@ -5562,7 +5563,7 @@ get_builtin_sync_mem (tree loc, machine_mode mode)
   int addr_space = TYPE_ADDR_SPACE (POINTER_TYPE_P (TREE_TYPE (loc))
 				    ? TREE_TYPE (TREE_TYPE (loc))
 				    : TREE_TYPE (loc));
-  scalar_int_mode addr_mode = targetm.addr_space.address_mode (addr_space);
+  scalar_int_mode addr_mode = addr_space_address_mode (addr_space);
 
   addr = expand_expr (loc, NULL_RTX, addr_mode, EXPAND_SUM);
   addr = convert_memory_address (addr_mode, addr);
diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c
index 007b928c54b..e957d620651 100644
--- a/gcc/c-family/c-attribs.c
+++ b/gcc/c-family/c-attribs.c
@@ -47,6 +47,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimplify.h"
 #include "tree-pretty-print.h"
 #include "gcc-rich-location.h"
+#include "addr-space.h"
 
 static tree handle_packed_attribute (tree *, tree, tree, int, bool *);
 static tree handle_nocommon_attribute (tree *, tree, tree, int, bool *);
@@ -2115,7 +2116,7 @@ handle_mode_attribute (tree *node, tree name, tree args,
 	  tree (*fn)(tree, machine_mode, bool);
 
 	  if (!is_a <scalar_int_mode> (mode, &addr_mode)
-	      || !targetm.addr_space.valid_pointer_mode (addr_mode, as))
+	      || !addr_space_valid_pointer_mode (addr_mode, as))
 	    {
 	      error ("invalid pointer mode %qs", p);
 	      return NULL_TREE;
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index d5dad99ff97..d9d5cc35a4c 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -831,6 +831,7 @@ extern tree (*make_fname_decl) (location_t, tree, int);
 
 /* In c-decl.c and cp/tree.c.  FIXME.  */
 extern void c_register_addr_space (const char *str, addr_space_t as);
+extern custom_addr_space *c_register_custom_addr_space (tree id, location_t loc);
 
 /* In c-common.c.  */
 extern bool in_late_binary_op;
diff --git a/gcc/c-family/c-pragma.c b/gcc/c-family/c-pragma.c
index 3663eb1cfbb..d4c57fb5544 100644
--- a/gcc/c-family/c-pragma.c
+++ b/gcc/c-family/c-pragma.c
@@ -34,6 +34,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "opts.h"
 #include "plugin.h"
 #include "opt-suggestions.h"
+#include "addr-space.h"
 
 #define GCC_BAD(gmsgid) \
   do { warning (OPT_Wpragmas, gmsgid); return; } while (0)
@@ -1220,6 +1221,30 @@ handle_pragma_message (cpp_reader *ARG_UNUSED(dummy))
 	    TREE_STRING_POINTER (message));
 }
 
+/* Handle #pragma GCC custom_address_space by attempting to register a
+   custom address space.  */
+
+static void
+handle_pragma_custom_address_space (cpp_reader *)
+{
+  location_t loc, id_loc;
+  tree x;
+  tree id;
+  const char *name = "#pragma GCC custom_address_space";
+  if (pragma_lex (&x, &loc) != CPP_OPEN_PAREN)
+    GCC_BAD2_AT (loc, "missing %<(%> after %<%s%> - ignored", name);
+
+  if (pragma_lex (&id, &id_loc) != CPP_NAME)
+    GCC_BAD2_AT (id_loc,
+		 "expected an identifier after %<%s(%> - ignored", name);
+
+  if (pragma_lex (&x) != CPP_CLOSE_PAREN)
+    GCC_BAD2_AT (loc, "malformed %<%s%> - ignored", name);
+
+  c_register_custom_addr_space (id, id_loc);
+  /* FIXME: additional clauses to set properties of addr space?  */
+}
+
 /* Mark whether the current location is valid for a STDC pragma.  */
 
 static bool valid_location_for_stdc_pragma;
@@ -1643,6 +1668,11 @@ init_pragma (void)
 
   c_register_pragma_with_expansion (0, "message", handle_pragma_message);
 
+  c_register_pragma ("GCC", "custom_address_space",
+		     handle_pragma_custom_address_space);
+  gcc_assert (g_addr_space_mgr == NULL);
+  g_addr_space_mgr
+    = new (ggc_alloc<addr_space_manager> ()) addr_space_manager ();
 #ifdef REGISTER_TARGET_PRAGMAS
   REGISTER_TARGET_PRAGMAS ();
 #endif
diff --git a/gcc/c/c-decl.c b/gcc/c/c-decl.c
index 186fa1692c1..c670f12ae06 100644
--- a/gcc/c/c-decl.c
+++ b/gcc/c/c-decl.c
@@ -61,6 +61,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "context.h"  /* For 'g'.  */
 #include "omp-general.h"
 #include "omp-offload.h"  /* For offload_vars.  */
+#include "addr-space.h"
 
 #include "tree-pretty-print.h"
 
@@ -653,7 +654,7 @@ c_build_pointer_type (tree to_type)
   machine_mode pointer_mode;
 
   if (as != ADDR_SPACE_GENERIC || c_default_pointer_mode == VOIDmode)
-    pointer_mode = targetm.addr_space.pointer_mode (as);
+    pointer_mode = addr_space_pointer_mode (as);
   else
     pointer_mode = c_default_pointer_mode;
   return build_pointer_type_for_mode (to_type, pointer_mode, false);
@@ -12334,23 +12335,67 @@ c_parse_final_cleanups (void)
   ext_block = NULL;
 }
 
+/* Register ID as a reserved word for the given RID.  */
+
+static void
+register_addr_space_identifier (tree id, int rid)
+{
+  C_SET_RID_CODE (id, rid);
+  C_IS_RESERVED_WORD (id) = 1;
+  ridpointers [rid] = id;
+}
+
 /* Register reserved keyword WORD as qualifier for address space AS.  */
 
 void
 c_register_addr_space (const char *word, addr_space_t as)
 {
+  /* Address space qualifiers are only supported
+     in C with GNU extensions enabled.  */
+  if (c_dialect_objc () || flag_no_asm)
+    return;
+
+  tree id = get_identifier (word);
+
   int rid = RID_FIRST_ADDR_SPACE + as;
-  tree id;
+  register_addr_space_identifier (id, rid);
+  gcc_assert (g_addr_space_mgr);
+  g_addr_space_mgr->on_builtin_addr_space (as);
+}
+
+/* Attempt to register a custom address space, reserving ID at LOC for
+   it as a reserved word.
 
+   If successful, register a GC-allocated custom_addr_space, registered
+   with the address_space_manager.
+   Otherwise emit a diagnostic and return NULL.  */
+
+custom_addr_space *
+c_register_custom_addr_space (tree id, location_t loc)
+{
   /* Address space qualifiers are only supported
      in C with GNU extensions enabled.  */
   if (c_dialect_objc () || flag_no_asm)
-    return;
+    return NULL; // FIXME: diagnostic
 
-  id = get_identifier (word);
-  C_SET_RID_CODE (id, rid);
-  C_IS_RESERVED_WORD (id) = 1;
-  ridpointers [rid] = id;
+  gcc_assert (g_addr_space_mgr);
+
+  addr_space_t as;
+  if (!g_addr_space_mgr->assign_dynamic_addr_space_t (&as))
+    {
+      warning_at (loc, OPT_Wpragmas, "too many custom address spaces");
+      return NULL;
+    }
+
+  int rid = RID_FIRST_ADDR_SPACE + as;
+  if (rid > RID_LAST_ADDR_SPACE)
+    {
+      warning_at (loc, OPT_Wpragmas, "too many custom address spaces");
+      return NULL;
+    }
+
+  register_addr_space_identifier (id, rid);
+  return g_addr_space_mgr->create_custom_addr_space (id, as, loc);
 }
 
 /* Return identifier to look up for omp declare reduction.  */
diff --git a/gcc/c/c-parser.c b/gcc/c/c-parser.c
index 80dd61d599e..88ad30c543a 100644
--- a/gcc/c/c-parser.c
+++ b/gcc/c/c-parser.c
@@ -71,6 +71,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-pretty-print.h"
 #include "memmodel.h"
 #include "c-family/known-headers.h"
+#include "addr-space.h"
 
 /* We need to walk over decls with incomplete struct/union/enum types
    after parsing the whole translation unit.
@@ -326,7 +327,7 @@ c_lex_one_token (c_parser *parser, c_token *token, bool raw = false)
 	      {
 		addr_space_t as;
 		as = (addr_space_t) (rid_code - RID_FIRST_ADDR_SPACE);
-		targetm.addr_space.diagnose_usage (as, token->location);
+		addr_space_diagnose_usage (as, token->location);
 		token->id_kind = C_ID_ADDRSPACE;
 		token->keyword = rid_code;
 		break;
diff --git a/gcc/c/c-typeck.c b/gcc/c/c-typeck.c
index 782414f8c8c..afaa3a63029 100644
--- a/gcc/c/c-typeck.c
+++ b/gcc/c/c-typeck.c
@@ -52,6 +52,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "attribs.h"
 #include "asan.h"
+#include "addr-space.h"
 
 /* Possible cases of implicit conversions.  Used to select diagnostic messages
    and control folding initializers in convert_for_assignment.  */
@@ -308,12 +309,12 @@ addr_space_superset (addr_space_t as1, addr_space_t as2, addr_space_t *common)
       *common = as1;
       return true;
     }
-  else if (targetm.addr_space.subset_p (as1, as2))
+  else if (addr_space_subset_p (as1, as2))
     {
       *common = as2;
       return true;
     }
-  else if (targetm.addr_space.subset_p (as2, as1))
+  else if (addr_space_subset_p (as2, as1))
     {
       *common = as1;
       return true;
@@ -6015,18 +6016,18 @@ build_c_cast (location_t loc, tree type, tree expr)
 	  if (!addr_space_superset (as_to, as_from, &as_common))
 	    {
 	      if (ADDR_SPACE_GENERIC_P (as_from))
-		warning_at (loc, 0, "cast to %s address space pointer "
+		warning_at (loc, 0, "cast to %qs address space pointer "
 			    "from disjoint generic address space pointer",
 			    c_addr_space_name (as_to));
 
 	      else if (ADDR_SPACE_GENERIC_P (as_to))
 		warning_at (loc, 0, "cast to generic address space pointer "
-			    "from disjoint %s address space pointer",
+			    "from disjoint %qs address space pointer",
 			    c_addr_space_name (as_from));
 
 	      else
-		warning_at (loc, 0, "cast to %s address space pointer "
-			    "from disjoint %s address space pointer",
+		warning_at (loc, 0, "cast to %qs address space pointer "
+			    "from disjoint %qs address space pointer",
 			    c_addr_space_name (as_to),
 			    c_addr_space_name (as_from));
 	    }
@@ -7233,8 +7234,10 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
       asl = TYPE_ADDR_SPACE (ttl);
       asr = TYPE_ADDR_SPACE (ttr);
       if (!null_pointer_constant_p (rhs)
-	  && asr != asl && !targetm.addr_space.subset_p (asr, asl))
+	  && asr != asl && !addr_space_subset_p (asr, asl))
 	{
+	  auto_diagnostic_group d;
+	  bool diagnosed = true;
 	  switch (errtype)
 	    {
 	    case ic_argpass:
@@ -7242,7 +7245,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 		const char msg[] = G_("passing argument %d of %qE from "
 				      "pointer to non-enclosed address space");
 		if (warnopt)
-		  warning_at (expr_loc, warnopt, msg, parmnum, rname);
+		  diagnosed
+		    = warning_at (expr_loc, warnopt, msg, parmnum, rname);
 		else
 		  error_at (expr_loc, msg, parmnum, rname);
 	      break;
@@ -7252,7 +7256,7 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 		const char msg[] = G_("assignment from pointer to "
 				      "non-enclosed address space");
 		if (warnopt)
-		  warning_at (location, warnopt, msg);
+		  diagnosed = warning_at (location, warnopt, msg);
 		else
 		  error_at (location, msg);
 		break;
@@ -7263,7 +7267,7 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 		const char msg[] = G_("initialization from pointer to "
 				      "non-enclosed address space");
 		if (warnopt)
-		  warning_at (location, warnopt, msg);
+		  diagnosed = warning_at (location, warnopt, msg);
 		else
 		  error_at (location, msg);
 		break;
@@ -7273,7 +7277,7 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 		const char msg[] = G_("return from pointer to "
 				      "non-enclosed address space");
 		if (warnopt)
-		  warning_at (location, warnopt, msg);
+		  diagnosed = warning_at (location, warnopt, msg);
 		else
 		  error_at (location, msg);
 		break;
@@ -7281,6 +7285,14 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 	    default:
 	      gcc_unreachable ();
 	    }
+	  if (diagnosed)
+	    {
+	      if (errtype == ic_argpass)
+		inform_for_arg (fundecl, expr_loc, parmnum, type, rhstype);
+	      else
+		inform (location, "expected %qT but pointer is of type %qT",
+			type, rhstype);
+	    }
 	  return error_mark_node;
 	}
 
diff --git a/gcc/cfgexpand.c b/gcc/cfgexpand.c
index 55ff75bd78e..9dd958a5371 100644
--- a/gcc/cfgexpand.c
+++ b/gcc/cfgexpand.c
@@ -74,6 +74,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "output.h"
 #include "builtins.h"
 #include "opts.h"
+#include "addr-space.h"
 
 /* Some systems use __main in a way incompatible with its use in gcc, in these
    cases use the macros NAME__MAIN to give a quoted symbol and SYMBOL__MAIN to
@@ -4248,12 +4249,12 @@ convert_debug_memory_address (scalar_int_mode mode, rtx x,
 {
 #ifndef POINTERS_EXTEND_UNSIGNED
   gcc_assert (mode == Pmode
-	      || mode == targetm.addr_space.address_mode (as));
+	      || mode == addr_space_address_mode (as));
   gcc_assert (GET_MODE (x) == mode || GET_MODE (x) == VOIDmode);
 #else
   rtx temp;
 
-  gcc_assert (targetm.addr_space.valid_pointer_mode (mode, as));
+  gcc_assert (addr_space_valid_pointer_mode (mode, as));
 
   if (GET_MODE (x) == mode || GET_MODE (x) == VOIDmode)
     return x;
@@ -4694,7 +4695,7 @@ expand_debug_expr (tree exp)
 
       as = TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (TREE_OPERAND (exp, 0))));
 
-      op0 = convert_debug_memory_address (targetm.addr_space.address_mode (as),
+      op0 = convert_debug_memory_address (addr_space_address_mode (as),
 					  op0, as);
       if (op0 == NULL_RTX)
 	return NULL;
@@ -4719,7 +4720,7 @@ expand_debug_expr (tree exp)
 	return NULL;
 
       as = TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (TREE_OPERAND (exp, 0))));
-      op0 = convert_debug_memory_address (targetm.addr_space.address_mode (as),
+      op0 = convert_debug_memory_address (addr_space_address_mode (as),
 					  op0, as);
       if (op0 == NULL_RTX)
 	return NULL;
diff --git a/gcc/config/i386/i386.c b/gcc/config/i386/i386.c
index e94efdf39fb..181edd9ef40 100644
--- a/gcc/config/i386/i386.c
+++ b/gcc/config/i386/i386.c
@@ -96,6 +96,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "i386-expand.h"
 #include "i386-features.h"
 #include "function-abi.h"
+#include "addr-space.h"
 
 /* This file should be included last.  */
 #include "target-def.h"
@@ -13718,6 +13719,8 @@ ix86_print_operand_address_as (FILE *file, rtx addr,
   bool vsib = false;
   int code = 0;
 
+  as = ensure_builtin_addr_space (as);
+
   if (GET_CODE (addr) == UNSPEC && XINT (addr, 1) == UNSPEC_VSIBADDR)
     {
       ok = ix86_decompose_address (XVECEXP (addr, 0, 0), &parts);
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index b4f530d57ac..f08932a1af7 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -166,11 +166,14 @@ class bitmap_view;
 
 /* Address space number for named address space support.  */
 typedef unsigned char addr_space_t;
+#define ADDR_SPACE_T_MAX 255
 
 /* The value of addr_space_t that represents the generic address space.  */
 #define ADDR_SPACE_GENERIC 0
 #define ADDR_SPACE_GENERIC_P(AS) ((AS) == ADDR_SPACE_GENERIC)
 
+struct custom_addr_space;
+
 /* The major intermediate representations of GCC.  */
 enum ir_type {
   IR_GIMPLE,
diff --git a/gcc/cp/tree.c b/gcc/cp/tree.c
index 32ddf835a91..1c741028a5e 100644
--- a/gcc/cp/tree.c
+++ b/gcc/cp/tree.c
@@ -5961,6 +5961,14 @@ c_register_addr_space (const char * /*word*/, addr_space_t /*as*/)
 {
 }
 
+/* Stub for c-common.  Please keep in sync with c-decl.c.  */
+
+custom_addr_space *
+c_register_custom_addr_space (tree, location_t)
+{
+  return NULL;
+}
+
 /* Return the number of operands in T that we care about for things like
    mangling.  */
 
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 6e6c580e329..bc298da8956 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -1412,6 +1412,10 @@ Address space identifiers may be used exactly like any other C type
 qualifier (e.g., @code{const} or @code{volatile}).  See the N1275
 document for more details.
 
+As a further extension, GNU C supports user-defined address spaces
+via @code{#pragma GCC custom_address_space}; see that pragma for
+more details.
+
 @anchor{AVR Named Address Spaces}
 @subsection AVR Named Address Spaces
 
@@ -23331,6 +23335,7 @@ information.
 * Push/Pop Macro Pragmas::
 * Function Specific Option Pragmas::
 * Loop-Specific Pragmas::
+* Custom Address Space Pragmas::
 @end menu
 
 @node AArch64 Pragmas
@@ -24008,6 +24013,47 @@ The values of @math{0} and @math{1} block any unrolling of the loop.
 
 @end table
 
+@node Custom Address Space Pragmas
+@subsection Custom Address Space Pragmas
+
+@table @code
+@item #pragma GCC custom_address_space (@var{name})
+@cindex pragma GCC custom_address_space
+
+As an extension, GNU C supports named address spaces on some targets as
+defined in the N1275 draft of ISO/IEC DTR 18037.  Support for named
+address spaces in GCC will evolve as the draft technical report
+changes.
+
+This pragma creates a user-defined address space with the given name
+within the translation unit, supplementing the implicit target-specific
+address spaces, and the ``generic'' address space.
+
+All custom address spaces are disjoint from each other and from all
+built-in address spaces (including the generic address space).
+For example, given:
+
+@smallexample
+#pragma GCC custom_address_space(__kernel)
+#pragma GCC custom_address_space(__user)
+void __kernel *kernel_ptr;
+void __user *user_ptr;
+@end smallexample
+
+then GNU C will issue a diagnostic on attempts to use a convert between
+@code{void __kernel *} and a @code {void __user *}, or between these
+pointers and a plain @code{void *}.
+
+Although new address spaces created this way are disjoint and thus are
+not equivalent in terms of type-checking, they are all equivalent to the
+generic address space in terms of code generation.
+
+The number of user-defined address spaces allowed in a translation unit
+is target-dependent, but very limited - the total number of target-specific
+and user-defined address spaces in a translation unit must not exceed 15.
+
+@end table
+
 @node Unnamed Fields
 @section Unnamed Structure and Union Fields
 @cindex @code{struct}
diff --git a/gcc/dwarf2out.c b/gcc/dwarf2out.c
index fb0e3381e5b..028d4235054 100644
--- a/gcc/dwarf2out.c
+++ b/gcc/dwarf2out.c
@@ -97,6 +97,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "attribs.h"
 #include "file-prefix-map.h" /* remap_debug_filename()  */
+#include "addr-space.h"
 
 static void dwarf2out_source_line (unsigned int, unsigned int, const char *,
 				   int, bool);
@@ -13771,7 +13772,7 @@ modified_type_die (tree type, int cv_quals, bool reverse,
       addr_space_t as = TYPE_ADDR_SPACE (item_type);
       if (!ADDR_SPACE_GENERIC_P (as))
 	{
-	  int action = targetm.addr_space.debug (as);
+	  int action = addr_space_debug (as);
 	  if (action >= 0)
 	    {
 	      /* Positive values indicate an address_class.  */
diff --git a/gcc/emit-rtl.c b/gcc/emit-rtl.c
index e6158f243c0..6419235c8d5 100644
--- a/gcc/emit-rtl.c
+++ b/gcc/emit-rtl.c
@@ -63,6 +63,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple.h"
 #include "gimple-ssa.h"
 #include "gimplify.h"
+#include "addr-space.h"
 
 struct target_rtl default_target_rtl;
 #if SWITCHABLE_TARGET
@@ -2349,7 +2350,7 @@ adjust_address_1 (rtx memref, machine_mode mode, poly_int64 offset,
   unsigned HOST_WIDE_INT max_align;
 #ifdef POINTERS_EXTEND_UNSIGNED
   scalar_int_mode pointer_mode
-    = targetm.addr_space.pointer_mode (attrs.addrspace);
+    = addr_space_pointer_mode (attrs.addrspace);
 #endif
 
   /* VOIDmode means no mode change for change_address_1.  */
diff --git a/gcc/explow.c b/gcc/explow.c
index a35423f5d16..81c688bb1c3 100644
--- a/gcc/explow.c
+++ b/gcc/explow.c
@@ -42,6 +42,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "stringpool.h"
 #include "common/common-target.h"
 #include "output.h"
+#include "addr-space.h"
 
 static rtx break_out_memory_refs (rtx);
 
@@ -310,8 +311,8 @@ convert_memory_address_addr_space_1 (scalar_int_mode to_mode ATTRIBUTE_UNUSED,
   if (GET_MODE (x) == to_mode)
     return x;
 
-  pointer_mode = targetm.addr_space.pointer_mode (as);
-  address_mode = targetm.addr_space.address_mode (as);
+  pointer_mode = addr_space_pointer_mode (as);
+  address_mode = addr_space_address_mode (as);
   from_mode = to_mode == pointer_mode ? address_mode : pointer_mode;
 
   /* Here we handle some special cases.  If none of them apply, fall through
@@ -433,7 +434,7 @@ rtx
 memory_address_addr_space (machine_mode mode, rtx x, addr_space_t as)
 {
   rtx oldx = x;
-  scalar_int_mode address_mode = targetm.addr_space.address_mode (as);
+  scalar_int_mode address_mode = addr_space_address_mode (as);
 
   x = convert_memory_address_addr_space (address_mode, x, as);
 
@@ -469,7 +470,7 @@ memory_address_addr_space (machine_mode mode, rtx x, addr_space_t as)
 	 transformations can make better code.  */
       {
 	rtx orig_x = x;
-	x = targetm.addr_space.legitimize_address (x, oldx, mode, as);
+	x = addr_space_legitimize_address (x, oldx, mode, as);
 	if (orig_x != x && memory_address_addr_space_p (mode, x, as))
 	  goto done;
       }
@@ -853,7 +854,7 @@ promote_mode (const_tree type ATTRIBUTE_UNUSED, machine_mode mode,
     case REFERENCE_TYPE:
     case POINTER_TYPE:
       *punsignedp = POINTERS_EXTEND_UNSIGNED;
-      return targetm.addr_space.address_mode
+      return addr_space_address_mode
 	       (TYPE_ADDR_SPACE (TREE_TYPE (type)));
 #endif
 
diff --git a/gcc/expr.c b/gcc/expr.c
index 5673902b1fc..bfecf416aa6 100644
--- a/gcc/expr.c
+++ b/gcc/expr.c
@@ -64,6 +64,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "rtx-vector-builder.h"
 #include "tree-pretty-print.h"
 #include "flags.h"
+#include "addr-space.h"
 
 
 /* If this is nonzero, we do not bother generating VOLATILE
@@ -6191,7 +6192,7 @@ store_expr (tree exp, rtx target, int call_param_p,
 	  else
 	    {
 	      machine_mode pointer_mode
-		= targetm.addr_space.pointer_mode (MEM_ADDR_SPACE (target));
+		= addr_space_pointer_mode (MEM_ADDR_SPACE (target));
 	      machine_mode address_mode = get_address_mode (target);
 
 	      /* Compute the size of the data to copy from the string.  */
@@ -8537,8 +8538,8 @@ expand_expr_addr_expr (tree exp, rtx target, machine_mode tmode,
   if (POINTER_TYPE_P (TREE_TYPE (exp)))
     {
       as = TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (exp)));
-      address_mode = targetm.addr_space.address_mode (as);
-      pointer_mode = targetm.addr_space.pointer_mode (as);
+      address_mode = addr_space_address_mode (as);
+      pointer_mode = addr_space_pointer_mode (as);
     }
 
   /* We can get called with some Weird Things if the user does silliness
@@ -9125,10 +9126,10 @@ expand_expr_real_2 (sepops ops, rtx target, machine_mode tmode,
 
         /* Ask target code to handle conversion between pointers
 	   to overlapping address spaces.  */
-	if (targetm.addr_space.subset_p (as_to, as_from)
-	    || targetm.addr_space.subset_p (as_from, as_to))
+	if (addr_space_subset_p (as_to, as_from)
+	    || addr_space_subset_p (as_from, as_to))
 	  {
-	    op0 = targetm.addr_space.convert (op0, treeop0_type, type);
+	    op0 = addr_space_convert (op0, treeop0_type, type);
 	  }
         else
           {
@@ -10727,7 +10728,7 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode tmode,
 	  /* Writing into CONST_DECL is always invalid, but handle it
 	     gracefully.  */
 	  addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (exp));
-	  scalar_int_mode address_mode = targetm.addr_space.address_mode (as);
+	  scalar_int_mode address_mode = addr_space_address_mode (as);
 	  op0 = expand_expr_addr_expr_1 (exp, NULL_RTX, address_mode,
 					 EXPAND_NORMAL, as);
 	  op0 = memory_address_addr_space (mode, op0, as);
@@ -10901,7 +10902,7 @@ expand_expr_real_1 (tree exp, rtx target, machine_mode tmode,
 	    REF_REVERSE_STORAGE_ORDER (exp) = reverse;
 	    return expand_expr (exp, target, tmode, modifier);
 	  }
-	address_mode = targetm.addr_space.address_mode (as);
+	address_mode = addr_space_address_mode (as);
 	if ((def_stmt = get_def_for_expr (base, BIT_AND_EXPR)))
 	  {
 	    tree mask = gimple_assign_rhs2 (def_stmt);
diff --git a/gcc/fold-const.c b/gcc/fold-const.c
index 90d82257ae7..dc5d8729508 100644
--- a/gcc/fold-const.c
+++ b/gcc/fold-const.c
@@ -84,6 +84,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "vec-perm-indices.h"
 #include "asan.h"
 #include "gimple-range.h"
+#include "addr-space.h"
 
 /* Nonzero if we are folding constants inside an initializer; zero
    otherwise.  */
@@ -1744,7 +1745,7 @@ const_unop (enum tree_code code, tree type, tree arg0)
       /* If the source address is 0, and the source address space
 	 cannot have a valid object at 0, fold to dest type null.  */
       if (integer_zerop (arg0)
-	  && !(targetm.addr_space.zero_address_valid
+	  && !(addr_space_zero_address_valid
 	       (TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (arg0))))))
 	return fold_convert_const (code, type, arg0);
       break;
diff --git a/gcc/gimple.c b/gcc/gimple.c
index 1e0fad92e15..1cf4bc8ffda 100644
--- a/gcc/gimple.c
+++ b/gcc/gimple.c
@@ -49,6 +49,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "ipa-modref-tree.h"
 #include "ipa-modref.h"
 #include "dbgcnt.h"
+#include "addr-space.h"
 
 /* All the tuples have their operand vector (if present) at the very bottom
    of the structure.  Therefore, the offset required to find the
@@ -3023,7 +3024,7 @@ check_loadstore (gimple *, tree op, tree, void *data)
     {
       /* Some address spaces may legitimately dereference zero.  */
       addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (op));
-      if (targetm.addr_space.zero_address_valid (as))
+      if (addr_space_zero_address_valid (as))
 	return false;
 
       return operand_equal_p (TREE_OPERAND (op, 0), (tree)data, 0);
diff --git a/gcc/lra-constraints.c b/gcc/lra-constraints.c
index 0195b4fb9c3..53066fcb675 100644
--- a/gcc/lra-constraints.c
+++ b/gcc/lra-constraints.c
@@ -132,6 +132,7 @@
 #include "print-rtl.h"
 #include "function-abi.h"
 #include "rtl-iter.h"
+#include "addr-space.h"
 
 /* Value of LRA_CURR_RELOAD_NUM at the beginning of BB of the current
    insn.  Remember that LRA_CURR_RELOAD_NUM is the number of emitted
@@ -335,7 +336,7 @@ valid_address_p (machine_mode mode ATTRIBUTE_UNUSED,
  win:
   return 1;
 #else
-  return targetm.addr_space.legitimate_address_p (mode, addr, 0, as);
+  return addr_space_legitimate_address_p (mode, addr, 0, as);
 #endif
 }
 
diff --git a/gcc/pointer-query.cc b/gcc/pointer-query.cc
index a0e4543d8a3..fec58093cd0 100644
--- a/gcc/pointer-query.cc
+++ b/gcc/pointer-query.cc
@@ -41,7 +41,7 @@
 #include "pointer-query.h"
 #include "tree-pretty-print.h"
 #include "tree-ssanames.h"
-#include "target.h"
+#include "addr-space.h"
 
 static bool compute_objsize_r (tree, gimple *, int, access_ref *,
 			       ssa_name_limit_t &, pointer_query *);
@@ -1889,7 +1889,7 @@ compute_objsize_r (tree ptr, gimple *stmt, int ostype, access_ref *pref,
 	{
 	  tree deref_type = TREE_TYPE (TREE_TYPE (ptr));
 	  addr_space_t as = TYPE_ADDR_SPACE (deref_type);
-	  if (targetm.addr_space.zero_address_valid (as))
+	  if (addr_space_zero_address_valid (as))
 	    pref->set_max_size_range ();
 	  else
 	    pref->sizrng[0] = pref->sizrng[1] = 0;
diff --git a/gcc/recog.c b/gcc/recog.c
index 5a42c45361d..7deb9c7285e 100644
--- a/gcc/recog.c
+++ b/gcc/recog.c
@@ -41,6 +41,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "reload.h"
 #include "tree-pass.h"
 #include "function-abi.h"
+#include "addr-space.h"
 
 #ifndef STACK_POP_CODE
 #if STACK_GROWS_DOWNWARD
@@ -1791,7 +1792,7 @@ memory_address_addr_space_p (machine_mode mode ATTRIBUTE_UNUSED,
  win:
   return true;
 #else
-  return targetm.addr_space.legitimate_address_p (mode, addr, 0, as);
+  return addr_space_legitimate_address_p (mode, addr, 0, as);
 #endif
 }
 
@@ -2423,9 +2424,9 @@ offsettable_address_addr_space_p (int strictp, machine_mode mode, rtx y,
 
   machine_mode address_mode = GET_MODE (y);
   if (address_mode == VOIDmode)
-    address_mode = targetm.addr_space.address_mode (as);
+    address_mode = addr_space_address_mode (as);
 #ifdef POINTERS_EXTEND_UNSIGNED
-  machine_mode pointer_mode = targetm.addr_space.pointer_mode (as);
+  machine_mode pointer_mode = addr_space_pointer_mode (as);
 #endif
 
   /* ??? How much offset does an offsettable BLKmode reference need?
diff --git a/gcc/reload.c b/gcc/reload.c
index 4c55ca58a5f..b1018ca684a 100644
--- a/gcc/reload.c
+++ b/gcc/reload.c
@@ -106,6 +106,7 @@ a register with any other reload.  */
 #include "reload.h"
 #include "addresses.h"
 #include "function-abi.h"
+#include "addr-space.h"
 
 /* True if X is a constant that can be forced into the constant pool.
    MODE is the mode of the operand, or VOIDmode if not known.  */
@@ -2172,7 +2173,7 @@ strict_memory_address_addr_space_p (machine_mode mode ATTRIBUTE_UNUSED,
  win:
   return true;
 #else
-  return targetm.addr_space.legitimate_address_p (mode, addr, 1, as);
+  return addr_space_legitimate_address_p (mode, addr, 1, as);
 #endif
 }
 
@@ -5242,7 +5243,7 @@ find_reloads_address (machine_mode mode, rtx *memrefloc, rtx ad,
     {
       machine_mode address_mode = GET_MODE (ad);
       if (address_mode == VOIDmode)
-	address_mode = targetm.addr_space.address_mode (as);
+	address_mode = addr_space_address_mode (as);
 
       /* If AD is an address in the constant pool, the MEM rtx may be shared.
 	 Unshare it so we can safely alter it.  */
diff --git a/gcc/rtlanal.c b/gcc/rtlanal.c
index d37f7789b20..1046b359c30 100644
--- a/gcc/rtlanal.c
+++ b/gcc/rtlanal.c
@@ -38,6 +38,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "rtl-iter.h"
 #include "hard-reg-set.h"
 #include "function-abi.h"
+#include "addr-space.h"
 
 /* Forward declarations */
 static void set_of_1 (rtx, const_rtx, void *);
@@ -6278,7 +6279,7 @@ get_address_mode (rtx mem)
   mode = GET_MODE (XEXP (mem, 0));
   if (mode != VOIDmode)
     return as_a <scalar_int_mode> (mode);
-  return targetm.addr_space.address_mode (MEM_ADDR_SPACE (mem));
+  return addr_space_address_mode (MEM_ADDR_SPACE (mem));
 }
 
 /* Split up a CONST_DOUBLE or integer constant rtx
diff --git a/gcc/testsuite/gcc.dg/custom-address-space-1.c b/gcc/testsuite/gcc.dg/custom-address-space-1.c
new file mode 100644
index 00000000000..9ca1157a730
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/custom-address-space-1.c
@@ -0,0 +1,174 @@
+/* Verify that we can create multiple custom address spaces,
+   that they are treated as disjoint from each other and from
+   the generic address space.  */
+
+/* Avoid using "-ansi".  */
+/* { dg-options "" } */
+
+#define __kernel
+#pragma GCC custom_address_space(__user)
+#pragma GCC custom_address_space(__iomem)
+#pragma GCC custom_address_space(__percpu)
+#pragma GCC custom_address_space(__rcu)
+
+void *p;
+void __kernel *p_kernel;
+void __user *p_user;
+void __iomem *p_iomem;
+void __percpu *p_percpu;
+void __rcu *p_rcu;
+
+extern void accepts_p (void *); /* { dg-message "24: expected 'void \\*' but argument is of type '__user void \\*'" } */
+extern void accepts_p_kernel (void __kernel *);
+extern void accepts_p_user (void __user *);
+
+void test_argpass_to_p (void)
+{
+  accepts_p (p);
+  accepts_p (p_kernel);
+  accepts_p (p_user); /* { dg-error "passing argument 1 of 'accepts_p' from pointer to non-enclosed address space" } */
+}
+
+void test_init_p (void)
+{
+  void *local_p_1 = p;
+  void *local_p_2 = p_kernel;
+  void *local_p_3 = p_user; /* { dg-error "initialization from pointer to non-enclosed address space" } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__user void \\*'" "" { target *-*-* } .-1 } */
+  void *local_p_4 = p_iomem; /* { dg-error "initialization from pointer to non-enclosed address space" } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__iomem void \\*'" "" { target *-*-* } .-1 } */
+}
+
+void test_init_p_kernel (void)
+{
+  void __kernel *local_p_1 = p;
+  void __kernel *local_p_2 = p_kernel;
+  void __kernel *local_p_3 = p_user; /* { dg-error "initialization from pointer to non-enclosed address space" } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__user void \\*'" "" { target *-*-* } .-1 } */
+}
+
+void test_init_p_user (void)
+{
+  void __user *local_p_1 = p; /* { dg-error "initialization from pointer to non-enclosed address space" } */
+  /* { dg-message "expected '__user void \\*' but pointer is of type 'void \\*'" "" { target *-*-* } .-1 } */
+  void __user *local_p_2 = p_kernel; /* { dg-error "initialization from pointer to non-enclosed address space" } */
+  /* { dg-message "expected '__user void \\*' but pointer is of type 'void \\*'" "" { target *-*-* } .-1 } */
+  void __user *local_p_3 = p_user;
+}
+
+void test_assign_to_p (void)
+{
+  p = p;
+  p = p_kernel;
+  p = p_user; /* { dg-error "assignment from pointer to non-enclosed address space" } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__user void \\*'" "" { target *-*-* } .-1 } */
+  // etc
+}
+
+void test_assign_to_p_kernel (void)
+{
+  p_kernel = p;
+  p_kernel = p_kernel;
+  p_kernel = p_user; /* { dg-error "assignment from pointer to non-enclosed address space" } */
+  /* { dg-message "expected 'void \\*' but pointer is of type '__user void \\*'" "" { target *-*-* } .-1 } */
+  // etc
+}
+
+void test_assign_to_p_user (void)
+{
+  p_user = p;  /* { dg-error "assignment from pointer to non-enclosed address space" } */
+  /* { dg-message "expected '__user void \\*' but pointer is of type 'void \\*'" "" { target *-*-* } .-1 } */
+  p_user = p_kernel;  /* { dg-error "assignment from pointer to non-enclosed address space" } */
+  /* { dg-message "expected '__user void \\*' but pointer is of type 'void \\*'" "" { target *-*-* } .-1 } */
+  p_user = p_user;
+  // etc
+}
+
+void *test_return_p (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      return p;
+    case 1:
+      return p_kernel;
+    case 2:
+      return p_user; /* { dg-error "return from pointer to non-enclosed address space" } */
+      /* { dg-message "expected 'void \\*' but pointer is of type '__user void \\*'" "" { target *-*-* } .-1 } */
+    }
+}
+
+void __kernel *test_return_p_kernel (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      return p;
+    case 1:
+      return p_kernel;
+    case 2:
+      return p_user; /* { dg-error "return from pointer to non-enclosed address space" } */
+      /* { dg-message "expected 'void \\*' but pointer is of type '__user void \\*'" "" { target *-*-* } .-1 } */
+    }
+}
+
+void __user *test_return_p_user (int i)
+{
+  switch (i)
+    {
+    default:
+    case 0:
+      return p; /* { dg-error "return from pointer to non-enclosed address space" } */
+      /* { dg-message "expected '__user void \\*' but pointer is of type 'void \\*'" "" { target *-*-* } .-1 } */
+    case 1:
+      return p_kernel; /* { dg-error "return from pointer to non-enclosed address space" } */
+      /* { dg-message "expected '__user void \\*' but pointer is of type 'void \\*'" "" { target *-*-* } .-1 } */
+    case 2:
+      return p_user;
+    }
+}
+
+void test_cast_k_to_u (void)
+{
+  p_user = (void __user *)p_kernel; /* { dg-warning "cast to '__user' address space pointer from disjoint generic address space pointer" } */
+}
+
+void test_cast_u_to_k (void)
+{
+  p_kernel = (void __kernel *)p_user; /* { dg-warning "cast to generic address space pointer from disjoint '__user' address space pointer" } */
+}
+
+void test_cast_user_to_iomem (void)
+{
+  p_iomem = (void __iomem *)p_user; /* { dg-warning "cast to '__iomem' address space pointer from disjoint '__user' address space pointer" } */
+}
+
+int test_deref_read (int __user *p)
+{
+  return *p; // FIXME: should we have a way to disallow direct access?
+}
+
+void test_deref_write (int __user *p, int i)
+{
+  *p = i; // FIXME: should we have a way to disallow direct access?
+}
+
+typedef struct foo { int i; } __user *foo_ptr_t;
+
+void __user *
+test_pass_through (void __user *ptr)
+{
+  return ptr;
+}
+
+#define NULL ((void *)0)
+
+void __user *
+test_return_null_p_user ()
+{
+  return NULL;
+}
+
+// etc
diff --git a/gcc/testsuite/gcc.dg/custom-address-space-2.c b/gcc/testsuite/gcc.dg/custom-address-space-2.c
new file mode 100644
index 00000000000..7a07f3b134a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/custom-address-space-2.c
@@ -0,0 +1,21 @@
+/* Verify that we fail gracefully if the user defines too many address spaces.  */
+/* Avoid using "-ansi".  */
+/* { dg-options "" } */
+
+#pragma GCC custom_address_space(__cas_01)
+#pragma GCC custom_address_space(__cas_02)
+#pragma GCC custom_address_space(__cas_03)
+#pragma GCC custom_address_space(__cas_04)
+#pragma GCC custom_address_space(__cas_05)
+#pragma GCC custom_address_space(__cas_06)
+#pragma GCC custom_address_space(__cas_07)
+#pragma GCC custom_address_space(__cas_08)
+#pragma GCC custom_address_space(__cas_09)
+#pragma GCC custom_address_space(__cas_10)
+#pragma GCC custom_address_space(__cas_11)
+#pragma GCC custom_address_space(__cas_12)
+#pragma GCC custom_address_space(__cas_13)
+#pragma GCC custom_address_space(__cas_14) /* { dg-warning "too many custom address spaces" } */
+#pragma GCC custom_address_space(__cas_15) /* { dg-warning "too many custom address spaces" } */
+
+// FIXME: how to filter this by target; it's going to vary by target
diff --git a/gcc/testsuite/gcc.dg/custom-address-space-3.c b/gcc/testsuite/gcc.dg/custom-address-space-3.c
new file mode 100644
index 00000000000..426d6ce5c64
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/custom-address-space-3.c
@@ -0,0 +1,15 @@
+/* Verify that we can successfully compile code with a custom address space.  */
+/* Avoid using "-ansi".  */
+/* { dg-options "" } */
+
+#pragma GCC custom_address_space(__user)
+
+int test_1 (int __user *p)
+{
+  return *p;
+}
+
+void test_2 (int __user *p, int val)
+{
+  *p = val;
+}
diff --git a/gcc/tree-ssa-address.c b/gcc/tree-ssa-address.c
index f35556db2f7..e75ab19c8f0 100644
--- a/gcc/tree-ssa-address.c
+++ b/gcc/tree-ssa-address.c
@@ -48,6 +48,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "tree-affine.h"
 #include "gimplify.h"
 #include "builtins.h"
+#include "addr-space.h"
 
 /* FIXME: We compute address costs using RTL.  */
 #include "tree-ssa-address.h"
@@ -192,8 +193,8 @@ rtx
 addr_for_mem_ref (struct mem_address *addr, addr_space_t as,
 		  bool really_expand)
 {
-  scalar_int_mode address_mode = targetm.addr_space.address_mode (as);
-  scalar_int_mode pointer_mode = targetm.addr_space.pointer_mode (as);
+  scalar_int_mode address_mode = addr_space_address_mode (as);
+  scalar_int_mode pointer_mode = addr_space_pointer_mode (as);
   rtx address, sym, bse, idx, st, off;
   struct mem_addr_template *templ;
 
@@ -576,7 +577,7 @@ multiplier_allowed_in_address_p (HOST_WIDE_INT ratio, machine_mode mode,
   valid_mult = valid_mult_list[data_index];
   if (!valid_mult)
     {
-      machine_mode address_mode = targetm.addr_space.address_mode (as);
+      machine_mode address_mode = addr_space_address_mode (as);
       rtx reg1 = gen_raw_REG (address_mode, LAST_VIRTUAL_REGISTER + 1);
       rtx reg2 = gen_raw_REG (address_mode, LAST_VIRTUAL_REGISTER + 2);
       rtx addr, scaled;
@@ -622,7 +623,7 @@ most_expensive_mult_to_index (tree type, struct mem_address *parts,
 			      aff_tree *addr, bool speed)
 {
   addr_space_t as = TYPE_ADDR_SPACE (type);
-  machine_mode address_mode = targetm.addr_space.address_mode (as);
+  machine_mode address_mode = addr_space_address_mode (as);
   HOST_WIDE_INT coef;
   unsigned best_mult_cost = 0, acost;
   tree mult_elt = NULL_TREE, elt;
diff --git a/gcc/tree-ssa-loop-ivopts.c b/gcc/tree-ssa-loop-ivopts.c
index 4a498abe3b0..0cda74825e8 100644
--- a/gcc/tree-ssa-loop-ivopts.c
+++ b/gcc/tree-ssa-loop-ivopts.c
@@ -131,6 +131,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "builtins.h"
 #include "tree-vectorizer.h"
 #include "dbgcnt.h"
+#include "addr-space.h"
 
 /* For lang_hooks.types.type_for_mode.  */
 #include "langhooks.h"
@@ -2606,7 +2607,7 @@ addr_offset_valid_p (struct iv_use *use, poly_int64 offset)
   addr = (*addr_list)[list_index];
   if (!addr)
     {
-      addr_mode = targetm.addr_space.address_mode (as);
+      addr_mode = addr_space_address_mode (as);
       reg = gen_raw_REG (addr_mode, LAST_VIRTUAL_REGISTER + 1);
       addr = gen_rtx_fmt_ee (PLUS, addr_mode, reg, NULL_RTX);
       (*addr_list)[list_index] = addr;
@@ -3729,7 +3730,7 @@ static rtx
 produce_memory_decl_rtl (tree obj, int *regno)
 {
   addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (obj));
-  machine_mode address_mode = targetm.addr_space.address_mode (as);
+  machine_mode address_mode = addr_space_address_mode (as);
   rtx x;
 
   gcc_assert (obj);
diff --git a/gcc/tree.c b/gcc/tree.c
index 845228a055b..119cef9cfcc 100644
--- a/gcc/tree.c
+++ b/gcc/tree.c
@@ -69,6 +69,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "gimple-fold.h"
 #include "escaped_string.h"
 #include "gimple-range.h"
+#include "addr-space.h"
 
 /* Tree code classes.  */
 
@@ -6829,7 +6830,7 @@ build_pointer_type_for_mode (tree to_type, machine_mode mode,
   if (mode == VOIDmode)
     {
       addr_space_t as = TYPE_ADDR_SPACE (to_type);
-      mode = targetm.addr_space.pointer_mode (as);
+      mode = addr_space_pointer_mode (as);
     }
 
   /* If the pointed-to type has the may_alias attribute set, force
@@ -6901,7 +6902,7 @@ build_reference_type_for_mode (tree to_type, machine_mode mode,
   if (mode == VOIDmode)
     {
       addr_space_t as = TYPE_ADDR_SPACE (to_type);
-      mode = targetm.addr_space.pointer_mode (as);
+      mode = addr_space_pointer_mode (as);
     }
 
   /* If the pointed-to type has the may_alias attribute set, force
diff --git a/gcc/varasm.c b/gcc/varasm.c
index 09316c62050..d328adc0a7b 100644
--- a/gcc/varasm.c
+++ b/gcc/varasm.c
@@ -61,6 +61,7 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "toplev.h"
 #include "opts.h"
+#include "addr-space.h"
 
 #ifdef XCOFF_DEBUGGING_INFO
 #include "xcoffout.h"		/* Needed for external data declarations.  */
@@ -1620,7 +1621,7 @@ make_decl_rtl (tree decl)
       if (TREE_TYPE (decl) != error_mark_node)
 	{
 	  addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (decl));
-	  address_mode = targetm.addr_space.address_mode (as);
+	  address_mode = addr_space_address_mode (as);
 	}
       x = gen_rtx_SYMBOL_REF (address_mode, name);
     }
@@ -5178,7 +5179,7 @@ output_constant (tree exp, unsigned HOST_WIDE_INT size, unsigned int align,
      resolving it.  */
   if (TREE_CODE (exp) == NOP_EXPR
       && POINTER_TYPE_P (TREE_TYPE (exp))
-      && targetm.addr_space.valid_pointer_mode
+      && addr_space_valid_pointer_mode
 	   (SCALAR_INT_TYPE_MODE (TREE_TYPE (exp)),
 	    TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (exp)))))
     {
@@ -5188,7 +5189,7 @@ output_constant (tree exp, unsigned HOST_WIDE_INT size, unsigned int align,
 	 pointer modes.  */
       while (TREE_CODE (exp) == NOP_EXPR
 	     && POINTER_TYPE_P (TREE_TYPE (exp))
-	     && targetm.addr_space.valid_pointer_mode
+	     && addr_space_valid_pointer_mode
 		  (SCALAR_INT_TYPE_MODE (TREE_TYPE (exp)),
 		   TYPE_ADDR_SPACE (TREE_TYPE (TREE_TYPE (exp)))))
 	exp = TREE_OPERAND (exp, 0);
