[PATCH gcc/* 2/2] gcc: stop using 'int' to represent sets of qualifiers

Arsen Arsenović <[email protected]> Sun, 5 Jul 2026 23:15:28 +0200
Newsgroups gmane.comp.gcc.patches,gmane.comp.gcc.fortran,gmane.comp.gcc.jit,gmane.comp.gcc.algol68,gmane.comp.gcc.rust
Message-ID <[email protected]>
The set of all qualifiers present on a type consists of the const,
volatile, restrict and atomic qualification, which may be either present
or absent, and the address space qualifier, which may be one of many
values (one of which is the "generic" address space, present on all
platforms, used in absence of another address space; it is also the
address space qualifier on which all standard library routines operate).

For the former four, 'int' serves us okay; using ints as sets is a
well-understood pattern (but it leads to problems like those described
here when those ints cease to be sets).  Since they're either present or
absent, treating an int as a bit-set is convenient and simple.  But,
with the introduction of address space qualifiers into the mix, the
semantics of these operators becomes incorrect.

Take, for instance, the following line of code:

  quals_union = quals1 | quals2;

... (where quals1, quals2 are TYPE_QUALS of some types)

Only in cases where DECODE_QUAL_ADDR_SPACE (quals1) ==
DECODE_QUAL_ADDR_SPACE (quals2), or where one of those address spaces is
generic, and the other a super/subset of the generic address space, does
this yields what the author intended.  In all other cases, the operation
above yields subtly incorrect results, while the compiler is (naturally)
silent about it.

This issue is not theoretical either.  In the implementation of the C++
Named Address Spaces support currently being worked on and discussed on
the mailing list[2], the following broken testcase arose (for the GCN
target; __flat address space is AS1, __lds is AS2, and __gds is AS3;
__flat is a superset address space to __lds):

  template<typename T>
  __flat T *
  foo ();

  void
  bar ()
  { **foo<__lds int> (); }

The above testcase produces the diagnostic:

  <stdin>:1:52: error: invalid type argument of unary '*' (have '__gds int')

It is obvious how this came about: the frontend used '|' to merge two
sets of qualifiers, presuming that such a merge can never fail, because
the union of two sets is a valid and complete function, and that the
bit-OR of these two bitsets is the union of the corresponding two sets
of qualifiers.

However, this is not so: with the introduction of address spaces, the
union of two sets of qualifiers ceases to coincide with the bit-OR
operator, and the union becomes a partial function.

To demonstrate the former, we can use the testcase above.  In it, the
C++ frontend was trying to take the union of the qualifier sets {__lds}
and {__flat}.  These were previously represented as 0x0100 and 0x0200.
Ergo, the result of the bit-op was 0x0300.

0x0300 corresponds to the qualifier set {__gds}.  This is how we got the
bad diagnostic above.  This is an instance of the "bit-OR no longer
coincides with set union (except by accident)" problem; the union of
those two sets (in most cases, anyway) is {__flat}.

But, even if we were to fix this initial problem, we still have the
problem of the union of qualifier sets becoming a partial function.

This one can be demonstrated in C, as the C frontend already deals with
this problem in an ad-hoc fashion.

In the following case, the __lds and __global address spaces are
distinct (though they are both subsets of the __flat address space):

  typedef __lds int lds_int;
  void foo (__global lds_int *x);

... the C frontend issues the diagnostic:

  <stdin>:2:31: error: conflicting named address spaces (__global vs __lds)

It had to do so because the union of the qualifier sets {__lds} and
{__global} for the purposes of this declaration does not exist.[1]

The way the C FE handles this is ad-hoc: in 'grokdeclarator', it has a
specific check that covers this case:

  if (!ADDR_SPACE_GENERIC_P (as1) && !ADDR_SPACE_GENERIC_P (as2) && as1 != as2)
    error_at (loc, "conflicting named address spaces (%s vs %s)",
	      c_addr_space_name (as1), c_addr_space_name (as2));

Thus, it is quite easy for developers to forget this check.

What's worse, there is a lot of existing code that presumes that the set
of qualifiers is actually the set of the four "simple" present/absent
qualifier (CVRA).  If that presumption changes, there's no way to
diagnose all sites where this presumption is now broken (as it is in the
C++ NAS support patch).

The C++ type system is completely capable of encoding the restrictions
above, and ergo diagnosing misuse.  Making use of that is the goal of
this patch.

First, we make a distinction between cv_qualifier and qualifier_set, as
many frontends do not care about anything but the cv-qualifiers, and
because the former have operations not applicable to the latter.

The former models the CVRA qualifiers, which are either present or
absent.

In this revision, I left it as an unscoped enum, but fixed its
underlying type as 'unsigned char' (so that its size is known to be less
than that of qualifier_set).  It may be desirable to make it an enum
class, to forbid the usage of operators like + on it.  This is a lower
priority since there's no existing code that does so.

The latter (qualifier_set) models the set of all qualifiers, i.e. CVRA +
the address space qualifier (at the moment).

The interesting bits of the patch are in tree-core.h and tree.h.  These
two provide the new types and matching helper functions.  It may be
worth breaking them out into bits-style headers, though.  I've tried to
curb the growth of those headers too much, but the constexpr operators
and functions were actually needed a few times.

For cv_qualifiers, I've provided binary bitwise operations, in order to
inhibit integer promotion.  This makes it so that manual casting isn't
necessary when using cv_qualifier values.

For qualifier_set, tree.h lost operations that were made redundant/wrong
by the change.  In their place, I've provided functions for modifying
and reading qualifier_set values.

Note, however, the decision to drop operator& for qualifier_sets.  As it
turns out, many places in the codebase used patterns like 'q & ~p' to
remove qualifier P from set Q, but this became incorrect, as it loses
the address space qualifier also.

This operation was also often used for simple presence checks, by simply
checking 'q & p', so I initially made operator& return 'bool', but this
turned out to silently change the meaning of some existing code, where
a pattern like 'int cqual = q & TYPE_QUALS_CONST' appeared.

Hence, I decided it is better not to provide this operator as it opens
the possibility for easy misuse, and because 'without', 'intercept' and
'has' are quite short anyway.

Qualifier sets may be decomposed into (currently) a pair, that may be
destructured via std::tie.  This was provided as such to allow inducing
errors should a new component ever appear on qualifiers sets, even
though this is quite an unlikely eventuality.  In essence, should
qualifier sets grow to contain one more member, all the places that do:

  std::tie (cvquals, addrspace) = quals.split ();

... would yell, letting us know what to fix.

The qualifier set type is 16 bits, and trivially copyable and
destructible, and so, fits into registers on most machines.  Most of the
operations on the qualifier set type are also provided as constexpr
functions, and so, should be very easy for the compiler to optimize
away.

The two union operations provided for qualifier sets now are merge and
join.  These differ in that the former is apt for finding qualification
that can be used in common for two objects, and that the latter can be
used to add qualification to an existing qualifier set
"syntactically" (i.e. as if the keywords were just added to the original
source code from which the qualifier set was constructed).  These two
operations were most common in the C++ frontend, especially the latter.

This version of the patch does not extensively refactor the C frontend
to utilize the new operations; since it is a blocker for the C++ Named
Address Spaces support, I didn't prioritize that.

Reg-strapped on x86_64-linux-gnu, powerpc64le-linux-gnu, and
(currently being) tested on amdgcn-amdhsa and s390x-ibm-linux-gnu.
Build-tested for rl78-elf.

No functional changes intended.

[1] Note that there's actually a few distinct union operations that
    could exist, depending on the context.  For instance, one would
    expect '__AS1 some_typedef', where 'some_typedef' is in the generic
    address space, to always be acceptable in a declaration, but, for
    two values __AS1 T* p1 and T* pG, the expression cond ? p1 : pG is
    not acceptable if __AS1 is not subset of the generic address space
    or vice-versa, despite both performing something that can be
    described as a qualifier-set union of {__AS1} and {}.
[2] https://inbox.sourceware.org/gcc-patches/[email protected]/

gcc/ada/ChangeLog:

	* gcc-interface/decl.cc (gnat_to_gnu_entity): Update uses of
	qualifiers not to use 'int'.
	(gnat_to_gnu_component_type): Ditto.
	* gcc-interface/gigi.h (ada_type_quals): New.  Helper function
	returning qualifiers for a type relevant to Ada.
	(change_qualified_type): Update not to use 'int' for qualifiers.
	* gcc-interface/utils.cc (update_pointer_to): Use ada_type_quals
	instead of TYPE_QUALS.

gcc/ChangeLog:

	* attribs.cc (decl_attributes): Update to use qualifier_sets
	instead of ints for qualifiers.
	(build_type_attribute_qual_variant): Ditto.
	(attr_access::array_as_string): Ditto.
	* attribs.h (build_type_attribute_qual_variant): Ditto.
	* config/gcn/gcn-tree.cc (gcn_goacc_get_worker_red_decl): Update
	not to use 'int' for qualifiers, and to use new qualifier_set
	APIs.
	(gcn_goacc_adjust_private_decl): Ditto.
	(gcn_goacc_create_worker_broadcast_record): Ditto.
	* config/i386/i386-builtins.cc (ix86_get_builtin_type): Use
	cv_qualifier instead of 'int' when dealing with qualifiers.
	* config/i386/i386.cc (ix86_stack_protect_guard): Update
	not to use 'int' for qualifiers, and to use new qualifier_set
	APIs.
	* config/rl78/rl78.cc (rl78_insert_attributes): Update
	not to use 'int' for qualifiers, and to use new qualifier_set
	APIs.
	* config/rs6000/rs6000-c.cc (altivec_resolve_overloaded_builtin):
	Update not to use 'int' for qualifiers, and to use new
	qualifier_set APIs.
	* config/rs6000/rs6000.cc (rs6000_handle_altivec_attribute):
	Update not to use 'int' for qualifiers, and to use new
	qualifier_set APIs.
	* config/s390/s390-c.cc (s390_fn_types_compatible): Update to
	use new qualifier APIs.
	* coretypes.h (ADDR_SPACE_GENERIC): Make sure the constant is of
	type addr_space_t.
	* dwarf2out.cc (decl_quals): Use cv_qualifier instead of 'int'
	for qualifiers.
	(modified_type_die): Ditto.
	(add_type_attribute): Ditto.
	(subrange_type_die): Ditto.
	(get_nearest_type_subqualifiers): Ditto.
	(struct dwarf_qual_info_t): Ditto.
	(qualified_die_p): Ditto.
	(override_type_for_decl_p): Ditto.
	* fold-const.cc (fold_unary_loc): Use new qualifier_set APIs.
	* gimple-lower-bitint.cc (bitint_large_huge::limb_access): Use
	new qualifier_set APIs.
	(bitint_large_huge::build_bit_field_ref): Ditto.
	(bitint_large_huge::lower_stmt): Ditto.
	* gimplify.cc: Use new qualifier_set APIs.
	* ipa-free-lang-data.cc (free_lang_data_in_type): Use new
	qualifier_set APIs.
	* langhooks.h (struct qualifier_set): Forward-declare.
	(struct lang_hooks_for_tree_dump): Make TYPE_QUALS return
	qualifier_set.
	* langhooks-def.h (lhd_tree_dump_type_quals): Update signature
	to match above change.
	* langhooks.cc (lhd_tree_dump_dump_tree): Ditto.
	* omp-low.cc (install_var_field): Update to use new
	qualifier_set APIs.
	* omp-oacc-neuter-broadcast.cc (install_var_field): Update to
	use new qualifier_set APIs.
	* omp-offload.cc (oacc_rewrite_var_decl): Update to use new
	qualifier_set APIs.
	* tree-core.h (enum cv_qualifier): Set underlying type to
	'unsigned char'.
	(TYPE_QUAL_ALL): New.  Mask of all elements of cv_qualifier.
	(operator|): New.  Returns union of two CV-qualifier sets.
	(operator|=): New.  As above, but mutates LHS.
	(operator&): New.  Returns intersection of two CV-qualifier
	sets.
	(operator&=): New.  As above, but mutates LHS.
	(operator^): New.  Returns symmetric difference of two
	CV-qualifier sets.
	(operator^=): New.  As above, but mutates LHS.
	(operator~): New.  Flips state of all CV-qualifiers from present
	to absent in a CV-qualifier set and vice-versa, returning a new
	CV-qualifier set.
	* tree-dump.cc (dequeue_and_dump): Update to use new qualifier
	set APIs, and handle address spaces.
	* tree-inline.cc (remap_type_1): Update to use new qualifier_set
	APIs.
	* tree-pretty-print.cc (dump_generic_node): Update to use new
	qualifier set APIs.
	* tree-profile.cc (tree_profiling): Update to use new qualifier
	set APIs.
	* tree-sra.cc (build_ref_for_offset): Update to use new
	qualifier set APIs.
	* tree-ssa-address.cc (move_hint_to_base): Update to use new
	qualifier set APIs.
	* tree-switch-conversion.cc (switch_conversion::build_one_array):
	Update to use new qualifier set APIs.
	* tree-vect-stmts.cc (get_related_vectype_for_scalar_type):
	Update to use new qualifier set APIs.
	* tree.cc (set_type_quals): Update to use qualifier_set instead
	of 'int'.
	(qualifier_set::merge): New.
	(qualifier_set::join): New.
	(qualifier_set::can_qualify): New.
	(qualifier_set::debug): New.
	(check_base_type): Update to use new qualifier_set APIs.
	(check_qualified_type): Ditto.
	(get_qualified_type): Ditto.
	(build_qualified_type): Ditto.
	(make_vector_type): Ditto.
	(build_atomic_base): Ditto.
	* tree.h (ENCODE_QUAL_ADDR_SPACE): Drop.
	(DECODE_QUAL_ADDR_SPACE): Drop.
	(CLEAR_QUAL_ADDR_SPACE): Drop.
	(KEEP_QUAL_ADDR_SPACE): Drop.
	(struct qualifier_set): New.  Provides a representation for a
	set of CV-qualifiers and an address space qualifier, as well as
	various operations that pertain to such sets.
	(TYPE_QUALS): Update to produce a qualifier_set.
	(operator|): New.  Convenience operator for adding CV-qualifiers
	to a qualifier_set.
	(operator|=): New.  Like the above, but mutates LHS.
	(operator^): New.  Convenience operator for taking the symmetric
	difference between a qualifier_set and a CV-qualifier set.
	(operator^=): New.  Like the above, but mutates LHS.
	(TYPE_QUALS_NO_ADDR_SPACE):  Update to produce cv_qualifier.
	(TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC): Ditto.
	(check_qualified_type): Update signature to use qualifier_set
	instead of ints.
	(get_qualified_type): Ditto.
	(build_qualified_type): Ditto.
	(build_type_variant): Ditto.
	(qualifier_set::qualifier_set): New.  Constructs a qualifier set
	from a TREE_NODE.  Used where new expansion of TYPE_QUALS (which
	contains a comma) breaks other macros.  :-(
	* ubsan.cc (instrument_bool_enum_load): Update to use new
	qualifier set APIs.
	* vtable-verify.cc (vtbl_map_get_node): Update to use new
	qualifier set APIs.
	(find_or_create_vtbl_map_node): Ditto.

gcc/c-family/ChangeLog:

	* c-ada-spec.cc (dump_ada_node): Update to use new qualifier set
	APIs.
	* c-common.cc (c_apply_type_quals_to_decl): Update to receive
	cv_qualifier.
	(complete_array_type): Use qualifier_set instead of 'int' for
	sets of qualifiers.
	(get_atomic_generic_size): Update to use new qualifier set APIs.
	* c-common.h (c_apply_type_quals_to_decl): Update to receive
	cv_qualifier.  Provide an overload that takes a qualifier_set as
	convenience.
	(c_build_qualified_type): Update to receive qualifier_set
	instead of an int.
	* c-format.cc (deref_n_times): Use TYPE_UNQUALIFIED instead of
	constant zero.
	* c-pretty-print.cc (pp_c_cv_qualifiers): Update to receive
	cv_qualifier.
	(pp_c_type_qualifier_list): Stop using 'int' for CV-qualifier
	sets.
	(c_pretty_printer::direct_abstract_declarator): Ditto.
	* c-pretty-print.h (pp_c_cv_qualifiers): Update to receive
	cv_qualifier.

gcc/c/ChangeLog:

	* c-aux-info.cc (gen_type): Update to use new qualifier_set
	APIs.
	* c-decl.cc (diagnose_mismatched_decls): Ditto.
	(quals_from_declspecs): Update to return qualifier sets.
	(build_array_declarator): Use qualifier_set instead of ints.
	(diagnose_uninitialized_cst_member): Update to use new
	qualifier_set APIs.
	(grokdeclarator): Ditto.
	(get_parm_info): Update to use new qualifier_set APIs.
	(c_update_type_canonical): Ditto.
	(finish_struct): Ditto.
	(make_pointer_declarator): Use qualifier_set instead of ints.
	* c-objc-common.cc (c_tree_printer): Convert %v arg to
	cv_qualifier.
	* c-parser.cc (c_parser_declspecs): Update to use qualifier_set
	APIs.
	(c_parser_typeof_specifier): Ditto.
	(c_parser_generic_selection): Ditto.
	(c_parser_postfix_expression_after_paren_type): Ditto.
	* c-tree.h (struct c_declarator): Update to use qualifier_set
	instead of ints.
	(quals_from_declspecs): Update to use qualifier_set.
	* c-typeck.cc (null_pointer_constant_p): Update to use
	qualifier_set.
	(qualify_type): Update to use new qualifier_set APIs.
	(c_build_array_type): Ditto.
	(c_build_type_attribute_qual_variant): Update to use
	qualifier_set.
	(composite_type_internal): Ditto.
	(common_pointer_type): Ditto.
	(c_common_type): Ditto.
	(function_types_compatible_p): Ditto.
	(convert_lvalue_to_rvalue): Ditto.
	(build_component_ref): Ditto.
	(build_function_call_vec): Ditto.
	(build_unary_op): Ditto.
	(build_conditional_expr): Ditto.
	(handle_warn_cast_qual): Ditto.
	(convert_for_assignment): Ditto.
	(build_binary_op): Ditto.
	(c_build_qualified_type): Ditto.

gcc/cp/ChangeLog:

	* call.cc (strip_top_quals): Use TYPE_UNQUALIFIED instead of 0.
	(standard_conversion): Update to use qualifier_set APIs.
	* class.cc (build_simple_base_path): Update to use qualifier_set
	instead of int.
	* cp-objcp-common.h (cp_type_quals_as_set): Add wrapper, to
	allow converting cv_qualifier to qualifier_set.
	(LANG_HOOKS_TREE_DUMP_TYPE_QUALS_FN): Use said wrapper.
	* cp-tree.h (cp_cv_quals): Change to be cv_qualifier.
	(build_stub_type): Update to receive cv_qualifier.
	(cp_build_qualified_type): Ditto.
	(cp_type_quals): Update to return cv_qualifier.
	(type_memfn_quals): Ditto.
	(cp_apply_type_quals_to_decl): Update to take cv_qualifier.
	* decl.cc (get_type_quals): Update to use and return
	cv_qualifier.
	(cp_finish_decomp): Replace usages of TYPE_QUALS with
	cp_type_quals.
	(grokdeclarator): Update to use cv_qualifier.
	(grokparms): Use TYPE_UNQUALIFIED instead of literal zero.
	* error.cc (dump_lambda_function): Update to use qualifier_set
	APIs.
	* mangle.cc (write_CV_qualifiers_for_type): Ditto.
	* method.cc (do_build_copy_constructor): Update to use
	cv_qualifier instead of int.
	(do_build_copy_assign): Ditto.
	(build_stub_type): Update to receive cv_qualifier.
	(get_copy_ctor): Update to use cv_qualifier instead of int.
	(get_copy_assign): Ditto.
	(walk_field_subobs): Update to receive cv_qualifier.
	(synthesized_method_base_walk): Ditto.
	(synthesized_method_walk): Ditto.
	(implicitly_declare_fn): Update to use cv_qualifier.
	* module.cc (trees_in::tree_node): Cast read-in qualifiers to
	cv_qualifier.
	* pt.cc (tsubst): Update to use cv_qualifier instead of int.
	(resolve_typename_type): Ditto.
	* reflect.cc (type_of): Ditto.
	(eval_remove_volatile): Ditto.
	(eval_add_const): Ditto.
	(eval_add_volatile): Ditto.
	(eval_add_cv): Ditto.
	(eval_make_signed): Ditto.
	* semantics.cc (finish_non_static_data_member): Ditto.
	(finish_decltype_type): Ditto.
	* tree.cc (c_build_qualified_type): Update to receive
	qualifier_set, in line with original.
	(cp_build_qualified_type): Update to receive qualifier_set.
	(cp_check_qualified_type): Update to receive cv_qualifier.
	(cv_unqualified): Update to use cv_qualifier.
	(build_cp_fntype_variant): Ditto.
	(maybe_dummy_object): Ditto.
	* typeck.cc (original_type): Ditto.
	(composite_pointer_type_r): Ditto.
	(merge_types): Ditto.
	(build_class_member_access_expr): Ditto.
	(cp_type_quals): Update to return cv_qualifier.
	(type_memfn_quals): Ditto.
	(cp_apply_type_quals_to_decl): Update to receive cv_qualifier.
	(casts_away_constness_r): Use cv_qualifier instead of int for
	representing CV-qualification.

gcc/d/ChangeLog:

	* d-codegen.cc (build_vthis_function): Update to use new
	qualifier_set APIs.
	* types.cc (insert_type_modifiers): Use cv_qualifier instead
	of int.

gcc/fortran/ChangeLog:

	* trans-openmp.cc (gfc_omp_finish_clause): Update to use new
	qualifier_set APIs.
	* trans-types.cc (gfc_nonrestricted_type): Ditto.

gcc/jit/ChangeLog:

	* dummy-frontend.cc (tree_type_to_jit_type):Update to use new
	qualifier_set APIs.

gcc/objc/ChangeLog:

	* objc-act.cc (objc_push_parm): Use TYPE_QUALS instead of
	inlining its definition.

gcc/rust/ChangeLog:

	* backend/rust-tree.cc (rs_type_quals): Update to use and return
	cv_qualifier.
	(type_memfn_quals): Ditto.
	(rs_build_qualified_type_real): Update to receive and use
	cv_qualifiers.
	(cv_unqualified): Update to use cv_qualifier.
	(strip_top_quals): Replace literal zero with TYPE_UNQUALIFIED.
	* backend/rust-tree.h (rs_type_quals): Update to return
	cv_qualifier.
	(type_memfn_quals): Ditto.
	(rs_build_qualified_type_real): Update to receive
	cv_qualifier. Remove duplicate declaration.
	(rs_build_qualified_type): Remove duplicate definition.

libcc1/ChangeLog:

	* libcc1plugin.cc (plugin_build_qualified_type): Update to use
	qualifier_set.
	* libcp1plugin.cc (plugin_build_method_type): Fix up
	initialization of cp_cv_quals.
	(plugin_build_qualified_type): Ditto.
---
 gcc/ada/gcc-interface/decl.cc    |  15 +-
 gcc/ada/gcc-interface/gigi.h     |  14 +-
 gcc/ada/gcc-interface/utils.cc   |   3 +-
 gcc/attribs.cc                   |   9 +-
 gcc/attribs.h                    |   2 +-
 gcc/c-family/c-ada-spec.cc       |   6 +-
 gcc/c-family/c-common.cc         |  18 +-
 gcc/c-family/c-common.h          |   8 +-
 gcc/c-family/c-format.cc         |   2 +-
 gcc/c-family/c-pretty-print.cc   |   9 +-
 gcc/c-family/c-pretty-print.h    |   2 +-
 gcc/c/c-aux-info.cc              |   2 +-
 gcc/c/c-decl.cc                  | 144 +++++++-------
 gcc/c/c-objc-common.cc           |   4 +-
 gcc/c/c-parser.cc                |  16 +-
 gcc/c/c-tree.h                   |   6 +-
 gcc/c/c-typeck.cc                | 108 ++++++-----
 gcc/config/gcn/gcn-tree.cc       |  11 +-
 gcc/config/i386/i386-builtins.cc |   2 +-
 gcc/config/i386/i386.cc          |   2 +-
 gcc/config/rl78/rl78.cc          |   2 +-
 gcc/config/rs6000/rs6000-c.cc    |   6 +-
 gcc/config/rs6000/rs6000.cc      |   2 +-
 gcc/config/s390/s390-c.cc        |   6 +-
 gcc/coretypes.h                  |   2 +-
 gcc/cp/call.cc                   |   8 +-
 gcc/cp/class.cc                  |   2 +-
 gcc/cp/cp-objcp-common.h         |   5 +-
 gcc/cp/cp-tree.h                 |  12 +-
 gcc/cp/decl.cc                   |  12 +-
 gcc/cp/error.cc                  |   2 +-
 gcc/cp/mangle.cc                 |   7 +-
 gcc/cp/method.cc                 |  32 +--
 gcc/cp/module.cc                 |   3 +-
 gcc/cp/pt.cc                     |  11 +-
 gcc/cp/reflect.cc                |  12 +-
 gcc/cp/semantics.cc              |   4 +-
 gcc/cp/tree.cc                   |  34 ++--
 gcc/cp/typeck.cc                 |  42 ++--
 gcc/d/d-codegen.cc               |   2 +-
 gcc/d/types.cc                   |   2 +-
 gcc/dwarf2out.cc                 |  46 ++---
 gcc/fold-const.cc                |   2 +-
 gcc/fortran/trans-openmp.cc      |   2 +-
 gcc/fortran/trans-types.cc       |   3 +-
 gcc/gimple-lower-bitint.cc       |  21 +-
 gcc/gimplify.cc                  |  13 +-
 gcc/ipa-free-lang-data.cc        |   5 +-
 gcc/jit/dummy-frontend.cc        |   2 +-
 gcc/langhooks-def.h              |   2 +-
 gcc/langhooks.cc                 |   2 +-
 gcc/langhooks.h                  |   5 +-
 gcc/objc/objc-act.cc             |   6 +-
 gcc/omp-low.cc                   |   4 +-
 gcc/omp-oacc-neuter-broadcast.cc |   4 +-
 gcc/omp-offload.cc               |  21 +-
 gcc/rust/backend/rust-tree.cc    |  35 ++--
 gcc/rust/backend/rust-tree.h     |  10 +-
 gcc/tree-core.h                  |  68 ++++++-
 gcc/tree-dump.cc                 |   7 +-
 gcc/tree-inline.cc               |   4 +-
 gcc/tree-pretty-print.cc         |  14 +-
 gcc/tree-profile.cc              |   2 +-
 gcc/tree-sra.cc                  |   3 +-
 gcc/tree-ssa-address.cc          |   3 +-
 gcc/tree-switch-conversion.cc    |   3 +-
 gcc/tree-vect-stmts.cc           |   3 +-
 gcc/tree.cc                      | 149 ++++++++++++--
 gcc/tree.h                       | 322 ++++++++++++++++++++++++++++---
 gcc/ubsan.cc                     |   4 +-
 gcc/vtable-verify.cc             |  10 +-
 libcc1/libcc1plugin.cc           |   2 +-
 libcc1/libcp1plugin.cc           |   4 +-
 73 files changed, 951 insertions(+), 421 deletions(-)

diff --git a/gcc/ada/gcc-interface/decl.cc b/gcc/ada/gcc-interface/decl.cc
index e569eb1bddf1..0f5ea69207bb 100644
--- a/gcc/ada/gcc-interface/decl.cc
+++ b/gcc/ada/gcc-interface/decl.cc
@@ -3041,9 +3041,10 @@ gnat_to_gnu_entity (Entity_Id gnat_entity, tree gnu_expr, bool definition)
 	      process_attributes (&gnu_type, &attr_list, false, gnat_entity);
 	      if (Treat_As_Volatile (gnat_entity))
 		{
-		  const int quals
+		  const auto quals
 		    = TYPE_QUAL_VOLATILE
-		      | (Is_Full_Access (gnat_entity) ? TYPE_QUAL_ATOMIC : 0);
+		      | (Is_Full_Access (gnat_entity)
+			 ? TYPE_QUAL_ATOMIC : TYPE_UNQUALIFIED);
 		  gnu_type = change_qualified_type (gnu_type, quals);
 		}
 	      /* Make it artificial only if the base type was artificial too.
@@ -4992,9 +4993,10 @@ gnat_to_gnu_entity (Entity_Id gnat_entity, tree gnu_expr, bool definition)
       if (Treat_As_Volatile (gnat_entity)
 	  && !Is_Packed_Array_Impl_Type (gnat_entity))
 	{
-	  const int quals
+	  const auto quals
 	    = TYPE_QUAL_VOLATILE
-	      | (Is_Full_Access (gnat_entity) ? TYPE_QUAL_ATOMIC : 0);
+	      | (Is_Full_Access (gnat_entity)
+		 ? TYPE_QUAL_ATOMIC : TYPE_UNQUALIFIED);
 	  /* This is required by free_lang_data_in_type to disable the ODR.  */
 	  if (TREE_CODE (gnu_type) == ENUMERAL_TYPE)
 	    TYPE_STUB_DECL (gnu_type)
@@ -5666,9 +5668,10 @@ gnat_to_gnu_component_type (Entity_Id gnat_array, bool definition,
 
   if (Has_Volatile_Components (gnat_array))
     {
-      const int quals
+      const auto quals
 	= TYPE_QUAL_VOLATILE
-	  | (Has_Atomic_Components (gnat_array) ? TYPE_QUAL_ATOMIC : 0);
+	  | (Has_Atomic_Components (gnat_array)
+	     ? TYPE_QUAL_ATOMIC : TYPE_UNQUALIFIED);
       gnu_type = change_qualified_type (gnu_type, quals);
     }
 
diff --git a/gcc/ada/gcc-interface/gigi.h b/gcc/ada/gcc-interface/gigi.h
index f4d791195187..3fd85debd9be 100644
--- a/gcc/ada/gcc-interface/gigi.h
+++ b/gcc/ada/gcc-interface/gigi.h
@@ -1248,11 +1248,23 @@ gnat_signed_type_for (tree type_node)
   return gnat_signed_or_unsigned_type_for (0, type_node);
 }
 
+/* Like TYPE_QUALS, but doesn't return qualifiers that GNAT doesn't use.  */
+static inline cv_qualifier
+ada_type_quals (const_tree type)
+{
+  addr_space_t as;
+  cv_qualifier cv;
+  std::tie (cv, as) = TYPE_QUALS (type).split ();
+  /* Ada does not support address spaces (at the moment).  */
+  gcc_assert (ADDR_SPACE_GENERIC_P (as));
+  return cv;
+}
+
 /* Like build_qualified_type, but TYPE_QUALS is added to the existing
    qualifiers on TYPE.  */
 
 static inline tree
-change_qualified_type (tree type, int type_quals)
+change_qualified_type (tree type, cv_qualifier type_quals)
 {
   /* Qualifiers must be put on the associated array type.  */
   if (TREE_CODE (type) == UNCONSTRAINED_ARRAY_TYPE)
diff --git a/gcc/ada/gcc-interface/utils.cc b/gcc/ada/gcc-interface/utils.cc
index f74b6361b404..81d53baa390b 100644
--- a/gcc/ada/gcc-interface/utils.cc
+++ b/gcc/ada/gcc-interface/utils.cc
@@ -4656,7 +4656,8 @@ update_pointer_to (tree old_type, tree new_type)
      initial set, and will often end up with OLD_TYPE == NEW_TYPE then.  */
   new_type
     = build_qualified_type (new_type,
-			    TYPE_QUALS (old_type) | TYPE_QUALS (new_type));
+			    ada_type_quals (old_type)
+			    | ada_type_quals (new_type));
 
   /* If old type and new type are identical, there is nothing to do.  */
   if (old_type == new_type)
diff --git a/gcc/attribs.cc b/gcc/attribs.cc
index cd4be1bd6c5f..a7bc7895dc46 100644
--- a/gcc/attribs.cc
+++ b/gcc/attribs.cc
@@ -726,7 +726,7 @@ decl_attributes (tree *node, tree attributes, int flags,
       tree *anode = node;
       const struct attribute_spec *spec
 	= lookup_scoped_attribute_spec (ns, name);
-      int fn_ptr_quals = 0;
+      qualifier_set fn_ptr_quals {};
       tree fn_ptr_tmp = NULL_TREE;
       const bool cxx11_attr_p = cxx11_attribute_p (attr);
 
@@ -1012,7 +1012,7 @@ decl_attributes (tree *node, tree attributes, int flags,
 	  /* Rebuild the function pointer type and put it in the
 	     appropriate place.  */
 	  fn_ptr_tmp = build_pointer_type (fn_ptr_tmp);
-	  if (fn_ptr_quals)
+	  if (fn_ptr_quals != qualifier_set {})
 	    fn_ptr_tmp = build_qualified_type (fn_ptr_tmp, fn_ptr_quals);
 	  if (DECL_P (*node))
 	    TREE_TYPE (*node) = fn_ptr_tmp;
@@ -1307,7 +1307,8 @@ build_decl_attribute_variant (tree ddecl, tree attribute)
    Record such modified types already made so we don't make duplicates.  */
 
 tree
-build_type_attribute_qual_variant (tree otype, tree attribute, int quals)
+build_type_attribute_qual_variant (tree otype, tree attribute,
+				   qualifier_set quals)
 {
   tree ttype = otype;
   if (! attribute_list_equal (TYPE_ATTRIBUTES (ttype), attribute))
@@ -2712,7 +2713,7 @@ attr_access::array_as_string (tree type) const
 	  arat = build_tree_list (get_identifier ("array "), flag);
 	}
 
-      const int quals = TYPE_QUALS (type);
+      const auto quals = TYPE_QUALS (type);
       type = build_array_type (eltype, index_type);
       type = build_type_attribute_qual_variant (type, arat, quals);
     }
diff --git a/gcc/attribs.h b/gcc/attribs.h
index 9f9abc5e4c32..b3881d9a91e2 100644
--- a/gcc/attribs.h
+++ b/gcc/attribs.h
@@ -67,7 +67,7 @@ extern void handle_ignored_attributes_option (vec<char *> *);
 
 extern tree build_type_attribute_variant (tree, tree);
 extern tree build_decl_attribute_variant (tree, tree);
-extern tree build_type_attribute_qual_variant (tree, tree, int);
+extern tree build_type_attribute_qual_variant (tree, tree, qualifier_set);
 
 extern bool simple_cst_list_equal (const_tree, const_tree);
 extern bool attribute_value_equal (const_tree, const_tree);
diff --git a/gcc/c-family/c-ada-spec.cc b/gcc/c-family/c-ada-spec.cc
index b06f78742a0b..857c3c8711a3 100644
--- a/gcc/c-family/c-ada-spec.cc
+++ b/gcc/c-family/c-ada-spec.cc
@@ -2313,7 +2313,7 @@ dump_ada_node (pretty_printer *pp, tree node, tree type, int spc,
       else
 	{
 	  tree ref_type = TREE_TYPE (node);
-	  const unsigned int quals = TYPE_QUALS (ref_type);
+	  const auto quals = TYPE_QUALS (ref_type);
 	  bool is_access;
 
 	  if (VOID_TYPE_P (ref_type))
@@ -2377,12 +2377,12 @@ dump_ada_node (pretty_printer *pp, tree node, tree type, int spc,
 			  is_access = true;
 			  pp_string (pp, "access ");
 
-			  if (quals & TYPE_QUAL_CONST)
+			  if (quals.has (TYPE_QUAL_CONST))
 			    pp_string (pp, "constant ");
 			  else if (!name_only)
 			    pp_string (pp, "all ");
 			}
-		      else if (quals & TYPE_QUAL_CONST)
+		      else if (quals.has (TYPE_QUAL_CONST))
 			{
 			  is_access = false;
 			  pp_string (pp, "in ");
diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index a16288f4441c..87111a8ac4a4 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -3875,7 +3875,7 @@ static void def_builtin_1  (enum built_in_function fncode,
 /* Apply the TYPE_QUALS to the new DECL.  */
 
 void
-c_apply_type_quals_to_decl (int type_quals, tree decl)
+c_apply_type_quals_to_decl (cv_qualifier type_quals, tree decl)
 {
   tree type = TREE_TYPE (decl);
 
@@ -7353,7 +7353,8 @@ int
 complete_array_type (tree *ptype, tree initial_value, bool do_default)
 {
   tree maxindex, type, main_type, elt, unqual_elt;
-  int failure = 0, quals;
+  int failure = 0;
+  qualifier_set quals;
   bool overflow_p = false;
 
   maxindex = size_zero_node;
@@ -7447,10 +7448,11 @@ complete_array_type (tree *ptype, tree initial_value, bool do_default)
   type = *ptype;
   elt = TREE_TYPE (type);
   quals = TYPE_QUALS (strip_array_types (elt));
-  if (quals == 0)
+  if (quals == qualifier_set {})
     unqual_elt = elt;
   else
-    unqual_elt = c_build_qualified_type (elt, KEEP_QUAL_ADDR_SPACE (quals));
+    unqual_elt = c_build_qualified_type (elt,
+					 quals.without (TYPE_QUAL_ALL));
 
   /* Using build_distinct_type_copy and modifying things afterward instead
      of using build_array_type to create a new type preserves all of the
@@ -7486,7 +7488,7 @@ complete_array_type (tree *ptype, tree initial_value, bool do_default)
 			  TYPE_CANONICAL (TYPE_DOMAIN (main_type)),
 			  TYPE_TYPELESS_STORAGE (main_type));
 
-  if (quals == 0)
+  if (quals == qualifier_set {})
     type = main_type;
   else
     type = c_build_qualified_type (main_type, quals);
@@ -8004,9 +8006,9 @@ get_atomic_generic_size (location_t loc, tree function,
 
       {
 	auto_diagnostic_group d;
-	int quals = TYPE_QUALS (TREE_TYPE (type));
+	auto quals = TYPE_QUALS (TREE_TYPE (type));
 	/* Must not write to an argument of a const-qualified type.  */
-	if (outputs & (1 << x) && quals & TYPE_QUAL_CONST)
+	if (outputs & (1 << x) && quals.has (TYPE_QUAL_CONST))
 	  {
 	    if (c_dialect_cxx ())
 	      {
@@ -8023,7 +8025,7 @@ get_atomic_generic_size (location_t loc, tree function,
 		       function);
 	  }
 	/* Only the first argument is allowed to be volatile.  */
-	if (x > 0 && quals & TYPE_QUAL_VOLATILE)
+	if (x > 0 && quals.has (TYPE_QUAL_VOLATILE))
 	  {
 	    if (c_dialect_cxx ())
 	      {
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 5711e1740498..fe933e5ca976 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -897,7 +897,10 @@ extern bool decl_with_nonnull_addr_p (const_tree);
 extern tree c_fully_fold (tree, bool, bool *, bool = false);
 extern tree c_wrap_maybe_const (tree, bool);
 extern tree c_common_truthvalue_conversion (location_t, tree);
-extern void c_apply_type_quals_to_decl (int, tree);
+extern void c_apply_type_quals_to_decl (cv_qualifier, tree);
+inline void
+c_apply_type_quals_to_decl (qualifier_set qs, tree decl)
+{ c_apply_type_quals_to_decl (qs.cv_quals (), decl); }
 extern tree c_sizeof_or_alignof_type (location_t, tree, bool, bool, int);
 extern tree c_alignof_expr (location_t, tree);
 extern tree c_countof_type (location_t, tree);
@@ -976,7 +979,8 @@ extern tree pointer_int_sum (location_t, enum tree_code, tree, tree,
 			     bool = true);
 
 /* Add qualifiers to a type, in the fashion for C.  */
-extern tree c_build_qualified_type (tree, int, tree = NULL_TREE, size_t = 0);
+extern tree c_build_qualified_type (tree, qualifier_set, tree = NULL_TREE,
+				    size_t = 0);
 
 /* Build tree nodes and builtin functions common to both C and C++ language
    frontends.  */
diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc
index 1eb8f90747d5..baae9a3bc693 100644
--- a/gcc/c-family/c-format.cc
+++ b/gcc/c-family/c-format.cc
@@ -4391,7 +4391,7 @@ deref_n_times (tree type, int n)
       type = TREE_TYPE (type);
     }
   /* Strip off any "const" etc.  */
-  return build_qualified_type (type, 0);
+  return build_qualified_type (type, TYPE_UNQUALIFIED);
 }
 
 /* Lookup the format code for FORMAT_LEN within FLI,
diff --git a/gcc/c-family/c-pretty-print.cc b/gcc/c-family/c-pretty-print.cc
index b084163e6a35..429134f68d02 100644
--- a/gcc/c-family/c-pretty-print.cc
+++ b/gcc/c-family/c-pretty-print.cc
@@ -168,7 +168,8 @@ pp_c_exclamation (c_pretty_printer *pp)
 /* Print out the external representation of QUALIFIERS.  */
 
 void
-pp_c_cv_qualifiers (c_pretty_printer *pp, int qualifiers, bool func_type)
+pp_c_cv_qualifiers (c_pretty_printer *pp, cv_qualifier qualifiers,
+		    bool func_type)
 {
   const char *p = pp_last_position_in_text (pp);
 
@@ -242,7 +243,7 @@ pp_c_space_for_pointer_operator (c_pretty_printer *pp, tree t)
 void
 pp_c_type_qualifier_list (c_pretty_printer *pp, tree t)
 {
-  int qualifiers;
+  cv_qualifier qualifiers;
 
   if (!t || t == error_mark_node)
     return;
@@ -252,7 +253,7 @@ pp_c_type_qualifier_list (c_pretty_printer *pp, tree t)
 
   if (TREE_CODE (t) != ARRAY_TYPE)
     {
-      qualifiers = TYPE_QUALS (t);
+      qualifiers = TYPE_QUALS_NO_ADDR_SPACE (t);
       pp_c_cv_qualifiers (pp, qualifiers,
 			  TREE_CODE (t) == FUNCTION_TYPE);
     }
@@ -627,7 +628,7 @@ c_pretty_printer::direct_abstract_declarator (tree t)
     case ARRAY_TYPE:
       pp_c_left_bracket (this);
 
-      if (int quals = TYPE_QUALS (t))
+      if (auto quals = TYPE_QUALS_NO_ADDR_SPACE (t))
 	{
 	  /* Print the array qualifiers such as in "T[const restrict 3]".  */
 	  pp_c_cv_qualifiers (this, quals, false);
diff --git a/gcc/c-family/c-pretty-print.h b/gcc/c-family/c-pretty-print.h
index 90ae3d033eed..de1dfccb8cbc 100644
--- a/gcc/c-family/c-pretty-print.h
+++ b/gcc/c-family/c-pretty-print.h
@@ -128,7 +128,7 @@ void pp_c_space_for_pointer_operator (c_pretty_printer *, tree);
 void pp_c_tree_decl_identifier (c_pretty_printer *, tree);
 void pp_c_function_definition (c_pretty_printer *, tree);
 void pp_c_attributes_display (c_pretty_printer *, tree);
-void pp_c_cv_qualifiers (c_pretty_printer *pp, int qualifiers, bool func_type);
+void pp_c_cv_qualifiers (c_pretty_printer *pp, cv_qualifier, bool func_type);
 void pp_c_type_qualifier_list (c_pretty_printer *, tree);
 void pp_c_parameter_type_list (c_pretty_printer *, tree);
 void pp_c_specifier_qualifier_list (c_pretty_printer *, tree);
diff --git a/gcc/c/c-aux-info.cc b/gcc/c/c-aux-info.cc
index fc2c97b51479..311ea5caef39 100644
--- a/gcc/c/c-aux-info.cc
+++ b/gcc/c/c-aux-info.cc
@@ -405,7 +405,7 @@ gen_type (const char *ret_val, tree t, formals_style style)
 	  data_type = IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (t)));
 	  /* Normally, `unsigned' is part of the deal.  Not so if it comes
 	     with a type qualifier.  */
-	  if (TYPE_UNSIGNED (t) && TYPE_QUALS (t))
+	  if (TYPE_UNSIGNED (t) && TYPE_QUALS (t) != qualifier_set {})
 	    data_type = concat ("unsigned ", data_type, NULL);
 	  break;
 
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 4dc1e94394bc..8e35fc53ed94 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -2320,13 +2320,15 @@ diagnose_mismatched_decls (tree newdecl, tree olddecl,
 	}
       else
 	{
-	  int new_quals = TYPE_QUALS (newtype);
-	  int old_quals = TYPE_QUALS (oldtype);
+	  auto new_quals = TYPE_QUALS (newtype);
+	  auto old_quals = TYPE_QUALS (oldtype);
 
 	  if (new_quals != old_quals)
 	    {
-	      addr_space_t new_addr = DECODE_QUAL_ADDR_SPACE (new_quals);
-	      addr_space_t old_addr = DECODE_QUAL_ADDR_SPACE (old_quals);
+	      addr_space_t new_addr, old_addr;
+	      cv_qualifier new_cv, old_cv;
+	      std::tie (new_cv, new_addr) = new_quals.split ();
+	      std::tie (old_cv, old_addr) = old_quals.split ();
 	      if (new_addr != old_addr)
 		{
 		  if (ADDR_SPACE_GENERIC_P (new_addr))
@@ -2345,8 +2347,7 @@ diagnose_mismatched_decls (tree newdecl, tree olddecl,
 			   newdecl);
 		}
 
-	      if (CLEAR_QUAL_ADDR_SPACE (new_quals)
-		  != CLEAR_QUAL_ADDR_SPACE (old_quals))
+	      if (new_cv != old_cv)
 		error ("conflicting type qualifiers for %q+D", newdecl);
 	    }
 	  else
@@ -5440,14 +5441,13 @@ shadow_tag_warned (const struct c_declspecs *declspecs, int warned)
    bits.  SPECS represents declaration specifiers that the grammar
    only permits to contain type qualifiers and attributes.  */
 
-int
+qualifier_set
 quals_from_declspecs (const struct c_declspecs *specs)
 {
-  int quals = ((specs->const_p ? TYPE_QUAL_CONST : 0)
-	       | (specs->volatile_p ? TYPE_QUAL_VOLATILE : 0)
-	       | (specs->restrict_p ? TYPE_QUAL_RESTRICT : 0)
-	       | (specs->atomic_p ? TYPE_QUAL_ATOMIC : 0)
-	       | (ENCODE_QUAL_ADDR_SPACE (specs->address_space)));
+  auto cv_quals = cv_qualifier ((specs->const_p ? TYPE_QUAL_CONST : 0)
+				| (specs->volatile_p ? TYPE_QUAL_VOLATILE : 0)
+				| (specs->restrict_p ? TYPE_QUAL_RESTRICT : 0)
+				| (specs->atomic_p ? TYPE_QUAL_ATOMIC : 0));
   gcc_assert (!specs->type
 	      && !specs->decl_attr
 	      && specs->typespec_word == cts_none
@@ -5465,7 +5465,7 @@ quals_from_declspecs (const struct c_declspecs *specs)
 	      && !specs->inline_p
 	      && !specs->noreturn_p
 	      && !specs->thread_p);
-  return quals;
+  return {cv_quals, specs->address_space};
 }
 
 /* Construct an array declarator.  LOC is the location of the
@@ -5497,7 +5497,7 @@ build_array_declarator (location_t loc,
   else
     {
       declarator->u.array.attrs = NULL_TREE;
-      declarator->u.array.quals = 0;
+      declarator->u.array.quals = qualifier_set {};
     }
   declarator->u.array.static_p = static_p;
   declarator->u.array.vla_unspec_p = vla_unspec_p;
@@ -5984,7 +5984,7 @@ diagnose_uninitialized_cst_member (tree decl, tree type)
 	continue;
       field_type = strip_array_types (TREE_TYPE (field));
 
-      if (TYPE_QUALS (field_type) & TYPE_QUAL_CONST)
+      if (TYPE_QUALS (field_type).has (TYPE_QUAL_CONST))
       	{
 	  auto_diagnostic_group d;
 	  if (warning_at (DECL_SOURCE_LOCATION (decl), OPT_Wc___compat,
@@ -6880,14 +6880,14 @@ grokdeclarator (const struct c_declarator *declarator,
   int restrictp;
   int volatilep;
   int atomicp;
-  int type_quals = TYPE_UNQUALIFIED;
+  qualifier_set type_quals {};
   tree name = NULL_TREE;
   bool funcdef_flag = false;
   bool funcdef_syntax = false;
   bool size_varies = false;
   bool size_error = false;
   tree decl_attr = declspecs->decl_attr;
-  int array_ptr_quals = TYPE_UNQUALIFIED;
+  qualifier_set array_ptr_quals {};
   tree array_ptr_attrs = NULL_TREE;
   bool array_parm_static = false;
   bool array_parm_vla_unspec_p = false;
@@ -7094,16 +7094,17 @@ grokdeclarator (const struct c_declarator *declarator,
 
   if ((TREE_CODE (type) == ARRAY_TYPE
        || first_non_attr_kind == cdk_array)
-      && TYPE_QUALS (element_type))
+      && TYPE_QUALS (element_type) != qualifier_set {})
     {
       orig_qual_type = type;
       type = c_build_qualified_type (type, TYPE_UNQUALIFIED);
     }
-  type_quals = ((constp ? TYPE_QUAL_CONST : 0)
-		| (restrictp ? TYPE_QUAL_RESTRICT : 0)
-		| (volatilep ? TYPE_QUAL_VOLATILE : 0)
-		| (atomicp ? TYPE_QUAL_ATOMIC : 0)
-		| ENCODE_QUAL_ADDR_SPACE (address_space));
+  type_quals =
+     {(constp ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED)
+      | (restrictp ? TYPE_QUAL_RESTRICT : TYPE_UNQUALIFIED)
+      | (volatilep ? TYPE_QUAL_VOLATILE : TYPE_UNQUALIFIED)
+      | (atomicp ? TYPE_QUAL_ATOMIC : TYPE_UNQUALIFIED),
+      address_space};
   if (type_quals != TYPE_QUALS (element_type))
     orig_qual_type = NULL_TREE;
 
@@ -7252,7 +7253,7 @@ grokdeclarator (const struct c_declarator *declarator,
 	 array or function or pointer, and DECLARATOR has had its
 	 outermost layer removed.  */
 
-      if (array_ptr_quals != TYPE_UNQUALIFIED
+      if (array_ptr_quals != qualifier_set {}
 	  || array_ptr_attrs != NULL_TREE
 	  || array_parm_static)
 	{
@@ -7260,7 +7261,7 @@ grokdeclarator (const struct c_declarator *declarator,
 	     array type which is converted to pointer type)
 	     may have static or type qualifiers.  */
 	  error_at (loc, "static or type qualifiers in non-parameter array declarator");
-	  array_ptr_quals = TYPE_UNQUALIFIED;
+	  array_ptr_quals = qualifier_set {};
 	  array_ptr_attrs = NULL_TREE;
 	  array_parm_static = false;
 	}
@@ -7581,10 +7582,10 @@ grokdeclarator (const struct c_declarator *declarator,
 	       modify the shared type, so we gcc_assert (itype)
 	       below.  */
 	      {
-		addr_space_t as = DECODE_QUAL_ADDR_SPACE (type_quals);
+		addr_space_t as = type_quals.addr_space ();
 		if (!ADDR_SPACE_GENERIC_P (as) && as != TYPE_ADDR_SPACE (type))
 		  type = c_build_qualified_type (type,
-						 ENCODE_QUAL_ADDR_SPACE (as));
+						 {TYPE_UNQUALIFIED, as});
 		if (array_parm_vla_unspec_p)
 		  type = c_build_array_type_unspecified (type);
 		else
@@ -7626,13 +7627,13 @@ grokdeclarator (const struct c_declarator *declarator,
 	      }
 
 	    if (decl_context != PARM
-		&& (array_ptr_quals != TYPE_UNQUALIFIED
+		&& (array_ptr_quals != qualifier_set {}
 		    || array_ptr_attrs != NULL_TREE
 		    || array_parm_static))
 	      {
 		error_at (loc, "static or type qualifiers in non-parameter "
 			  "array declarator");
-		array_ptr_quals = TYPE_UNQUALIFIED;
+		array_ptr_quals = qualifier_set {};
 		array_ptr_attrs = NULL_TREE;
 		array_parm_static = false;
 	      }
@@ -7691,7 +7692,7 @@ grokdeclarator (const struct c_declarator *declarator,
 
 	    /* Type qualifiers before the return type of the function
 	       qualify the return type, not the function type.  */
-	    if (type_quals)
+	    if (type_quals != qualifier_set {})
 	      {
 		const enum c_declspec_word ignored_quals_list[] =
 		  {
@@ -7716,12 +7717,14 @@ grokdeclarator (const struct c_declarator *declarator,
 		   actually removed from the return type when
 		   determining the function type.  For C23, _Atomic is
 		   removed as well.  */
-		int quals_used = type_quals;
+		auto quals_used = type_quals;
 		if (flag_isoc23)
-		  quals_used = 0;
+		  quals_used = qualifier_set {};
 		else if (flag_isoc11)
-		  quals_used &= TYPE_QUAL_ATOMIC;
-		if (quals_used && VOID_TYPE_P (type) && really_funcdef)
+		  quals_used = quals_used.without (~TYPE_QUAL_ATOMIC);
+		if (quals_used != qualifier_set {}
+		    && VOID_TYPE_P (type)
+		    && really_funcdef)
 		  pedwarn (specs_loc, 0,
 			   "function definition has qualified void "
 			   "return type");
@@ -7734,13 +7737,13 @@ grokdeclarator (const struct c_declarator *declarator,
 		   DR#423 resolution is not entirely clear about
 		   this.  */
 		if (flag_isoc11
-		    && (type_quals & TYPE_QUAL_RESTRICT)
+		    && (type_quals.has (TYPE_QUAL_RESTRICT))
 		    && (!POINTER_TYPE_P (type)
 			|| !C_TYPE_OBJECT_OR_INCOMPLETE_P (TREE_TYPE (type))))
 		  error_at (loc, "invalid use of %<restrict%>");
 		type = c_build_qualified_type (type, quals_used);
 	      }
-	    type_quals = TYPE_UNQUALIFIED;
+	    type_quals = qualifier_set {};
 
 	    type = c_build_function_type (type, arg_types,
 					  arg_info->no_named_args_stdarg_p);
@@ -7762,18 +7765,18 @@ grokdeclarator (const struct c_declarator *declarator,
 	  {
 	    /* Merge any constancy or volatility into the target type
 	       for the pointer.  */
-	    if ((type_quals & TYPE_QUAL_ATOMIC)
+	    if (type_quals.has (TYPE_QUAL_ATOMIC)
 		&& TREE_CODE (type) == FUNCTION_TYPE)
 	      {
 		error_at (loc,
 			  "%<_Atomic%>-qualified function type");
-		type_quals &= ~TYPE_QUAL_ATOMIC;
+		type_quals = type_quals.without (TYPE_QUAL_ATOMIC);
 	      }
 	    else if (pedantic && TREE_CODE (type) == FUNCTION_TYPE
-		     && type_quals)
+		     && type_quals != qualifier_set {})
 	      pedwarn (loc, OPT_Wpedantic,
 		       "ISO C forbids qualified function types");
-	    if (type_quals)
+	    if (type_quals != qualifier_set {})
 	      type = c_build_qualified_type (type, type_quals, orig_qual_type,
 					     orig_qual_indirect);
 	    orig_qual_type = NULL_TREE;
@@ -7817,7 +7820,7 @@ grokdeclarator (const struct c_declarator *declarator,
 
   /* Warn about address space used for things other than static memory or
      pointers.  */
-  address_space = DECODE_QUAL_ADDR_SPACE (type_quals);
+  address_space = type_quals.addr_space ();
   if (!ADDR_SPACE_GENERIC_P (address_space))
     {
       if (decl_context == NORMAL)
@@ -7875,13 +7878,13 @@ grokdeclarator (const struct c_declarator *declarator,
       /* C11 makes it implementation-defined (6.7.2.1#5) whether
 	 atomic types are permitted for bit-fields; we have no code to
 	 make bit-field accesses atomic, so disallow them.  */
-      if (type_quals & TYPE_QUAL_ATOMIC)
+      if (type_quals.has (TYPE_QUAL_ATOMIC))
 	{
 	  if (name)
 	    error_at (loc, "bit-field %qE has atomic type", name);
 	  else
 	    error_at (loc, "bit-field has atomic type");
-	  type_quals &= ~TYPE_QUAL_ATOMIC;
+	  type_quals = type_quals.without (TYPE_QUAL_ATOMIC);
 	}
     }
 
@@ -7930,18 +7933,18 @@ grokdeclarator (const struct c_declarator *declarator,
   if (storage_class == csc_typedef)
     {
       tree decl;
-      if ((type_quals & TYPE_QUAL_ATOMIC)
+      if ((type_quals.has (TYPE_QUAL_ATOMIC))
 	  && TREE_CODE (type) == FUNCTION_TYPE)
 	{
 	  error_at (loc,
 		    "%<_Atomic%>-qualified function type");
-	  type_quals &= ~TYPE_QUAL_ATOMIC;
+	  type_quals = type_quals.without (TYPE_QUAL_ATOMIC);
 	}
       else if (pedantic && TREE_CODE (type) == FUNCTION_TYPE
-	       && type_quals)
+	       && type_quals != qualifier_set {})
 	pedwarn (loc, OPT_Wpedantic,
 		 "ISO C forbids qualified function types");
-      if (type_quals)
+      if (type_quals != qualifier_set {})
 	type = c_build_qualified_type (type, type_quals, orig_qual_type,
 				       orig_qual_indirect);
       decl = build_decl (declarator->id_loc,
@@ -7984,18 +7987,18 @@ grokdeclarator (const struct c_declarator *declarator,
 	 and fields.  */
       gcc_assert (storage_class == csc_none && !threadp
 		  && !declspecs->inline_p && !declspecs->noreturn_p);
-      if ((type_quals & TYPE_QUAL_ATOMIC)
+      if (type_quals.has (TYPE_QUAL_ATOMIC)
 	  && TREE_CODE (type) == FUNCTION_TYPE)
 	{
 	  error_at (loc,
 		    "%<_Atomic%>-qualified function type");
-	  type_quals &= ~TYPE_QUAL_ATOMIC;
+	  type_quals = type_quals.without (TYPE_QUAL_ATOMIC);
 	}
       else if (pedantic && TREE_CODE (type) == FUNCTION_TYPE
-	       && type_quals)
+	       && type_quals != qualifier_set {})
 	pedwarn (loc, OPT_Wpedantic,
 		 "ISO C forbids const or volatile function types");
-      if (type_quals)
+      if (type_quals != qualifier_set {})
 	type = c_build_qualified_type (type, type_quals, orig_qual_type,
 				       orig_qual_indirect);
       return type;
@@ -8055,7 +8058,7 @@ grokdeclarator (const struct c_declarator *declarator,
 		else
 		  orig_qual_indirect--;
 	      }
-	    if (type_quals)
+	    if (type_quals != qualifier_set {})
 	      type = c_build_qualified_type (type, type_quals, orig_qual_type,
 					     orig_qual_indirect);
 
@@ -8070,7 +8073,7 @@ grokdeclarator (const struct c_declarator *declarator,
 
 	    type = c_build_pointer_type (type);
 	    type_quals = array_ptr_quals;
-	    if (type_quals)
+	    if (type_quals != qualifier_set {})
 	      type = c_build_qualified_type (type, type_quals);
 
 	    /* We don't yet implement attributes in this context.  */
@@ -8083,21 +8086,21 @@ grokdeclarator (const struct c_declarator *declarator,
 	  }
 	else if (TREE_CODE (type) == FUNCTION_TYPE)
 	  {
-	    if (type_quals & TYPE_QUAL_ATOMIC)
+	    if (type_quals.has (TYPE_QUAL_ATOMIC))
 	      {
 		error_at (loc,
 			  "%<_Atomic%>-qualified function type");
-		type_quals &= ~TYPE_QUAL_ATOMIC;
+		type_quals = type_quals.without (TYPE_QUAL_ATOMIC);
 	      }
-	    else if (type_quals)
+	    else if (type_quals != qualifier_set {})
 	      pedwarn (loc, OPT_Wpedantic,
 		       "ISO C forbids qualified function types");
-	    if (type_quals)
+	    if (type_quals != qualifier_set {})
 	      type = c_build_qualified_type (type, type_quals);
 	    type = c_build_pointer_type (type);
-	    type_quals = TYPE_UNQUALIFIED;
+	    type_quals = qualifier_set {};
 	  }
-	else if (type_quals)
+	else if (type_quals != qualifier_set {})
 	  type = c_build_qualified_type (type, type_quals);
 
 	decl = build_decl (declarator->id_loc,
@@ -8204,13 +8207,15 @@ grokdeclarator (const struct c_declarator *declarator,
 			   FUNCTION_DECL, declarator->u.id.id, type);
 	decl = build_decl_attribute_variant (decl, decl_attr);
 
-	if (type_quals & TYPE_QUAL_ATOMIC)
+	if (type_quals.has (TYPE_QUAL_ATOMIC))
 	  {
 	    error_at (loc,
 		      "%<_Atomic%>-qualified function type");
-	    type_quals &= ~TYPE_QUAL_ATOMIC;
+	    type_quals = type_quals.without (TYPE_QUAL_ATOMIC);
 	  }
-	else if (pedantic && type_quals && !DECL_IN_SYSTEM_HEADER (decl))
+	else if (pedantic
+		 && type_quals != qualifier_set {}
+		 && !DECL_IN_SYSTEM_HEADER (decl))
 	  pedwarn (loc, OPT_Wpedantic,
 		   "ISO C forbids qualified function types");
 
@@ -8307,8 +8312,8 @@ grokdeclarator (const struct c_declarator *declarator,
 	    if (c_type_variably_modified_p (type))
 	      error_at (loc, "%<constexpr%> object has variably modified "
 			"type");
-	    if (type_quals
-		& (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
+	    if (type_quals.has
+		(TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
 	      error_at (loc, "invalid qualifiers for %<constexpr%> object");
 	    else
 	      {
@@ -8645,7 +8650,7 @@ get_parm_info (bool ellipsis, tree expr)
       && !DECL_NAME (b->decl)               /* anonymous */
       && VOID_TYPE_P (TREE_TYPE (b->decl))) /* of void type */
     {
-      if (TYPE_QUALS (TREE_TYPE (b->decl)) != TYPE_UNQUALIFIED
+      if (TYPE_QUALS (TREE_TYPE (b->decl)) != qualifier_set {}
 	  || C_DECL_REGISTER (b->decl))
 	error_at (b->locus, "%<void%> as only parameter may not be qualified");
 
@@ -9453,12 +9458,13 @@ is_flexible_array_member_p (bool is_last_field,
 static void
 c_update_type_canonical (tree t)
 {
-  gcc_checking_assert (TYPE_MAIN_VARIANT (t) == t && !TYPE_QUALS (t));
+  gcc_checking_assert (TYPE_MAIN_VARIANT (t) == t
+		       && qualifier_set {t} == qualifier_set {});
   for (tree x = t, l = NULL_TREE; x; l = x, x = TYPE_NEXT_VARIANT (x))
     {
       if (x != t && TYPE_STRUCTURAL_EQUALITY_P (x))
 	{
-	  if (!TYPE_QUALS (x))
+	  if (TYPE_QUALS (x) == qualifier_set {})
 	    TYPE_CANONICAL (x) = TYPE_CANONICAL (t);
 	  else
 	    {
@@ -9716,7 +9722,7 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
       /* Any field that is volatile, restrict-qualified or atomic
 	 means the type cannot be used for a constexpr object.  */
       if (TYPE_QUALS (t1)
-	  & (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
+	  .has (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
 	C_TYPE_FIELDS_NON_CONSTEXPR (t) = 1;
       else if (RECORD_OR_UNION_TYPE_P (t1) && C_TYPE_FIELDS_NON_CONSTEXPR (t1))
 	    C_TYPE_FIELDS_NON_CONSTEXPR (t) = 1;
@@ -12051,7 +12057,7 @@ make_pointer_declarator (struct c_declspecs *type_quals_attrs,
 			 struct c_declarator *target)
 {
   tree attrs;
-  int quals = 0;
+  qualifier_set quals {};
   struct c_declarator *itarget = target;
   struct c_declarator *ret = XOBNEW (&parser_obstack, struct c_declarator);
   if (type_quals_attrs)
diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc
index 15f981bfc525..ea21f1cc7c72 100644
--- a/gcc/c/c-objc-common.cc
+++ b/gcc/c/c-objc-common.cc
@@ -385,7 +385,9 @@ c_tree_printer (pretty_printer *pp, text_info *text, const char *spec,
       return true;
 
     case 'v':
-      pp_c_cv_qualifiers (cpp, va_arg (*text->m_args_ptr, int), hash);
+      pp_c_cv_qualifiers (cpp,
+			  cv_qualifier (va_arg (*text->m_args_ptr, int)),
+			  hash);
       return true;
 
     default:
diff --git a/gcc/c/c-parser.cc b/gcc/c/c-parser.cc
index 4eba29997273..0aec48c36629 100644
--- a/gcc/c/c-parser.cc
+++ b/gcc/c/c-parser.cc
@@ -3984,7 +3984,7 @@ c_parser_declspecs (c_parser *parser, struct c_declspecs *specs,
 		    error_at (loc, "%<_Atomic%>-qualified array type");
 		  else if (TREE_CODE (t.spec) == FUNCTION_TYPE)
 		    error_at (loc, "%<_Atomic%>-qualified function type");
-		  else if (TYPE_QUALS (t.spec) != TYPE_UNQUALIFIED)
+		  else if (TYPE_QUALS (t.spec) != qualifier_set {})
 		    error_at (loc, "%<_Atomic%> applied to a qualified type");
 		  else
 		    t.spec = c_build_qualified_type (t.spec, TYPE_QUAL_ATOMIC);
@@ -4833,12 +4833,12 @@ c_parser_typeof_specifier (c_parser *parser)
       if (is_unqual)
 	{
 	  bool is_array = TREE_CODE (ret.spec) == ARRAY_TYPE;
-	  int quals = TYPE_QUALS (strip_array_types (ret.spec));
-	  if ((is_array ? quals & ~TYPE_QUAL_ATOMIC : quals)
+	  auto quals = TYPE_QUALS (strip_array_types (ret.spec));
+	  if ((is_array ? quals.without (TYPE_QUAL_ATOMIC) : quals)
 	      != TYPE_UNQUALIFIED)
 	    {
 	      ret.spec = TYPE_MAIN_VARIANT (ret.spec);
-	      if (quals & TYPE_QUAL_ATOMIC && is_array)
+	      if (quals.has (TYPE_QUAL_ATOMIC) && is_array)
 		ret.spec = c_build_qualified_type (ret.spec,
 						   TYPE_QUAL_ATOMIC);
 	    }
@@ -4849,10 +4849,10 @@ c_parser_typeof_specifier (c_parser *parser)
 	     expressions such as &abort, but in GCC it is represented
 	     internally as a type qualifier.  */
 	  if (TREE_CODE (ret.spec) == FUNCTION_TYPE
-	      && TYPE_QUALS (ret.spec) != TYPE_UNQUALIFIED)
+	      && TYPE_QUALS (ret.spec) != qualifier_set {})
 	    ret.spec = TYPE_MAIN_VARIANT (ret.spec);
 	  else if (FUNCTION_POINTER_TYPE_P (ret.spec)
-		   && TYPE_QUALS (TREE_TYPE (ret.spec)) != TYPE_UNQUALIFIED)
+		   && TYPE_QUALS (TREE_TYPE (ret.spec)) != qualifier_set {})
 	    ret.spec
 	      = c_build_pointer_type (TYPE_MAIN_VARIANT (TREE_TYPE (ret.spec)));
 	}
@@ -11424,7 +11424,7 @@ c_parser_generic_selection (c_parser *parser)
 	 such as &abort, but in GCC it is represented internally as a type
 	 qualifier.  */
       if (FUNCTION_POINTER_TYPE_P (selector_type)
-	  && TYPE_QUALS (TREE_TYPE (selector_type)) != TYPE_UNQUALIFIED)
+	  && TYPE_QUALS (TREE_TYPE (selector_type)) != qualifier_set {})
 	selector_type
 	  = c_build_pointer_type (TYPE_MAIN_VARIANT (TREE_TYPE (selector_type)));
     }
@@ -13818,7 +13818,7 @@ c_parser_postfix_expression_after_paren_type (c_parser *parser,
 	 restrict qualified or have a member with such a qualifier.
 	 const qualification is implicitly added.  */
       if (TYPE_QUALS (type_no_array)
-	  & (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
+	  .has (TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC))
 	error_at (type_loc, "invalid qualifiers for %<constexpr%> object");
       else if (RECORD_OR_UNION_TYPE_P (type_no_array)
 	       && C_TYPE_FIELDS_NON_CONSTEXPR (type_no_array))
diff --git a/gcc/c/c-tree.h b/gcc/c/c-tree.h
index 511b155d46dc..87a6c64b1680 100644
--- a/gcc/c/c-tree.h
+++ b/gcc/c/c-tree.h
@@ -567,7 +567,7 @@ struct c_declarator {
       /* The array dimension, or NULL for [] and [*].  */
       tree dimen;
       /* The qualifiers inside [].  */
-      int quals;
+      qualifier_set quals;
       /* The attributes (currently ignored) inside [].  */
       tree attrs;
       /* Whether [static] was used.  */
@@ -576,7 +576,7 @@ struct c_declarator {
       bool vla_unspec_p : 1;
     } array;
     /* For pointers, the qualifiers on the pointer type.  */
-    int pointer_quals;
+    qualifier_set pointer_quals;
     /* For attributes.  */
     tree attrs;
   } u;
@@ -685,7 +685,7 @@ extern void record_inline_static (location_t, tree, tree,
 				  enum c_inline_static_type);
 extern void c_init_decl_processing (void);
 extern void c_print_identifier (FILE *, tree, int);
-extern int quals_from_declspecs (const struct c_declspecs *);
+extern qualifier_set quals_from_declspecs (const struct c_declspecs *);
 extern struct c_declarator *build_array_declarator (location_t, tree,
     						    struct c_declspecs *,
 						    bool, bool);
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 643035b50a08..9c3d0cfbeb45 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -165,7 +165,7 @@ null_pointer_constant_p (const_tree expr)
 	  && (INTEGRAL_TYPE_P (type)
 	      || (TREE_CODE (type) == POINTER_TYPE
 		  && VOID_TYPE_P (TREE_TYPE (type))
-		  && TYPE_QUALS (TREE_TYPE (type)) == TYPE_UNQUALIFIED)));
+		  && TYPE_QUALS (TREE_TYPE (type)) == qualifier_set {})));
 }
 
 /* EXPR may appear in an unevaluated part of an integer constant
@@ -362,9 +362,9 @@ qualify_type (tree type, tree like)
     }
 
   return c_build_qualified_type (type,
-				 TYPE_QUALS_NO_ADDR_SPACE (type)
-				 | TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC (like)
-				 | ENCODE_QUAL_ADDR_SPACE (as_common));
+				 {TYPE_QUALS_NO_ADDR_SPACE (type)
+				  | TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC (like),
+				  as_common});
 }
 
 
@@ -489,11 +489,11 @@ c_build_function_type (tree type, tree args, bool no)
 tree
 c_build_array_type (tree type, tree domain)
 {
-  int type_quals = TYPE_QUALS (type);
+  auto type_quals = TYPE_QUALS (type);
 
   /* Identify typeless storage as introduced in C2Y
      and supported also in earlier language modes.  */
-  bool typeless = (char_type_p (type) && !(type_quals & TYPE_QUAL_ATOMIC))
+  bool typeless = (char_type_p (type) && !(type_quals.has (TYPE_QUAL_ATOMIC)))
 		  || (AGGREGATE_TYPE_P (type) && TYPE_TYPELESS_STORAGE (type));
 
   tree ret = build_array_type (type, domain, typeless);
@@ -518,8 +518,8 @@ c_build_array_type_unspecified (tree type)
 }
 
 
-tree
-c_build_type_attribute_qual_variant (tree type, tree attrs, int quals)
+static tree
+c_build_type_attribute_qual_variant (tree type, tree attrs, qualifier_set quals)
 {
   tree ret = build_type_attribute_qual_variant (type, attrs, quals);
   return c_set_type_bits (ret, type);
@@ -807,7 +807,7 @@ composite_type_internal (tree t1, tree t2, tree cond,
     case POINTER_TYPE:
       /* For two pointers, do this recursively on the target type.  */
       {
-	gcc_checking_assert (TYPE_QUALS (t1) == TYPE_QUALS (t2));
+	gcc_checking_assert (qualifier_set {t1} == qualifier_set {t2});
 	tree target = composite_type_internal (TREE_TYPE (t1), TREE_TYPE (t2),
 					       cond, cache);
 	tree n = c_build_pointer_type_for_mode (target, TYPE_MODE (t1), false);
@@ -886,7 +886,7 @@ composite_type_internal (tree t1, tree t2, tree cond,
 	   up TYPE_MAIN_VARIANT correctly, we need to form the
 	   composite of the unqualified types and add the qualifiers
 	   back at the end.  */
-	int quals = TYPE_QUALS (strip_array_types (elt));
+	auto quals = TYPE_QUALS (strip_array_types (elt));
 	tree unqual_elt = c_build_qualified_type (elt, TYPE_UNQUALIFIED);
 
 	t1 = c_build_array_type (unqual_elt, td);
@@ -1108,9 +1108,9 @@ static tree
 common_pointer_type (tree t1, tree t2, tree cond)
 {
   tree attributes;
-  unsigned target_quals;
+  qualifier_set target_quals;
   addr_space_t as1, as2, as_common;
-  int quals1, quals2;
+  cv_qualifier quals1, quals2;
 
   /* Save time if the two types are the same.  */
 
@@ -1143,9 +1143,9 @@ common_pointer_type (tree t1, tree t2, tree cond)
      if used inconsistently.  The middle-end uses these to mark const
      and noreturn functions.  */
   if (TREE_CODE (pointed_to_1) == FUNCTION_TYPE)
-    target_quals = (quals1 & quals2);
+    target_quals = quals1 & quals2;
   else
-    target_quals = (quals1 | quals2);
+    target_quals = quals1 | quals2;
 
   /* If the two named address spaces are different, determine the common
      superset address space.  This is guaranteed to exist due to the
@@ -1155,7 +1155,7 @@ common_pointer_type (tree t1, tree t2, tree cond)
   if (!addr_space_superset (as1, as2, &as_common))
     gcc_unreachable ();
 
-  target_quals |= ENCODE_QUAL_ADDR_SPACE (as_common);
+  target_quals.set_as (as_common);
 
   t1 = c_build_pointer_type (c_build_qualified_type (target, target_quals));
   return c_build_type_attribute_variant (t1, attributes);
@@ -1181,10 +1181,10 @@ c_common_type (tree t1, tree t2)
   if (t2 == error_mark_node)
     return t1;
 
-  if (TYPE_QUALS (t1) != TYPE_UNQUALIFIED)
+  if (TYPE_QUALS (t1) != qualifier_set {})
     t1 = TYPE_MAIN_VARIANT (t1);
 
-  if (TYPE_QUALS (t2) != TYPE_UNQUALIFIED)
+  if (TYPE_QUALS (t2) != qualifier_set {})
     t2 = TYPE_MAIN_VARIANT (t2);
 
   if (TYPE_ATTRIBUTES (t1) != NULL_TREE)
@@ -2108,10 +2108,10 @@ function_types_compatible_p (const_tree f1, const_tree f2,
     pedwarn (input_location, 0, "function return types not compatible due to %<volatile%>");
   if (TYPE_VOLATILE (ret1))
     ret1 = build_qualified_type (TYPE_MAIN_VARIANT (ret1),
-				 TYPE_QUALS (ret1) & ~TYPE_QUAL_VOLATILE);
+				 TYPE_QUALS (ret1).without (TYPE_QUAL_VOLATILE));
   if (TYPE_VOLATILE (ret2))
     ret2 = build_qualified_type (TYPE_MAIN_VARIANT (ret2),
-				 TYPE_QUALS (ret2) & ~TYPE_QUAL_VOLATILE);
+				 TYPE_QUALS (ret2).without (TYPE_QUAL_VOLATILE));
 
   bool ignore_pargs = data->ignore_promoting_args;
   data->ignore_promoting_args = false;
@@ -2638,7 +2638,7 @@ convert_lvalue_to_rvalue (location_t loc, struct c_expr exp,
     exp = default_function_array_conversion (loc, exp);
   if (!VOID_TYPE_P (TREE_TYPE (exp.value))
       || (flag_isoc2y
-	  && TYPE_QUALS (TREE_TYPE (exp.value)) != TYPE_UNQUALIFIED))
+	  && TYPE_QUALS (TREE_TYPE (exp.value)) != qualifier_set {}))
     exp.value = require_complete_type (loc, exp.value);
   if (for_init || !RECORD_OR_UNION_TYPE_P (TREE_TYPE (exp.value)))
     {
@@ -3377,7 +3377,7 @@ build_component_ref (location_t loc, tree datum, tree component,
       do
 	{
 	  tree subdatum = TREE_VALUE (field);
-	  int quals;
+	  qualifier_set quals;
 	  tree subtype;
 	  bool use_datum_quals;
 
@@ -3393,7 +3393,17 @@ build_component_ref (location_t loc, tree datum, tree component,
 
 	  quals = TYPE_QUALS (strip_array_types (TREE_TYPE (subdatum)));
 	  if (use_datum_quals)
-	    quals |= TYPE_QUALS (TREE_TYPE (datum));
+	    {
+	      /* SUBDATUM refers to a field, which lack their own address
+		 space.  */
+	      gcc_assert (ADDR_SPACE_GENERIC_P (quals.addr_space ()));
+	      addr_space_t datum_as;
+	      cv_qualifier datum_cv;
+	      std::tie (datum_cv, datum_as)
+		= TYPE_QUALS (TREE_TYPE (datum)).split();
+	      quals |= datum_cv;
+	      quals.set_as(datum_as);
+	    }
 	  subtype = c_build_qualified_type (TREE_TYPE (subdatum), quals);
 
 	  ref = build3 (COMPONENT_REF, subtype, datum, subdatum,
@@ -4388,7 +4398,7 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
       warning_at (loc, 0, "function called through a non-compatible type");
 
       if (VOID_TYPE_P (return_type)
-	  && TYPE_QUALS (return_type) != TYPE_UNQUALIFIED)
+	  && TYPE_QUALS (return_type) != qualifier_set {})
 	pedwarn (loc, 0,
 		 "function with qualified void return type called");
      }
@@ -4407,7 +4417,7 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
 					    nargs, argarray, &arg_loc,
 					    comptypes);
 
-  if (TYPE_QUALS (return_type) != TYPE_UNQUALIFIED
+  if (TYPE_QUALS (return_type) != qualifier_set {}
       && !VOID_TYPE_P (return_type))
     return_type = c_build_qualified_type (return_type, TYPE_UNQUALIFIED);
   if (name != NULL_TREE
@@ -4444,7 +4454,7 @@ build_function_call_vec (location_t loc, vec<location_t> arg_loc,
 
   if (VOID_TYPE_P (TREE_TYPE (result)))
     {
-      if (TYPE_QUALS (TREE_TYPE (result)) != TYPE_UNQUALIFIED)
+      if (TYPE_QUALS (TREE_TYPE (result)) != qualifier_set {})
 	pedwarn (loc, 0,
 		 "function with qualified void return type called");
       return result;
@@ -6206,7 +6216,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
 	else
 	  val = build2 (code, TREE_TYPE (arg), arg, inc);
 	TREE_SIDE_EFFECTS (val) = 1;
-	if (TYPE_QUALS (TREE_TYPE (val)) != TYPE_UNQUALIFIED)
+	if (TYPE_QUALS (TREE_TYPE (val)) != qualifier_set {})
 	  TREE_TYPE (val) = c_build_qualified_type (TREE_TYPE (val),
 						    TYPE_UNQUALIFIED);
 	ret = val;
@@ -6220,7 +6230,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
 	 expressions of type void), or, in C99, the result of a [] or
 	 unary '*' operator.  */
       if (VOID_TYPE_P (TREE_TYPE (arg))
-	  && TYPE_QUALS (TREE_TYPE (arg)) == TYPE_UNQUALIFIED
+	  && TYPE_QUALS (TREE_TYPE (arg)) == qualifier_set {}
 	  && (!INDIRECT_REF_P (arg) || !flag_isoc99))
 	pedwarn (location, 0, "taking address of expression of type %<void%>");
 
@@ -6266,8 +6276,7 @@ build_unary_op (location_t location, enum tree_code code, tree xarg,
 	  && (TREE_READONLY (arg) || TREE_THIS_VOLATILE (arg))
 	  && TREE_CODE (argtype) == FUNCTION_TYPE)
 	{
-	  int orig_quals = TYPE_QUALS (strip_array_types (argtype));
-	  int quals = orig_quals;
+	  auto quals = TYPE_QUALS (strip_array_types (argtype));
 
 	  if (TREE_READONLY (arg))
 	    quals |= TYPE_QUAL_CONST;
@@ -6890,7 +6899,7 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
 	   }
 	  tree t2_stripped = strip_array_types (t2);
 	  if ((TREE_CODE (t2) == ARRAY_TYPE)
-	      && (TYPE_QUALS (t2_stripped) & ~TYPE_QUALS (t1)))
+	      && !TYPE_QUALS (t1).can_qualify (TYPE_QUALS (t2_stripped)))
 	    {
 	      if (!flag_isoc23)
 		warning_at (colon_loc, OPT_Wdiscarded_array_qualifiers,
@@ -6915,7 +6924,7 @@ build_conditional_expr (location_t colon_loc, tree ifexp, bool ifexp_bcp,
 	result_type = objc_common_type (type1, type2);
       else
 	{
-	  int qual = ENCODE_QUAL_ADDR_SPACE (as_common);
+	  qualifier_set qual {TYPE_UNQUALIFIED, as_common};
 	  enum diagnostics::kind kind = diagnostics::kind::permerror;
 	  if (!flag_isoc99)
 	    /* This downgrade to a warning ensures that -std=gnu89
@@ -7310,7 +7319,7 @@ handle_warn_cast_qual (location_t loc, tree type, tree otype)
     {
       in_type = TREE_TYPE (in_type);
       in_otype = TREE_TYPE (in_otype);
-      if ((TYPE_QUALS (in_type) &~ TYPE_QUALS (in_otype)) != 0
+      if (!TYPE_QUALS (in_otype).can_qualify (TYPE_QUALS (in_type))
 	  && !is_const)
 	{
 	  warning_at (loc, OPT_Wcast_qual,
@@ -8700,14 +8709,14 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 		  || (VOID_TYPE_P (ttr) && !TYPE_ATOMIC (ttr))
 		  || comp_target_types (location, memb_type, rhstype))
 		{
-		  int lquals = TYPE_QUALS (ttl) & ~TYPE_QUAL_ATOMIC;
-		  int rquals = TYPE_QUALS (ttr) & ~TYPE_QUAL_ATOMIC;
+		  auto lquals = TYPE_QUALS (ttl).without (TYPE_QUAL_ATOMIC);
+		  auto rquals = TYPE_QUALS (ttr).without (TYPE_QUAL_ATOMIC);
 		  /* If this type won't generate any warnings, use it.  */
 		  if (lquals == rquals
 		      || ((TREE_CODE (ttr) == FUNCTION_TYPE
 			   && TREE_CODE (ttl) == FUNCTION_TYPE)
-			  ? ((lquals | rquals) == rquals)
-			  : ((lquals | rquals) == lquals)))
+			  ? (rquals.can_qualify (lquals))
+			  : (lquals.can_qualify (rquals))))
 		    break;
 
 		  /* Keep looking for a better type, but remember this one.  */
@@ -8758,7 +8767,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 					       "unqualified"),
 					    G_("return makes %q#v qualified function "
 					       "pointer from unqualified"),
-					    TYPE_QUALS (ttl) & ~TYPE_QUALS (ttr));
+					    TYPE_QUALS_NO_ADDR_SPACE (ttl)
+					    & ~TYPE_QUALS_NO_ADDR_SPACE (ttr));
 		}
 	      else if (TYPE_QUALS_NO_ADDR_SPACE (ttr)
 		       & ~TYPE_QUALS_NO_ADDR_SPACE (ttl))
@@ -8772,7 +8782,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 					   "from pointer target type"),
 				        G_("return discards %qv qualifier from "
 					   "pointer target type"),
-				        TYPE_QUALS (ttr) & ~TYPE_QUALS (ttl));
+				        TYPE_QUALS_NO_ADDR_SPACE (ttr)
+					& ~TYPE_QUALS_NO_ADDR_SPACE (ttl));
 
 	      memb = marginal_memb;
 	    }
@@ -9035,7 +9046,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 					   "from pointer target type"),
 					G_("return discards %qv qualifier from "
 					   "pointer target type"),
-					TYPE_QUALS (ttr) & ~TYPE_QUALS (ttl));
+					TYPE_QUALS_NO_ADDR_SPACE (ttr)
+					& ~TYPE_QUALS_NO_ADDR_SPACE (ttl));
             }
           else if (pedantic
 	      && ((VOID_TYPE_P (ttl) && TREE_CODE (ttr) == FUNCTION_TYPE)
@@ -9078,7 +9090,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 					   "from pointer target type"),
 					G_("return discards %qv qualifier from "
 					   "pointer target type"),
-					TYPE_QUALS (ttr) & ~TYPE_QUALS (ttl));
+					TYPE_QUALS_NO_ADDR_SPACE (ttr)
+					& ~TYPE_QUALS_NO_ADDR_SPACE (ttl));
 	      else if (warn_quals_ped)
 		pedwarn_c11 (location, OPT_Wc11_c23_compat,
 			     "array with qualifier on the element is not qualified before C23");
@@ -9146,7 +9159,8 @@ convert_for_assignment (location_t location, location_t expr_loc, tree type,
 					   "function pointer from unqualified"),
 				        G_("return makes %q#v qualified function "
 					   "pointer from unqualified"),
-				        TYPE_QUALS (ttl) & ~TYPE_QUALS (ttr));
+				        TYPE_QUALS_NO_ADDR_SPACE (ttl)
+					& ~TYPE_QUALS_NO_ADDR_SPACE (ttr));
 	    }
 	}
       /* Avoid warning about the volatile ObjC EH puts on decls.  */
@@ -14941,7 +14955,7 @@ build_binary_op (location_t location, enum tree_code code,
 
 	  if (result_type == NULL_TREE)
 	    {
-	      int qual = ENCODE_QUAL_ADDR_SPACE (as_common);
+	      qualifier_set qual {TYPE_UNQUALIFIED, as_common};
 	      result_type = c_build_pointer_type
 			      (c_build_qualified_type (void_type_node, qual));
 	    }
@@ -15075,7 +15089,7 @@ build_binary_op (location_t location, enum tree_code code,
 	    }
 	  else
 	    {
-	      int qual = ENCODE_QUAL_ADDR_SPACE (as_common);
+	      qualifier_set qual {TYPE_UNQUALIFIED, as_common};
 	      result_type = c_build_pointer_type
 			      (c_build_qualified_type (void_type_node, qual));
               pedwarn (location, OPT_Wcompare_distinct_pointer_types,
@@ -18656,8 +18670,8 @@ c_finish_transaction (location_t loc, tree block, int flags)
    type was derived).  */
 
 tree
-c_build_qualified_type (tree type, int type_quals, tree orig_qual_type,
-			size_t orig_qual_indirect)
+c_build_qualified_type (tree type, qualifier_set type_quals,
+			tree orig_qual_type, size_t orig_qual_indirect)
 {
   if (type == error_mark_node)
     return type;
@@ -18718,12 +18732,12 @@ c_build_qualified_type (tree type, int type_quals, tree orig_qual_type,
   /* A restrict-qualified pointer type must be a pointer to object or
      incomplete type.  Note that the use of POINTER_TYPE_P also allows
      REFERENCE_TYPEs, which is appropriate for C++.  */
-  if ((type_quals & TYPE_QUAL_RESTRICT)
+  if ((type_quals.has (TYPE_QUAL_RESTRICT))
       && (!POINTER_TYPE_P (type)
 	  || !C_TYPE_OBJECT_OR_INCOMPLETE_P (TREE_TYPE (type))))
     {
       error ("invalid use of %<restrict%>");
-      type_quals &= ~TYPE_QUAL_RESTRICT;
+      type_quals = type_quals.without (TYPE_QUAL_RESTRICT);
     }
 
   tree var_type = (orig_qual_type && orig_qual_indirect == 0
diff --git a/gcc/config/gcn/gcn-tree.cc b/gcc/config/gcn/gcn-tree.cc
index cfad5cbda46f..4512634c340f 100644
--- a/gcc/config/gcn/gcn-tree.cc
+++ b/gcc/config/gcn/gcn-tree.cc
@@ -315,8 +315,7 @@ gcn_goacc_get_worker_red_decl (tree type, unsigned offset)
 
   tree var_type
     = build_qualified_type (type,
-			    (TYPE_QUALS (type)
-			     | ENCODE_QUAL_ADDR_SPACE (ADDR_SPACE_LDS)));
+			    (TYPE_QUALS (type).with_as (ADDR_SPACE_LDS)));
 
   gcc_assert (offset
 	      < (machfun->reduction_limit - machfun->reduction_base));
@@ -536,8 +535,8 @@ gcn_goacc_adjust_private_decl (location_t, tree var, int level)
 
   tree type = TREE_TYPE (var);
   tree lds_type = build_qualified_type (type,
-		    TYPE_QUALS_NO_ADDR_SPACE (type)
-		    | ENCODE_QUAL_ADDR_SPACE (ADDR_SPACE_LDS));
+					TYPE_QUALS (type)
+					.with_as (ADDR_SPACE_LDS));
   machine_function *machfun = cfun->machine;
 
   TREE_TYPE (var) = lds_type;
@@ -565,8 +564,8 @@ gcn_goacc_create_worker_broadcast_record (tree record_type, bool sender,
 					  unsigned HOST_WIDE_INT offset)
 {
   tree type = build_qualified_type (record_type,
-				    TYPE_QUALS_NO_ADDR_SPACE (record_type)
-				    | ENCODE_QUAL_ADDR_SPACE (ADDR_SPACE_LDS));
+				    TYPE_QUALS (record_type)
+				    .with_as (ADDR_SPACE_LDS));
 
   if (!sender)
     {
diff --git a/gcc/config/i386/i386-builtins.cc b/gcc/config/i386/i386-builtins.cc
index 6837efd8da5e..c25f6d67d97c 100644
--- a/gcc/config/i386/i386-builtins.cc
+++ b/gcc/config/i386/i386-builtins.cc
@@ -157,7 +157,7 @@ ix86_get_builtin_type (enum ix86_builtin_type tcode)
     }
   else
     {
-      int quals;
+      cv_qualifier quals;
 
       index = tcode - IX86_BT_LAST_VECT - 1;
       if (tcode <= IX86_BT_LAST_PTR)
diff --git a/gcc/config/i386/i386.cc b/gcc/config/i386/i386.cc
index e66958db7acb..7f79b4f4f7fe 100644
--- a/gcc/config/i386/i386.cc
+++ b/gcc/config/i386/i386.cc
@@ -25503,7 +25503,7 @@ ix86_stack_protect_guard (void)
   if (TARGET_SSP_TLS_GUARD)
     {
       tree type_node = lang_hooks.types.type_for_mode (ptr_mode, 1);
-      int qual = ENCODE_QUAL_ADDR_SPACE (ix86_stack_protector_guard_reg);
+      qualifier_set qual {TYPE_UNQUALIFIED, ix86_stack_protector_guard_reg};
       tree type = build_qualified_type (type_node, qual);
       tree t;
 
diff --git a/gcc/config/rl78/rl78.cc b/gcc/config/rl78/rl78.cc
index 193a5fa080b2..3fc111f36595 100644
--- a/gcc/config/rl78/rl78.cc
+++ b/gcc/config/rl78/rl78.cc
@@ -4732,7 +4732,7 @@ rl78_insert_attributes (tree decl, tree *attributes ATTRIBUTE_UNUSED)
     {
       tree type = TREE_TYPE (decl);
       tree attr = TYPE_ATTRIBUTES (type);
-      int q = TYPE_QUALS_NO_ADDR_SPACE (type) | ENCODE_QUAL_ADDR_SPACE (ADDR_SPACE_FAR);
+      auto q = TYPE_QUALS (type).with_as (ADDR_SPACE_FAR);
 
       TREE_TYPE (decl) = build_type_attribute_qual_variant (type, attr, q);
     }
diff --git a/gcc/config/rs6000/rs6000-c.cc b/gcc/config/rs6000/rs6000-c.cc
index 3cbdb6fb2ba1..2fc984ddc040 100644
--- a/gcc/config/rs6000/rs6000-c.cc
+++ b/gcc/config/rs6000/rs6000-c.cc
@@ -1927,14 +1927,14 @@ altivec_resolve_overloaded_builtin (location_t loc, tree fndecl,
 	 matching further down.  */
       if (POINTER_TYPE_P (decl_type)
 	  && POINTER_TYPE_P (type)
-	  && TYPE_QUALS (TREE_TYPE (type)) != 0)
+	  && TYPE_QUALS (TREE_TYPE (type)).nonempty_p ())
 	{
 	  if (TYPE_READONLY (TREE_TYPE (type))
 	      && !TYPE_READONLY (TREE_TYPE (decl_type)))
 	    warning (0, "passing argument %d of %qE discards %qs "
 		     "qualifier from pointer target type", n + 1, fndecl,
 		     "const");
-	  type = build_qualified_type (TREE_TYPE (type), 0);
+	  type = build_qualified_type (TREE_TYPE (type), TYPE_UNQUALIFIED);
 	  type = build_pointer_type (type);
 	  arg = c_fold_convert (type, arg);
 	}
@@ -1945,7 +1945,7 @@ altivec_resolve_overloaded_builtin (location_t loc, tree fndecl,
 	  && POINTER_TYPE_P (type)
 	  && TYPE_READONLY (TREE_TYPE (type)))
 	{
-	  type = build_qualified_type (TREE_TYPE (type), 0);
+	  type = build_qualified_type (TREE_TYPE (type), TYPE_UNQUALIFIED);
 	  type = build_pointer_type (type);
 	  arg = c_fold_convert (type, arg);
 	}
diff --git a/gcc/config/rs6000/rs6000.cc b/gcc/config/rs6000/rs6000.cc
index d8669d9ffce4..b38840a28d38 100644
--- a/gcc/config/rs6000/rs6000.cc
+++ b/gcc/config/rs6000/rs6000.cc
@@ -20592,7 +20592,7 @@ rs6000_handle_altivec_attribute (tree *node,
 
   /* Propagate qualifiers attached to the element type
      onto the vector type.  */
-  if (result && result != type && TYPE_QUALS (type))
+  if (result && result != type && TYPE_QUALS (type).nonempty_p ())
     result = build_qualified_type (result, TYPE_QUALS (type));
 
   *no_add_attrs = true;  /* No need to hang on to the attribute.  */
diff --git a/gcc/config/s390/s390-c.cc b/gcc/config/s390/s390-c.cc
index db9a88ee8054..579dacde3b0a 100644
--- a/gcc/config/s390/s390-c.cc
+++ b/gcc/config/s390/s390-c.cc
@@ -847,10 +847,10 @@ s390_fn_types_compatible (enum s390_builtin_ov_type_index typeindex,
       /* If the incoming pointer argument has more qualifiers than the
 	 argument type it can still be an imperfect match.  */
       if (POINTER_TYPE_P (b_arg_type) && POINTER_TYPE_P (in_type)
-	  && !(TYPE_QUALS (TREE_TYPE (in_type))
-	       & ~TYPE_QUALS (TREE_TYPE (b_arg_type)))
 	  && (TYPE_QUALS (TREE_TYPE (b_arg_type))
-	      & ~TYPE_QUALS (TREE_TYPE (in_type))))
+	      .can_qualify (TYPE_QUALS (TREE_TYPE (in_type))))
+	  && (TYPE_QUALS (TREE_TYPE (b_arg_type))
+	      != TYPE_QUALS (TREE_TYPE (in_type))))
 	{
 	  tree qual_in_type =
 	    build_qualified_type (TREE_TYPE (in_type),
diff --git a/gcc/coretypes.h b/gcc/coretypes.h
index 5cc602ed7e5d..d0d61d5f0451 100644
--- a/gcc/coretypes.h
+++ b/gcc/coretypes.h
@@ -191,7 +191,7 @@ class bitmap_view;
 typedef unsigned char addr_space_t;
 
 /* The value of addr_space_t that represents the generic address space.  */
-#define ADDR_SPACE_GENERIC 0
+#define ADDR_SPACE_GENERIC ((addr_space_t) 0)
 #define ADDR_SPACE_GENERIC_P(AS) ((AS) == ADDR_SPACE_GENERIC)
 
 /* The major intermediate representations of GCC.  */
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index 143e85760b3b..17de392743f2 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -1277,7 +1277,7 @@ strip_top_quals (tree t)
 {
   if (TREE_CODE (t) == ARRAY_TYPE)
     return t;
-  return cp_build_qualified_type (t, 0);
+  return cp_build_qualified_type (t, TYPE_UNQUALIFIED);
 }
 
 /* Returns the standard conversion path (see [conv]) from type FROM to type
@@ -1428,10 +1428,10 @@ standard_conversion (tree to, tree from, tree expr, bool c_cast_p,
 	     qualifiers, so any TYPE_QUALS must be for attributes const or
 	     noreturn.  Strip them.  */
 	  if (TREE_CODE (to_pointee) == FUNCTION_TYPE
-	      && TYPE_QUALS (to_pointee))
+	      && TYPE_QUALS (to_pointee).nonempty_p ())
 	    to_pointee = build_qualified_type (to_pointee, TYPE_UNQUALIFIED);
 	  if (TREE_CODE (from_pointee) == FUNCTION_TYPE
-	      && TYPE_QUALS (from_pointee))
+	      && TYPE_QUALS (from_pointee).nonempty_p ())
 	    from_pointee = build_qualified_type (from_pointee, TYPE_UNQUALIFIED);
 	}
       else
@@ -1450,7 +1450,7 @@ standard_conversion (tree to, tree from, tree expr, bool c_cast_p,
 	{
 	  tree nfrom = TREE_TYPE (from);
 	  /* Don't try to apply restrict to void.  */
-	  int quals = cp_type_quals (nfrom) & ~TYPE_QUAL_RESTRICT;
+	  auto quals = cp_type_quals (nfrom) & ~TYPE_QUAL_RESTRICT;
 	  from_pointee = cp_build_qualified_type (void_type_node, quals);
 	  from = build_pointer_type (from_pointee);
 	  conv = build_conv (ck_ptr, from, conv);
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index 2fcaa6cd81bd..93fa2f6864d3 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -593,7 +593,7 @@ build_simple_base_path (tree expr, tree binfo)
 	/* We don't use build_class_member_access_expr here, as that
 	   has unnecessary checks, and more importantly results in
 	   recursive calls to dfs_walk_once.  */
-	int type_quals = cp_type_quals (TREE_TYPE (expr));
+	auto type_quals = cp_type_quals (TREE_TYPE (expr));
 
 	expr = build3 (COMPONENT_REF,
 		       cp_build_qualified_type (type, type_quals),
diff --git a/gcc/cp/cp-objcp-common.h b/gcc/cp/cp-objcp-common.h
index ed29e65e4f3e..e66b8f7329d9 100644
--- a/gcc/cp/cp-objcp-common.h
+++ b/gcc/cp/cp-objcp-common.h
@@ -142,7 +142,10 @@ static const scoped_attribute_specs *const cp_objcp_attribute_table[] =
 #undef LANG_HOOKS_TREE_DUMP_DUMP_TREE_FN
 #define LANG_HOOKS_TREE_DUMP_DUMP_TREE_FN cp_dump_tree
 #undef LANG_HOOKS_TREE_DUMP_TYPE_QUALS_FN
-#define LANG_HOOKS_TREE_DUMP_TYPE_QUALS_FN cp_type_quals
+inline qualifier_set
+cp_type_quals_as_set (const_tree type)
+{ return { cp_type_quals (type) }; }
+#define LANG_HOOKS_TREE_DUMP_TYPE_QUALS_FN cp_type_quals_as_set
 
 #undef LANG_HOOKS_MAKE_TYPE
 #define LANG_HOOKS_MAKE_TYPE cxx_make_type_hook
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index a1b95b6f569b..e9505fe3aabf 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6890,7 +6890,7 @@ inline tree ovl_op_identifier (tree_code code) { return ovl_op_identifier (false
 /* A type-qualifier, or bitmask therefore, using the TYPE_QUAL
    constants.  */
 
-typedef int cp_cv_quals;
+typedef cv_qualifier cp_cv_quals;
 
 /* Non-static member functions have an optional virt-specifier-seq.
    There is a VIRT_SPEC value for each virt-specifier.
@@ -8006,7 +8006,7 @@ extern tree get_copy_ctor			(tree, tsubst_flags_t);
 extern tree get_copy_assign			(tree);
 extern tree get_default_ctor			(tree);
 extern tree get_dtor				(tree, tsubst_flags_t);
-extern tree build_stub_type			(tree, int, bool);
+extern tree build_stub_type			(tree, cv_qualifier, bool);
 extern tree build_stub_object			(tree);
 extern bool is_stub_object			(tree);
 extern tree build_invoke			(tree, const_tree,
@@ -8866,7 +8866,7 @@ extern tree make_ptrmem_cst			(tree, tree);
 extern tree cp_build_type_attribute_variant     (tree, tree);
 extern tree cp_build_reference_type		(tree, bool);
 extern tree move				(tree);
-extern tree cp_build_qualified_type		(tree, int,
+extern tree cp_build_qualified_type		(tree, cv_qualifier,
 						 tsubst_flags_t = tf_warning_or_error);
 extern tree cp_build_function_type		(tree, tree);
 extern bool cv_qualified_p			(const_tree);
@@ -9029,14 +9029,14 @@ extern bool error_type_p			(const_tree);
 extern bool ptr_reasonably_similar		(const_tree, const_tree);
 extern tree build_ptrmemfunc			(tree, tree, int, bool,
 						 tsubst_flags_t);
-extern int cp_type_quals			(const_tree);
-extern int type_memfn_quals			(const_tree);
+extern cv_qualifier cp_type_quals		(const_tree);
+extern cv_qualifier type_memfn_quals		(const_tree);
 extern cp_ref_qualifier type_memfn_rqual	(const_tree);
 extern tree apply_memfn_quals			(tree, cp_cv_quals,
 						 cp_ref_qualifier = REF_QUAL_NONE);
 extern bool cp_has_mutable_p			(const_tree);
 extern bool at_least_as_qualified_p		(const_tree, const_tree);
-extern void cp_apply_type_quals_to_decl		(int, tree);
+extern void cp_apply_type_quals_to_decl		(cv_qualifier, tree);
 extern tree build_ptrmemfunc1			(tree, tree, tree);
 extern void expand_ptrmemfunc_cst		(tree, tree *, tree *);
 extern tree type_after_usual_arithmetic_conversions (tree, tree);
diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc
index c2a75c669c7b..aed28b0c92c6 100644
--- a/gcc/cp/decl.cc
+++ b/gcc/cp/decl.cc
@@ -6266,10 +6266,10 @@ warn_misplaced_attr_for_class_type (location_t location,
 /* Returns the cv-qualifiers that apply to the type specified
    by the DECLSPECS.  */
 
-static int
+static cv_qualifier
 get_type_quals (const cp_decl_specifier_seq *declspecs)
 {
-  int type_quals = TYPE_UNQUALIFIED;
+  auto type_quals = TYPE_UNQUALIFIED;
 
   if (decl_spec_seq_has_spec_p (declspecs, ds_const))
     type_quals |= TYPE_QUAL_CONST;
@@ -10859,7 +10859,7 @@ cp_finish_decomp (tree decl, cp_decomp *decomp, bool test_p)
       eltscnt = 2;
       if (pack != -1 ? count - 1 > eltscnt : count != eltscnt)
 	goto cnt_mismatch;
-      eltype = cp_build_qualified_type (TREE_TYPE (type), TYPE_QUALS (type));
+      eltype = cp_build_qualified_type (TREE_TYPE (type), cp_type_quals (type));
       for (unsigned int i = 0; i < count; i++)
 	{
 	  if ((unsigned) pack == i)
@@ -10905,7 +10905,7 @@ cp_finish_decomp (tree decl, cp_decomp *decomp, bool test_p)
 	}
       if (pack != -1 ? count - 1 > eltscnt : count != eltscnt)
 	goto cnt_mismatch;
-      eltype = cp_build_qualified_type (TREE_TYPE (type), TYPE_QUALS (type));
+      eltype = cp_build_qualified_type (TREE_TYPE (type), cp_type_quals (type));
       for (unsigned int i = 0; i < count; i++)
 	{
 	  if ((unsigned) pack == i)
@@ -14019,7 +14019,7 @@ grokdeclarator (const cp_declarator *declarator,
      a member function.  */
   cp_ref_qualifier rqual = REF_QUAL_NONE;
   /* cv-qualifiers that apply to the type specified by the DECLSPECS.  */
-  int type_quals = get_type_quals (declspecs);
+  auto type_quals = get_type_quals (declspecs);
   tree raises = NULL_TREE;
   int template_count = 0;
   tree returned_attrs = NULL_TREE;
@@ -17442,7 +17442,7 @@ grokparms (tree parmlist, tree *parms)
 
 	  /* Top-level qualifiers on the parameters are
 	     ignored for function types.  */
-	  type = cp_build_qualified_type (type, 0);
+	  type = cp_build_qualified_type (type, TYPE_UNQUALIFIED);
 	  if (TREE_CODE (type) == METHOD_TYPE)
 	    {
 	      error ("parameter %qD invalidly declared method type", decl);
diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc
index c2fb6027c521..a322ba54855e 100644
--- a/gcc/cp/error.cc
+++ b/gcc/cp/error.cc
@@ -1913,7 +1913,7 @@ dump_lambda_function (cxx_pretty_printer *pp,
       pp_c_ws_string (pp, "static");
     }
   else if (!(TYPE_QUALS (class_of_this_parm (TREE_TYPE (fn)))
-	     & TYPE_QUAL_CONST))
+	     .has (TYPE_QUAL_CONST)))
     {
       pp->set_padding (pp_before);
       pp_c_ws_string (pp, "mutable");
diff --git a/gcc/cp/mangle.cc b/gcc/cp/mangle.cc
index d368359dccd6..e88e61f85184 100644
--- a/gcc/cp/mangle.cc
+++ b/gcc/cp/mangle.cc
@@ -2892,7 +2892,12 @@ write_CV_qualifiers_for_type (const tree type)
   /* Note that we do not use cp_type_quals below; given "const
      int[3]", the "const" is emitted with the "int", not with the
      array.  */
-  cp_cv_quals quals = TYPE_QUALS (type);
+  cv_qualifier quals;
+  addr_space_t as;
+  std::tie (quals, as) = TYPE_QUALS (type).split ();
+
+  /* No address space support yet.  */
+  gcc_checking_assert (ADDR_SPACE_GENERIC_P (as));
 
   if (quals & TYPE_QUAL_RESTRICT)
     {
diff --git a/gcc/cp/method.cc b/gcc/cp/method.cc
index 4c432efb56ae..215edb86a20b 100644
--- a/gcc/cp/method.cc
+++ b/gcc/cp/method.cc
@@ -736,7 +736,7 @@ do_build_copy_constructor (tree fndecl)
 
       if (!inh)
 	{
-	  int cvquals = cp_type_quals (TREE_TYPE (parm));
+	  auto cvquals = cp_type_quals (TREE_TYPE (parm));
 
 	  for (tree fields = TYPE_FIELDS (current_class_type);
 	       fields; fields = DECL_CHAIN (fields))
@@ -767,7 +767,7 @@ do_build_copy_constructor (tree fndecl)
 		 types.)  */
 	      if (!TYPE_REF_P (expr_type))
 		{
-		  int quals = cvquals;
+		  auto quals = cvquals;
 
 		  if (DECL_MUTABLE_P (field))
 		    quals &= ~TYPE_QUAL_CONST;
@@ -820,7 +820,7 @@ do_build_copy_assign (tree fndecl)
   else
     {
       tree fields;
-      int cvquals = cp_type_quals (TREE_TYPE (parm));
+      auto cvquals = cp_type_quals (TREE_TYPE (parm));
       int i;
       tree binfo, base_binfo;
 
@@ -856,7 +856,7 @@ do_build_copy_assign (tree fndecl)
 	  tree init = parm;
 	  tree field = fields;
 	  tree expr_type;
-	  int quals;
+	  cv_qualifier quals;
 
 	  if (TREE_CODE (field) != FIELD_DECL || DECL_ARTIFICIAL (field))
 	    continue;
@@ -1900,7 +1900,7 @@ maybe_synthesize_method (tree fndecl)
    rvalue if RVALUE is true.  */
 
 tree
-build_stub_type (tree type, int quals, bool rvalue)
+build_stub_type (tree type, cv_qualifier quals, bool rvalue)
 {
   tree argtype
     = cp_build_qualified_type (type, quals,
@@ -2196,8 +2196,8 @@ get_default_ctor (tree type)
 tree
 get_copy_ctor (tree type, tsubst_flags_t complain)
 {
-  int quals = (TYPE_HAS_CONST_COPY_CTOR (type)
-	       ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED);
+  auto quals = (TYPE_HAS_CONST_COPY_CTOR (type)
+		? TYPE_QUAL_CONST : TYPE_UNQUALIFIED);
   tree argtype = build_stub_type (type, quals, false);
   tree fn = locate_fn_flags (type, complete_ctor_identifier, argtype,
 			     LOOKUP_NORMAL, complain);
@@ -2211,8 +2211,8 @@ get_copy_ctor (tree type, tsubst_flags_t complain)
 tree
 get_copy_assign (tree type)
 {
-  int quals = (TYPE_HAS_CONST_COPY_ASSIGN (type)
-	       ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED);
+  auto quals = (TYPE_HAS_CONST_COPY_ASSIGN (type)
+		? TYPE_QUAL_CONST : TYPE_UNQUALIFIED);
   tree argtype = build_stub_type (type, quals, false);
   tree fn = locate_fn_flags (type, assign_op_identifier, argtype,
 			     LOOKUP_NORMAL, tf_warning_or_error);
@@ -2679,7 +2679,7 @@ process_subob_fn (tree fn, special_function_kind sfk, tree *spec_p,
 
 static void
 walk_field_subobs (tree fields, special_function_kind sfk, tree fnname,
-		   int quals, tree *spec_p, bool *trivial_p,
+		   cv_qualifier quals, tree *spec_p, bool *trivial_p,
 		   bool *deleted_p, bool *constexpr_p,
 		   bool diag, int flags, tsubst_flags_t complain,
 		   bool dtor_from_ctor)
@@ -2845,7 +2845,7 @@ walk_field_subobs (tree fields, special_function_kind sfk, tree fnname,
 
       if (SFK_COPY_P (sfk) || SFK_MOVE_P (sfk))
 	{
-	  int mem_quals = cp_type_quals (mem_type) | quals;
+	  auto mem_quals = cp_type_quals (mem_type) | quals;
 	  if (DECL_MUTABLE_P (field))
 	    mem_quals &= ~TYPE_QUAL_CONST;
 	  argtype = build_stub_type (mem_type, mem_quals, SFK_MOVE_P (sfk));
@@ -2926,9 +2926,9 @@ walk_field_subobs (tree fields, special_function_kind sfk, tree fnname,
 
 static tree
 synthesized_method_base_walk (tree binfo, tree base_binfo,
-			      special_function_kind sfk, tree fnname, int quals,
-			      tree *inheriting_ctor, tree inherited_parms,
-			      int flags, bool diag,
+			      special_function_kind sfk, tree fnname,
+			      cv_qualifier quals, tree *inheriting_ctor,
+			      tree inherited_parms, int flags, bool diag,
 			      tree *spec_p, bool *trivial_p,
 			      bool *deleted_p, bool *constexpr_p)
 {
@@ -3118,7 +3118,7 @@ synthesized_method_walk (tree ctype, special_function_kind sfk, bool const_p,
     /* We're in get_defaulted_eh_spec; we don't actually want any walking
        diagnostics, we just want complain set.  */
     diag = false;
-  int quals = const_p ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED;
+  auto quals = const_p ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED;
 
   for (binfo = TYPE_BINFO (ctype), i = 0;
        BINFO_BASE_ITERATE (binfo, i, base_binfo); ++i)
@@ -3519,7 +3519,7 @@ implicitly_declare_fn (special_function_kind kind, tree type,
   else
     return_type = void_type_node;
 
-  int this_quals = TYPE_UNQUALIFIED;
+  auto this_quals = TYPE_UNQUALIFIED;
   switch (kind)
     {
     case sfk_destructor:
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index f250dc3e9dfe..9bde899e5594 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -10793,7 +10793,8 @@ trees_in::tree_node (bool is_use)
 
 	int quals = i ();
 	if (quals >= 0 && !get_overrun ())
-	  res = cp_build_qualified_type (res, quals);
+	  res = cp_build_qualified_type (res,
+					 static_cast<cv_qualifier> (quals));
 
 	int tag = i ();
 	if (!tag)
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index f7aa10226801..3e7f22c0fd75 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -17236,7 +17236,7 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
       else
 	{
 	  /* We don't have an instantiation yet, so drop the typedef.  */
-	  int quals = cp_type_quals (t);
+	  auto quals = cp_type_quals (t);
 	  t = DECL_ORIGINAL_TYPE (decl);
 	  t = cp_build_qualified_type (t, quals,
 				       complain | tf_ignore_bad_quals);
@@ -17418,11 +17418,9 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 
 	    if (code == TEMPLATE_TYPE_PARM)
 	      {
-		int quals;
-
 		gcc_assert (TYPE_P (arg));
 
-		quals = cp_type_quals (arg) | cp_type_quals (t);
+		auto quals = cp_type_quals (arg) | cp_type_quals (t);
 
 		return cp_build_qualified_type
 		  (arg, quals, complain | tf_ignore_bad_quals);
@@ -17515,7 +17513,7 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl)
 	/* If we get here, we must have been looking at a parm for a
 	   more deeply nested template.  Make a new version of this
 	   template parameter, but with a lower level.  */
-	int quals;
+	cv_qualifier quals;
 	switch (code)
 	  {
 	  case TEMPLATE_TYPE_PARM:
@@ -31076,7 +31074,6 @@ resolve_typename_type (tree type, bool only_current_p)
   tree scope;
   tree name;
   tree decl;
-  int quals;
   tree pushed_scope;
   tree result;
 
@@ -31198,7 +31195,7 @@ resolve_typename_type (tree type, bool only_current_p)
     }
 
   /* Qualify the resulting type.  */
-  quals = cp_type_quals (type);
+  auto quals = cp_type_quals (type);
   if (quals)
     result = cp_build_qualified_type (result, cp_type_quals (result) | quals);
 
diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
index 96f5de8481d5..07bdcd19b55f 100644
--- a/gcc/cp/reflect.cc
+++ b/gcc/cp/reflect.cc
@@ -2611,7 +2611,7 @@ type_of (tree r, reflect_kind kind)
       r = TREE_TYPE (TREE_VALUE (TREE_VALUE (r)));
       if (CLASS_TYPE_P (r))
 	{
-	  int quals = cp_type_quals (r);
+	  auto quals = cp_type_quals (r);
 	  quals |= TYPE_QUAL_CONST;
 	  r = cp_build_qualified_type (r, quals);
 	}
@@ -5234,7 +5234,7 @@ static tree
 eval_remove_volatile (location_t loc, tree type)
 {
   type = strip_typedefs (type);
-  int quals = cp_type_quals (type);
+  auto quals = cp_type_quals (type);
   quals &= ~TYPE_QUAL_VOLATILE;
   type = cp_build_qualified_type (type, quals);
   return get_reflection_raw (loc, type);
@@ -5264,7 +5264,7 @@ eval_add_const (location_t loc, tree type)
   type = strip_typedefs (type);
   if (!TYPE_REF_P (type) && !FUNC_OR_METHOD_TYPE_P (type))
     {
-      int quals = cp_type_quals (type);
+      auto quals = cp_type_quals (type);
       quals |= TYPE_QUAL_CONST;
       type = cp_build_qualified_type (type, quals);
     }
@@ -5282,7 +5282,7 @@ eval_add_volatile (location_t loc, tree type)
   type = strip_typedefs (type);
   if (!TYPE_REF_P (type) && !FUNC_OR_METHOD_TYPE_P (type))
     {
-      int quals = cp_type_quals (type);
+      auto quals = cp_type_quals (type);
       quals |= TYPE_QUAL_VOLATILE;
       type = cp_build_qualified_type (type, quals);
     }
@@ -5300,7 +5300,7 @@ eval_add_cv (location_t loc, tree type)
   type = strip_typedefs (type);
   if (!TYPE_REF_P (type) && !FUNC_OR_METHOD_TYPE_P (type))
     {
-      int quals = cp_type_quals (type);
+      auto quals = cp_type_quals (type);
       quals |= (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE);
       type = cp_build_qualified_type (type, quals);
     }
@@ -5391,7 +5391,7 @@ eval_make_signed (location_t loc, const constexpr_ctx *ctx, tree type,
     ret = c_common_signed_or_unsigned_type (unsignedp, type);
   if (ret != type)
     {
-      int quals = cp_type_quals (type);
+      auto quals = cp_type_quals (type);
       quals &= (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE);
       ret = cp_build_qualified_type (ret, quals);
     }
diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 3a7e19c190a4..ad0bff79d705 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -2808,7 +2808,7 @@ finish_non_static_data_member (tree decl, tree object, tree qualifying_scope,
       else
 	{
 	  /* Set the cv qualifiers.  */
-	  int quals = cp_type_quals (TREE_TYPE (object));
+	  auto quals = cp_type_quals (TREE_TYPE (object));
 
 	  if (DECL_MUTABLE_P (decl))
 	    quals &= ~TYPE_QUAL_CONST;
@@ -13472,7 +13472,7 @@ finish_decltype_type (tree expr, bool id_expression_or_member_access_p,
 
 	  if (type && !TYPE_REF_P (type))
 	    {
-	      int quals;
+	      cv_qualifier quals;
 	      if (current_function_decl
 		  && LAMBDA_FUNCTION_P (current_function_decl)
 		  && DECL_XOBJ_MEMBER_FUNCTION_P (current_function_decl))
diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc
index e543cf145195..befdec3ae0de 100644
--- a/gcc/cp/tree.cc
+++ b/gcc/cp/tree.cc
@@ -1469,10 +1469,16 @@ move (tree expr)
    the C version of this function does not properly maintain canonical
    types (which are not used in C).  */
 tree
-c_build_qualified_type (tree type, int type_quals, tree /* orig_qual_type */,
+c_build_qualified_type (tree type, qualifier_set type_quals,
+			tree /* orig_qual_type */,
 			size_t /* orig_qual_indirect */)
 {
-  return cp_build_qualified_type (type, type_quals);
+  cv_qualifier cv_quals;
+  addr_space_t as;
+  std::tie (cv_quals, as) = type_quals.split ();
+  /* No address space support yet.  */
+  gcc_assert (ADDR_SPACE_GENERIC_P (as));
+  return cp_build_qualified_type (type, cv_quals);
 }
 
 
@@ -1497,11 +1503,11 @@ c_build_qualified_type (tree type, int type_quals, tree /* orig_qual_type */,
    in a similar manner for restricting non-pointer types.  */
 
 tree
-cp_build_qualified_type (tree type, int type_quals,
+cp_build_qualified_type (tree type, cv_qualifier type_quals,
 			 tsubst_flags_t complain /* = tf_warning_or_error */)
 {
   tree result;
-  int bad_quals = TYPE_UNQUALIFIED;
+  auto bad_quals = TYPE_UNQUALIFIED;
 
   if (type == error_mark_node)
     return type;
@@ -1640,12 +1646,10 @@ cp_build_function_type (tree value_type, tree arg_types)
 tree
 cv_unqualified (tree type)
 {
-  int quals;
-
   if (type == error_mark_node)
     return type;
 
-  quals = cp_type_quals (type);
+  auto quals = cp_type_quals (type);
   quals &= ~(TYPE_QUAL_CONST|TYPE_QUAL_VOLATILE);
   return cp_build_qualified_type (type, quals);
 }
@@ -2448,8 +2452,9 @@ build_qualified_name (tree type, tree scope, tree name, bool template_p)
    parameters.  */
 
 static bool
-cp_check_qualified_type (const_tree cand, const_tree base, int type_quals,
-			 cp_ref_qualifier rqual, tree raises, bool late)
+cp_check_qualified_type (const_tree cand, const_tree base,
+			 cv_qualifier type_quals, cp_ref_qualifier rqual,
+			 tree raises, bool late)
 {
   return (TYPE_QUALS (cand) == type_quals
 	  && check_base_type (cand, base)
@@ -2937,7 +2942,14 @@ tree
 build_cp_fntype_variant (tree type, cp_ref_qualifier rqual,
 			 tree raises, bool late)
 {
-  cp_cv_quals type_quals = TYPE_QUALS (type);
+  cv_qualifier type_quals;
+  addr_space_t as;
+  std::tie (type_quals, as) = TYPE_QUALS (type).split ();
+  /* AS here is the address space of the method or function.  For the latter,
+     it won't ever make sense.  For the former, it may, one day, if we support
+     address space qualification on non-static member functions.  But not
+     today.  */
+  gcc_assert (ADDR_SPACE_GENERIC_P (as));
 
   if (cp_check_qualified_type (type, type, type_quals, rqual, raises, late))
     return type;
@@ -4659,7 +4671,7 @@ maybe_dummy_object (tree type, tree* binfop)
 	 non-lambda) 'this' if available.  */
       if (ctype)
 	{
-	  int quals = TYPE_UNQUALIFIED;
+	  auto quals = TYPE_UNQUALIFIED;
 	  if (tree lambda = CLASSTYPE_LAMBDA_EXPR (ctype))
 	    {
 	      if (tree cap = lambda_expr_this_capture (lambda, false))
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index 683457ccaf8c..61a8111b2ad7 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -241,7 +241,7 @@ commonparms (tree p1, tree p2)
 static tree
 original_type (tree t)
 {
-  int quals = cp_type_quals (t);
+  auto quals = cp_type_quals (t);
   while (t != error_mark_node
 	 && TYPE_NAME (t) != NULL_TREE)
     {
@@ -719,9 +719,9 @@ composite_pointer_type_r (const op_location_t &location,
 	return error_mark_node;
       result_type = void_type_node;
     }
-  const int q1 = cp_type_quals (pointee1);
-  const int q2 = cp_type_quals (pointee2);
-  const int quals = q1 | q2;
+  const auto q1 = cp_type_quals (pointee1);
+  const auto q2 = cp_type_quals (pointee2);
+  const auto quals = q1 | q2;
   result_type = cp_build_qualified_type (result_type,
 					 (quals | (*add_const
 						   ? TYPE_QUAL_CONST
@@ -987,7 +987,7 @@ merge_types (tree t1, tree t2)
       /* For two pointers, do this recursively on the target type.  */
       {
 	tree target = merge_types (TREE_TYPE (t1), TREE_TYPE (t2));
-	int quals = cp_type_quals (t1);
+	auto quals = cp_type_quals (t1);
 
 	if (code1 == POINTER_TYPE)
 	  {
@@ -1005,9 +1005,8 @@ merge_types (tree t1, tree t2)
 
     case OFFSET_TYPE:
       {
-	int quals;
 	tree pointee;
-	quals = cp_type_quals (t1);
+	auto quals = cp_type_quals (t1);
 	pointee = merge_types (TYPE_PTRMEM_POINTED_TO_TYPE (t1),
 			       TYPE_PTRMEM_POINTED_TO_TYPE (t2));
 	t1 = build_ptrmem_type (TYPE_PTRMEM_CLASS_TYPE (t1),
@@ -3047,7 +3046,7 @@ build_class_member_access_expr (cp_expr object, tree member,
     {
       /* A non-static data member.  */
       bool null_object_p;
-      int type_quals;
+      cv_qualifier type_quals;
       tree member_type;
 
       if (INDIRECT_REF_P (object))
@@ -12123,10 +12122,11 @@ comp_ptr_ttypes_const (tree to, tree from, compare_bounds_t cb)
 /* Returns the type qualifiers for this type, including the qualifiers on the
    elements for an array type.  */
 
-int
+cv_qualifier
 cp_type_quals (const_tree type)
 {
-  int quals;
+  cv_qualifier quals;
+  addr_space_t as;
   /* This CONST_CAST is okay because strip_array_types returns its
      argument unmodified and we assign it to a const_tree.  */
   type = strip_array_types (const_cast<tree> (type));
@@ -12134,7 +12134,10 @@ cp_type_quals (const_tree type)
       /* Quals on a FUNCTION_TYPE are memfn quals.  */
       || TREE_CODE (type) == FUNCTION_TYPE)
     return TYPE_UNQUALIFIED;
-  quals = TYPE_QUALS (type);
+
+  std::tie (quals, as) = TYPE_QUALS (type).split ();
+  /* No address space support yet.  */
+  gcc_assert (ADDR_SPACE_GENERIC_P (as));
   /* METHOD and REFERENCE_TYPEs should never have quals.  */
   gcc_assert ((TREE_CODE (type) != METHOD_TYPE
 	       && !TYPE_REF_P (type))
@@ -12161,11 +12164,18 @@ type_memfn_rqual (const_tree type)
 /* Returns the function-cv-quals for TYPE, which must be a FUNCTION_TYPE or
    METHOD_TYPE.  */
 
-int
+cv_qualifier
 type_memfn_quals (const_tree type)
 {
   if (TREE_CODE (type) == FUNCTION_TYPE)
-    return TYPE_QUALS (type);
+    {
+      cv_qualifier quals;
+      addr_space_t as;
+      std::tie (quals, as) = TYPE_QUALS (type).split ();
+      /* No address space support yet.  */
+      gcc_assert (ADDR_SPACE_GENERIC_P (as));
+      return quals;
+    }
   else if (TREE_CODE (type) == METHOD_TYPE)
     return cp_type_quals (class_of_this_parm (type));
   else
@@ -12224,7 +12234,7 @@ cp_has_mutable_p (const_tree type)
    initializer is non-constant.  */
 
 void
-cp_apply_type_quals_to_decl (int type_quals, tree decl)
+cp_apply_type_quals_to_decl (cv_qualifier type_quals, tree decl)
 {
   tree type = TREE_TYPE (decl);
 
@@ -12257,8 +12267,8 @@ cp_apply_type_quals_to_decl (int type_quals, tree decl)
 static void
 casts_away_constness_r (tree *t1, tree *t2, tsubst_flags_t complain)
 {
-  int quals1;
-  int quals2;
+  cv_qualifier quals1;
+  cv_qualifier quals2;
 
   /* [expr.const.cast]
 
diff --git a/gcc/d/d-codegen.cc b/gcc/d/d-codegen.cc
index 0f51fd70eb64..7422354bb499 100644
--- a/gcc/d/d-codegen.cc
+++ b/gcc/d/d-codegen.cc
@@ -2487,7 +2487,7 @@ build_vthis_function (tree basetype, tree type)
   tree fntype = build_function_type (TREE_TYPE (type), argtypes);
 
   /* Copy volatile qualifiers from the original function type.  */
-  if (TYPE_QUALS (type) & TYPE_QUAL_VOLATILE)
+  if (TYPE_QUALS (type).has (TYPE_QUAL_VOLATILE))
     fntype = build_qualified_type (fntype, TYPE_QUAL_VOLATILE);
 
   if (RECORD_OR_UNION_TYPE_P (basetype))
diff --git a/gcc/d/types.cc b/gcc/d/types.cc
index 4ad28993b89f..a55baebcee1b 100644
--- a/gcc/d/types.cc
+++ b/gcc/d/types.cc
@@ -226,7 +226,7 @@ make_struct_type (const char *name, int nfields, ...)
 tree
 insert_type_modifiers (tree type, unsigned mod)
 {
-  int quals = 0;
+  cv_qualifier quals = TYPE_UNQUALIFIED;
 
   switch (mod)
     {
diff --git a/gcc/dwarf2out.cc b/gcc/dwarf2out.cc
index 0b974d63c805..e7d93fa17c63 100644
--- a/gcc/dwarf2out.cc
+++ b/gcc/dwarf2out.cc
@@ -3866,8 +3866,8 @@ static void output_line_info (bool);
 static void output_file_names (void);
 static bool is_base_type (tree);
 static dw_die_ref subrange_type_die (tree, tree, tree, tree, dw_die_ref);
-static int decl_quals (const_tree);
-static dw_die_ref modified_type_die (tree, int, tree, bool, dw_die_ref);
+static cv_qualifier decl_quals (const_tree);
+static dw_die_ref modified_type_die (tree, cv_qualifier, tree, bool, dw_die_ref);
 static dw_die_ref generic_parameter_die (tree, tree, bool, dw_die_ref);
 static dw_die_ref template_parameter_pack_die (tree, tree, dw_die_ref);
 static unsigned int debugger_reg_number (const_rtx);
@@ -3935,7 +3935,7 @@ static dw_die_ref scope_die_for (tree, dw_die_ref);
 static inline bool local_scope_p (dw_die_ref);
 static inline bool class_scope_p (dw_die_ref);
 static inline bool class_or_namespace_scope_p (dw_die_ref);
-static void add_type_attribute (dw_die_ref, tree, int, bool, dw_die_ref);
+static void add_type_attribute (dw_die_ref, tree, cv_qualifier, bool, dw_die_ref);
 static void add_calling_convention_attribute (dw_die_ref, tree);
 static const char *type_tag (const_tree);
 static tree member_declared_type (const_tree);
@@ -13580,7 +13580,7 @@ subrange_type_die (tree type, tree low, tree high, tree bias,
    the decl node.  This will normally be augmented with the
    cv_qualifiers of the underlying type in add_type_attribute.  */
 
-static int
+static cv_qualifier
 decl_quals (const_tree decl)
 {
   return ((TREE_READONLY (decl)
@@ -13597,11 +13597,13 @@ decl_quals (const_tree decl)
    of the given TYPE_QUALS, and return its qualifiers.  Ignore all
    qualifiers outside QUAL_MASK.  */
 
-static int
-get_nearest_type_subqualifiers (tree type, int type_quals, int qual_mask)
+static cv_qualifier
+get_nearest_type_subqualifiers (tree type, cv_qualifier type_quals,
+				cv_qualifier qual_mask)
 {
   tree t;
-  int best_rank = 0, best_qual = 0, max_rank;
+  int best_rank = 0, max_rank;
+  cv_qualifier best_qual = TYPE_UNQUALIFIED;
 
   type_quals &= qual_mask;
   max_rank = popcount_hwi (type_quals) - 1;
@@ -13609,7 +13611,7 @@ get_nearest_type_subqualifiers (tree type, int type_quals, int qual_mask)
   for (t = TYPE_MAIN_VARIANT (type); t && best_rank < max_rank;
        t = TYPE_NEXT_VARIANT (t))
     {
-      int q = TYPE_QUALS (t) & qual_mask;
+      auto q = TYPE_QUALS (t).intersect (qual_mask);
 
       if ((q & type_quals) == q && q != type_quals
 	  && check_base_type (t, type))
@@ -13627,7 +13629,7 @@ get_nearest_type_subqualifiers (tree type, int type_quals, int qual_mask)
   return best_qual;
 }
 
-struct dwarf_qual_info_t { int q; enum dwarf_tag t; };
+struct dwarf_qual_info_t { cv_qualifier q; enum dwarf_tag t; };
 static const dwarf_qual_info_t dwarf_qual_info[] =
 {
   { TYPE_QUAL_CONST, DW_TAG_const_type },
@@ -13642,7 +13644,7 @@ static const unsigned int dwarf_qual_info_size = ARRAY_SIZE (dwarf_qual_info);
    qualifiers added compared to the returned DIE.  */
 
 static dw_die_ref
-qualified_die_p (dw_die_ref die, int *mask, unsigned int depth)
+qualified_die_p (dw_die_ref die, cv_qualifier *mask, unsigned int depth)
 {
   unsigned int i;
   for (i = 0; i < dwarf_qual_info_size; i++)
@@ -13898,7 +13900,7 @@ maybe_gen_btf_decl_tag_dies (tree t, dw_die_ref target)
    in the reverse storage order wrt the target order.  */
 
 static dw_die_ref
-modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
+modified_type_die (tree type, cv_qualifier cv_quals, tree type_attrs, bool reverse,
 		   dw_die_ref context_die)
 {
   enum tree_code code = TREE_CODE (type);
@@ -13911,8 +13913,8 @@ modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
   dw_die_ref mod_scope;
   struct array_descr_info info;
   /* Only these cv-qualifiers are currently handled.  */
-  const int cv_qual_mask = (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE
-			    | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC);
+  const auto cv_qual_mask = (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE
+			     | TYPE_QUAL_RESTRICT | TYPE_QUAL_ATOMIC);
   /* DW_AT_endianity is specified only for base types in the standard.  */
   const bool reverse_type
     = need_endianity_attribute_p (reverse)
@@ -14023,7 +14025,7 @@ modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
 	}
       else
 	{
-	  int dquals = TYPE_QUALS_NO_ADDR_SPACE (dtype);
+	  auto dquals = TYPE_QUALS_NO_ADDR_SPACE (dtype);
 	  dquals &= cv_qual_mask;
 	  if ((dquals & ~cv_quals) != TYPE_UNQUALIFIED
 	      || (cv_quals == dquals && DECL_ORIGINAL_TYPE (name) != type))
@@ -14065,7 +14067,7 @@ modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
 
   if (cv_quals)
     {
-      int sub_quals = 0, first_quals = 0;
+      cv_qualifier sub_quals = TYPE_UNQUALIFIED, first_quals = TYPE_UNQUALIFIED;
       unsigned i;
       dw_die_ref first = NULL, last = NULL;
 
@@ -14086,7 +14088,7 @@ modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
 	      needed = true;
 	    else if (needed && (dwarf_qual_info[i].q & cv_quals))
 	      {
-		sub_quals = 0;
+		sub_quals = TYPE_UNQUALIFIED;
 		break;
 	      }
 	}
@@ -14111,7 +14113,7 @@ modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
 	       count < (1U << dwarf_qual_info_size);
 	       count++, last = last->die_sib)
 	    {
-	      int quals = 0;
+	      cv_qualifier quals = TYPE_UNQUALIFIED;
 	      if (last == mod_scope->die_child)
 		break;
 	      if (qualified_die_p (last->die_sib, &quals, dwarf_qual_info_size)
@@ -14128,7 +14130,7 @@ modified_type_die (tree type, int cv_quals, tree type_attrs, bool reverse,
 	      {
 		for (d = first->die_sib; ; d = d->die_sib)
 		  {
-		    int quals = 0;
+		    cv_qualifier quals = TYPE_UNQUALIFIED;
 		    qualified_die_p (d, &quals, dwarf_qual_info_size);
 		    if (quals == (first_quals | dwarf_qual_info[i].q))
 		      break;
@@ -22746,7 +22748,7 @@ class_or_namespace_scope_p (dw_die_ref context_die)
    adds a DW_AT_type attribute below the given die.  */
 
 static void
-add_type_attribute (dw_die_ref object_die, tree type, int cv_quals,
+add_type_attribute (dw_die_ref object_die, tree type, cv_qualifier cv_quals,
 		    bool reverse, dw_die_ref context_die)
 {
   enum tree_code code  = TREE_CODE (type);
@@ -22771,7 +22773,7 @@ add_type_attribute (dw_die_ref object_die, tree type, int cv_quals,
     return;
 
   type_die = modified_type_die (type,
-				cv_quals | TYPE_QUALS (type),
+				cv_quals | TYPE_QUALS_NO_ADDR_SPACE (type),
 				TYPE_ATTRIBUTES (type),
 				reverse,
 				context_die);
@@ -24869,7 +24871,7 @@ override_type_for_decl_p (tree decl, dw_die_ref old_die,
 			  dw_die_ref context_die)
 {
   tree type = TREE_TYPE (decl);
-  int cv_quals;
+  cv_qualifier cv_quals;
 
   if (decl_by_reference_p (decl))
     {
@@ -24881,7 +24883,7 @@ override_type_for_decl_p (tree decl, dw_die_ref old_die,
 
   dw_die_ref type_die
     = modified_type_die (type,
-			 cv_quals | TYPE_QUALS (type),
+			 cv_quals | TYPE_QUALS_NO_ADDR_SPACE (type),
 			 TYPE_ATTRIBUTES (type),
 			 false,
 			 context_die);
diff --git a/gcc/fold-const.cc b/gcc/fold-const.cc
index 11d1129f1253..708654f0a980 100644
--- a/gcc/fold-const.cc
+++ b/gcc/fold-const.cc
@@ -9209,7 +9209,7 @@ fold_unary_loc (location_t loc, enum tree_code code, tree type, tree op0)
 	      && known_eq (bitpos, 0)
 	      && (TYPE_MAIN_VARIANT (TREE_TYPE (type))
 		  == TYPE_MAIN_VARIANT (TREE_TYPE (base)))
-	      && TYPE_QUALS (type) == TYPE_UNQUALIFIED)
+	      && TYPE_QUALS (type) == qualifier_set {})
 	    return fold_convert_loc (loc, type,
 				     build_fold_addr_expr_loc (loc, base));
         }
diff --git a/gcc/fortran/trans-openmp.cc b/gcc/fortran/trans-openmp.cc
index eb0012714970..bba1a4955afc 100644
--- a/gcc/fortran/trans-openmp.cc
+++ b/gcc/fortran/trans-openmp.cc
@@ -1731,7 +1731,7 @@ gfc_omp_finish_clause (tree c, gimple_seq *pre_p, bool openacc)
       bool always_modifier = false;
 
       if (!openacc
-	  && !(TYPE_QUALS (TREE_TYPE (ptr)) & TYPE_QUAL_RESTRICT))
+	  && !TYPE_QUALS (TREE_TYPE (ptr)).has (TYPE_QUAL_RESTRICT))
 	always_modifier = true;
 
       if (present)
diff --git a/gcc/fortran/trans-types.cc b/gcc/fortran/trans-types.cc
index bb33cddbfa07..fbf0a06e4e89 100644
--- a/gcc/fortran/trans-types.cc
+++ b/gcc/fortran/trans-types.cc
@@ -2385,7 +2385,8 @@ gfc_nonrestricted_type (tree t)
 	  else
 	    ret = build_reference_type (totype);
 	  ret = build_qualified_type (ret,
-				      TYPE_QUALS (t) & ~TYPE_QUAL_RESTRICT);
+				      TYPE_QUALS (t)
+				      .without (TYPE_QUAL_RESTRICT));
 	}
 	break;
 
diff --git a/gcc/gimple-lower-bitint.cc b/gcc/gimple-lower-bitint.cc
index 19e39f4d7efb..7eb0a0e5acc6 100644
--- a/gcc/gimple-lower-bitint.cc
+++ b/gcc/gimple-lower-bitint.cc
@@ -640,8 +640,8 @@ bitint_large_huge::limb_access (tree type, tree var, tree idx, bool write_p,
   if (DECL_P (var) && tree_fits_uhwi_p (idx))
     {
       if (as != TYPE_ADDR_SPACE (ltype))
-	ltype = build_qualified_type (ltype, TYPE_QUALS (ltype)
-				      | ENCODE_QUAL_ADDR_SPACE (as));
+	ltype = build_qualified_type (ltype,
+				      TYPE_QUALS (ltype).with_as (as));
       tree ptype = build_pointer_type (strip_array_types (TREE_TYPE (var)));
       unsigned HOST_WIDE_INT off = tree_to_uhwi (idx) * m_limb_size;
       if (bitint_big_endian)
@@ -655,8 +655,8 @@ bitint_large_huge::limb_access (tree type, tree var, tree idx, bool write_p,
   else if (TREE_CODE (var) == MEM_REF && tree_fits_uhwi_p (idx))
     {
       if (as != TYPE_ADDR_SPACE (ltype))
-	ltype = build_qualified_type (ltype, TYPE_QUALS (ltype)
-				      | ENCODE_QUAL_ADDR_SPACE (as));
+	ltype = build_qualified_type (ltype,
+				      TYPE_QUALS (ltype).with_as (as));
       unsigned HOST_WIDE_INT off = tree_to_uhwi (idx) * m_limb_size;
       if (bitint_big_endian)
 	off += m_limb_size - tree_to_uhwi (TYPE_SIZE_UNIT (ltype));
@@ -673,8 +673,8 @@ bitint_large_huge::limb_access (tree type, tree var, tree idx, bool write_p,
     {
       ltype = m_limb_type;
       if (as != TYPE_ADDR_SPACE (ltype))
-	ltype = build_qualified_type (ltype, TYPE_QUALS (ltype)
-				      | ENCODE_QUAL_ADDR_SPACE (as));
+	ltype = build_qualified_type (ltype,
+				      TYPE_QUALS (ltype).with_as (as));
       var = unshare_expr (var);
       if (TREE_CODE (TREE_TYPE (var)) != ARRAY_TYPE
 	  || !useless_type_conversion_p (m_limb_type,
@@ -713,8 +713,8 @@ bitint_large_huge::build_bit_field_ref (tree ftype, tree obj,
       tree ltype = m_limb_type;
       addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (obj));
       if (as != TYPE_ADDR_SPACE (ltype))
-	ltype = build_qualified_type (ltype, TYPE_QUALS (ltype)
-				      | ENCODE_QUAL_ADDR_SPACE (as));
+	ltype = build_qualified_type (ltype,
+				      TYPE_QUALS (ltype).with_as (as));
       tree atype = build_array_type_nelts (ltype, nelts);
       obj = build1 (VIEW_CONVERT_EXPR, atype, obj);
     }
@@ -6504,8 +6504,7 @@ bitint_large_huge::lower_stmt (gimple *stmt)
 		  if (as != TYPE_ADDR_SPACE (ltype))
 		    ltype
 		      = build_qualified_type (ltype,
-					      TYPE_QUALS (ltype)
-					      | ENCODE_QUAL_ADDR_SPACE (as));
+					      TYPE_QUALS (ltype).with_as (as));
 		  rhs1 = build1 (VIEW_CONVERT_EXPR, ltype, unshare_expr (mem));
 		  gimple_assign_set_rhs1 (stmt, rhs1);
 		}
@@ -6610,7 +6609,7 @@ bitint_large_huge::lower_stmt (gimple *stmt)
 		      ltype
 			= build_qualified_type (ltype,
 						TYPE_QUALS (TREE_TYPE (lhs))
-						| ENCODE_QUAL_ADDR_SPACE (as));
+						.with_as (as));
 		      lhs = build1 (VIEW_CONVERT_EXPR, ltype, lhs);
 		      gimple_assign_set_lhs (stmt, lhs);
 		      gimple_assign_set_rhs1 (stmt, rhs1);
diff --git a/gcc/gimplify.cc b/gcc/gimplify.cc
index 6c5d182ffab2..9c64b91cb154 100644
--- a/gcc/gimplify.cc
+++ b/gcc/gimplify.cc
@@ -3153,12 +3153,19 @@ canonicalize_component_ref (tree *expr_p)
 #ifdef ENABLE_TYPES_CHECKING
       tree old_type = TREE_TYPE (expr);
 #endif
-      int type_quals;
+      auto type_quals = TYPE_QUALS (type);
+      gcc_checking_assert (/* Fields should lack address space
+			      qualification.  */
+			   ADDR_SPACE_GENERIC_P (type_quals.addr_space ()));
 
       /* We need to preserve qualifiers and propagate them from
 	 operand 0.  */
-      type_quals = TYPE_QUALS (type)
-	| TYPE_QUALS (TREE_TYPE (TREE_OPERAND (expr, 0)));
+      addr_space_t op_as;
+      cv_qualifier op_cv;
+      std::tie (op_cv, op_as)
+	= TYPE_QUALS (TREE_TYPE (TREE_OPERAND (expr, 0))).split();
+      type_quals |= op_cv;
+      type_quals.set_as (op_as);
       if (TYPE_QUALS (type) != type_quals)
 	type = build_qualified_type (TYPE_MAIN_VARIANT (type), type_quals);
 
diff --git a/gcc/ipa-free-lang-data.cc b/gcc/ipa-free-lang-data.cc
index 3ff3f9638ff3..04df50d62074 100644
--- a/gcc/ipa-free-lang-data.cc
+++ b/gcc/ipa-free-lang-data.cc
@@ -444,9 +444,8 @@ free_lang_data_in_type (tree type, class free_lang_data_d *fld)
 	  tree arg_type = TREE_VALUE (p);
 	  if (TYPE_READONLY (arg_type) || TYPE_VOLATILE (arg_type))
 	    {
-	      int quals = TYPE_QUALS (arg_type)
-		& ~TYPE_QUAL_CONST
-		& ~TYPE_QUAL_VOLATILE;
+	      auto quals = (TYPE_QUALS (arg_type)
+			    .without (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE));
 	      TREE_VALUE (p) = build_qualified_type (arg_type, quals);
 	      if (!fld->pset.add (TREE_VALUE (p)))
 		free_lang_data_in_type (TREE_VALUE (p), fld);
diff --git a/gcc/jit/dummy-frontend.cc b/gcc/jit/dummy-frontend.cc
index 4df9eada65bd..8a8dc4cbaa06 100644
--- a/gcc/jit/dummy-frontend.cc
+++ b/gcc/jit/dummy-frontend.cc
@@ -1344,7 +1344,7 @@ recording::type* tree_type_to_jit_type (tree type)
     tree tp = TYPE_MAIN_VARIANT (type);
     for ( ; tp != NULL ; tp = TYPE_NEXT_VARIANT (tp))
     {
-      if (TYPE_QUALS (tp) == 0 && type != tp)
+      if (TYPE_QUALS (tp) == qualifier_set {} && type != tp)
       {
 	recording::type* result = tree_type_to_jit_type (tp);
 	if (result != NULL)
diff --git a/gcc/langhooks-def.h b/gcc/langhooks-def.h
index 33a99266187c..5031463a054b 100644
--- a/gcc/langhooks-def.h
+++ b/gcc/langhooks-def.h
@@ -176,7 +176,7 @@ extern const char *lhd_get_sarif_source_language (const char *);
 
 /* Tree dump hooks.  */
 extern bool lhd_tree_dump_dump_tree (void *, tree);
-extern int lhd_tree_dump_type_quals (const_tree);
+extern qualifier_set lhd_tree_dump_type_quals (const_tree);
 extern tree lhd_make_node (enum tree_code);
 
 #define LANG_HOOKS_TREE_DUMP_DUMP_TREE_FN lhd_tree_dump_dump_tree
diff --git a/gcc/langhooks.cc b/gcc/langhooks.cc
index d55e2ca78560..8711386bb206 100644
--- a/gcc/langhooks.cc
+++ b/gcc/langhooks.cc
@@ -262,7 +262,7 @@ lhd_tree_dump_dump_tree (void *di ATTRIBUTE_UNUSED, tree t ATTRIBUTE_UNUSED)
 /* lang_hooks.tree_dump.type_qual:  Determine type qualifiers in a
    language-specific way.  */
 
-int
+qualifier_set
 lhd_tree_dump_type_quals (const_tree t)
 {
   return TYPE_QUALS (t);
diff --git a/gcc/langhooks.h b/gcc/langhooks.h
index 546d7ddcdfb9..39eec160c2de 100644
--- a/gcc/langhooks.h
+++ b/gcc/langhooks.h
@@ -28,6 +28,9 @@ struct gimplify_omp_ctx;
 
 struct array_descr_info;
 
+/* Forward-declaration for qualifier_set in tree.h.  */
+struct qualifier_set;
+
 /* A print hook for print_tree ().  */
 typedef void (*lang_print_tree_hook) (FILE *, tree, int indent);
 
@@ -53,7 +56,7 @@ struct lang_hooks_for_tree_dump
   bool (*dump_tree) (void *, tree);
 
   /* Determine type qualifiers in a language-specific way.  */
-  int (*type_quals) (const_tree);
+  qualifier_set (*type_quals) (const_tree);
 };
 
 /* Hooks related to types.  */
diff --git a/gcc/objc/objc-act.cc b/gcc/objc/objc-act.cc
index f41a8b42b6dc..f49ff9f245f2 100644
--- a/gcc/objc/objc-act.cc
+++ b/gcc/objc/objc-act.cc
@@ -8461,11 +8461,7 @@ objc_push_parm (tree parm)
     = lang_hooks.types.type_promotes_to (TREE_TYPE (parm));
 
   /* Record constancy and volatility.  */
-  c_apply_type_quals_to_decl
-  ((TYPE_READONLY (TREE_TYPE (parm)) ? TYPE_QUAL_CONST : 0)
-   | (TYPE_RESTRICT (TREE_TYPE (parm)) ? TYPE_QUAL_RESTRICT : 0)
-   | (TYPE_ATOMIC (TREE_TYPE (parm)) ? TYPE_QUAL_ATOMIC : 0)
-   | (TYPE_VOLATILE (TREE_TYPE (parm)) ? TYPE_QUAL_VOLATILE : 0), parm);
+  c_apply_type_quals_to_decl (TYPE_QUALS (TREE_TYPE (parm)), parm);
 
   objc_parmlist = chainon (objc_parmlist, parm);
 }
diff --git a/gcc/omp-low.cc b/gcc/omp-low.cc
index ad5b2225d279..96b36c0b03cc 100644
--- a/gcc/omp-low.cc
+++ b/gcc/omp-low.cc
@@ -835,7 +835,9 @@ install_var_field (tree var, bool by_ref, int mask, omp_context *ctx,
      the pointed-to type will be ignored by points-to analysis.  */
   if (POINTER_TYPE_P (type)
       && TYPE_RESTRICT (type))
-    type = build_qualified_type (type, TYPE_QUALS (type) & ~TYPE_QUAL_RESTRICT);
+    type = build_qualified_type (type,
+				 TYPE_QUALS (type)
+				 .without (TYPE_QUAL_RESTRICT));
 
   if (mask & 4)
     {
diff --git a/gcc/omp-oacc-neuter-broadcast.cc b/gcc/omp-oacc-neuter-broadcast.cc
index ef3f02bf0b5a..8ce248d0ea23 100644
--- a/gcc/omp-oacc-neuter-broadcast.cc
+++ b/gcc/omp-oacc-neuter-broadcast.cc
@@ -573,7 +573,9 @@ install_var_field (tree var, tree record_type, field_map_t *fields)
 
   if (POINTER_TYPE_P (type)
       && TYPE_RESTRICT (type))
-    type = build_qualified_type (type, TYPE_QUALS (type) & ~TYPE_QUAL_RESTRICT);
+    type = build_qualified_type (type,
+				 TYPE_QUALS (type)
+				 .without (TYPE_QUAL_RESTRICT));
 
   tree field = build_decl (BUILTINS_LOCATION, FIELD_DECL, name, type);
 
diff --git a/gcc/omp-offload.cc b/gcc/omp-offload.cc
index 7cd2a572b7c0..19dde16b3c25 100644
--- a/gcc/omp-offload.cc
+++ b/gcc/omp-offload.cc
@@ -1891,26 +1891,33 @@ oacc_rewrite_var_decl (tree *tp, int *walk_subtrees, void *data)
       if (!new_decl)
 	return NULL;
 
-      int base_quals = TYPE_QUALS (TREE_TYPE (*new_decl));
+      auto base_quals = TYPE_QUALS (TREE_TYPE (*new_decl));
+      cv_qualifier base_cv;
+      addr_space_t base_as;
+      std::tie (base_cv, base_as) = base_quals.split ();
       tree field = TREE_OPERAND (*tp, 1);
 
       /* Adjust the type of the field.  */
-      int field_quals = TYPE_QUALS (TREE_TYPE (field));
-      if (TREE_CODE (field) == FIELD_DECL && field_quals != base_quals)
+      auto field_quals = TYPE_QUALS (TREE_TYPE (field));
+      if (TREE_CODE (field) == FIELD_DECL
+	  && field_quals != base_quals)
 	{
 	  tree *field_type = &TREE_TYPE (field);
 	  while (TREE_CODE (*field_type) == ARRAY_TYPE)
 	    field_type = &TREE_TYPE (*field_type);
-	  field_quals |= base_quals;
+	  field_quals |= base_cv;
+	  field_quals.set_as (base_as);
 	  *field_type = build_qualified_type (*field_type, field_quals);
 	}
 
       /* Adjust the type of the component ref itself.  */
       tree comp_type = TREE_TYPE (*tp);
-      int comp_quals = TYPE_QUALS (comp_type);
-      if (TREE_CODE (*tp) == COMPONENT_REF && comp_quals != base_quals)
+      auto comp_quals = TYPE_QUALS (comp_type);
+      if (TREE_CODE (*tp) == COMPONENT_REF
+	  && comp_quals != base_quals)
 	{
-	  comp_quals |= base_quals;
+	  comp_quals |= base_cv;
+	  comp_quals.set_as (base_as);
 	  TREE_TYPE (*tp)
 	    = build_qualified_type (comp_type, comp_quals);
 	}
diff --git a/gcc/rust/backend/rust-tree.cc b/gcc/rust/backend/rust-tree.cc
index b7a50376ecd8..bc09acf8ed88 100644
--- a/gcc/rust/backend/rust-tree.cc
+++ b/gcc/rust/backend/rust-tree.cc
@@ -951,10 +951,9 @@ decl_maybe_constant_var_p (tree decl)
 /* Returns the type qualifiers for this type, including the qualifiers on the
    elements for an array type.  */
 
-int
+cv_qualifier
 rs_type_quals (const_tree type)
 {
-  int quals;
   /* This CONST_CAST is okay because strip_array_types returns its
      argument unmodified and we assign it to a const_tree.  */
   type = strip_array_types (const_cast<tree> (type));
@@ -962,12 +961,17 @@ rs_type_quals (const_tree type)
       /* Quals on a FUNCTION_TYPE are memfn quals.  */
       || TREE_CODE (type) == FUNCTION_TYPE)
     return TYPE_UNQUALIFIED;
-  quals = TYPE_QUALS (type);
+
+  addr_space_t addr_space;
+  cv_qualifier quals;
+  std::tie (quals, addr_space) = TYPE_QUALS (type).split ();
   /* METHOD and REFERENCE_TYPEs should never have quals.  */
   // gcc_assert (
   //   (TREE_CODE (type) != METHOD_TYPE && !TYPE_REF_P (type))
   //   || ((quals & (TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE)) ==
   //   TYPE_UNQUALIFIED));
+  /* At the moment, the Rust front-end does not do address spaces.  */
+  gcc_assert (ADDR_SPACE_GENERIC_P (addr_space));
   return quals;
 }
 
@@ -1292,15 +1296,24 @@ lookup_add (tree fns, tree lookup)
 /* Returns the function-cv-quals for TYPE, which must be a FUNCTION_TYPE or
    METHOD_TYPE.  */
 
-int
+cv_qualifier
 type_memfn_quals (const_tree type)
 {
+  qualifier_set quals;
   if (TREE_CODE (type) == FUNCTION_TYPE)
-    return TYPE_QUALS (type);
+    quals = TYPE_QUALS (type);
   else if (TREE_CODE (type) == METHOD_TYPE)
-    return rs_type_quals (class_of_this_parm (type));
+    quals = rs_type_quals (class_of_this_parm (type));
   else
     rust_unreachable ();
+
+  addr_space_t as;
+  cv_qualifier cv;
+  std::tie (cv, as) = quals.split ();
+  /* These should never include an address space, at least for the time
+     being.  */
+  gcc_checking_assert (ADDR_SPACE_GENERIC_P (as));
+  return cv;
 }
 
 // forked from gcc/cp/pt.cc find_parameter_pack_data
@@ -2502,11 +2515,11 @@ build_cplus_array_type (tree elt_type, tree index_type, int dependent)
    in a similar manner for restricting non-pointer types.  */
 
 tree
-rs_build_qualified_type_real (tree type, int type_quals,
+rs_build_qualified_type_real (tree type, cv_qualifier type_quals,
 			      tsubst_flags_t complain)
 {
   tree result;
-  int bad_quals = TYPE_UNQUALIFIED;
+  auto bad_quals = TYPE_UNQUALIFIED;
 
   if (type == error_mark_node)
     return type;
@@ -3323,12 +3336,10 @@ check_for_uninitialized_const_var (tree decl, bool constexpr_context_p,
 tree
 cv_unqualified (tree type)
 {
-  int quals;
-
   if (type == error_mark_node)
     return type;
 
-  quals = rs_type_quals (type);
+  auto quals = rs_type_quals (type);
   quals &= ~(TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE);
   return rs_build_qualified_type (type, quals);
 }
@@ -3843,7 +3854,7 @@ strip_top_quals (tree t)
 {
   if (TREE_CODE (t) == ARRAY_TYPE)
     return t;
-  return rs_build_qualified_type (t, 0);
+  return rs_build_qualified_type (t, TYPE_UNQUALIFIED);
 }
 
 // forked from gcc/cp/typeck2.cc cxx_incomplete_type_inform
diff --git a/gcc/rust/backend/rust-tree.h b/gcc/rust/backend/rust-tree.h
index b995d0afd8a8..74fc876cd539 100644
--- a/gcc/rust/backend/rust-tree.h
+++ b/gcc/rust/backend/rust-tree.h
@@ -2976,7 +2976,7 @@ extern bool maybe_constexpr_fn (tree);
 
 extern bool var_in_maybe_constexpr_fn (tree);
 
-extern int rs_type_quals (const_tree type);
+extern cv_qualifier rs_type_quals (const_tree type);
 
 inline bool type_unknown_p (const_tree);
 
@@ -3004,7 +3004,7 @@ extern bool builtin_pack_fn_p (tree);
 
 extern tree make_conv_op_name (tree);
 
-extern int type_memfn_quals (const_tree);
+extern cv_qualifier type_memfn_quals (const_tree);
 
 struct c_fileinfo *get_fileinfo (const char *);
 
@@ -3024,7 +3024,7 @@ extern bool rs_tree_equal (tree, tree);
 
 extern bool compparms (const_tree, const_tree);
 
-extern tree rs_build_qualified_type_real (tree, int, tsubst_flags_t);
+extern tree rs_build_qualified_type_real (tree, cv_qualifier, tsubst_flags_t);
 #define rs_build_qualified_type(TYPE, QUALS)                                   \
   rs_build_qualified_type_real ((TYPE), (QUALS), tf_warning_or_error)
 extern bool cv_qualified_p (const_tree);
@@ -3215,10 +3215,6 @@ enum
   ce_exact
 };
 
-extern tree rs_build_qualified_type_real (tree, int, tsubst_flags_t);
-#define rs_build_qualified_type(TYPE, QUALS)                                   \
-  rs_build_qualified_type_real ((TYPE), (QUALS), tf_warning_or_error)
-
 extern tree rs_walk_subtrees (tree *, int *, walk_tree_fn, void *,
 			      hash_set<tree> *);
 #define rs_walk_tree(tp, func, data, pset)                                     \
diff --git a/gcc/tree-core.h b/gcc/tree-core.h
index 918e077af1b3..f222e21e272a 100644
--- a/gcc/tree-core.h
+++ b/gcc/tree-core.h
@@ -1,4 +1,4 @@
-/* Core data structures for the 'tree' type.
+/* Core data structures for the 'tree' type.  -*- C++ -*-
    Copyright (C) 1989-2026 Free Software Foundation, Inc.
 
 This file is part of GCC.
@@ -689,17 +689,73 @@ enum omp_memory_order {
 };
 #define OMP_FAIL_MEMORY_ORDER_SHIFT 3
 
-/* There is a TYPE_QUAL value for each type qualifier.  They can be
-   combined by bitwise-or to form the complete set of qualifiers for a
-   type.  */
-enum cv_qualifier {
+/* There is a TYPE_QUAL value for each type qualifier, except for address
+   spaces.  Note that the 'qualifier_set' type, used to represent the totality
+   of qualifiers of a type, including these, will need adjustment if a new
+   qualifier is added here.  */
+enum cv_qualifier : unsigned char {
   TYPE_UNQUALIFIED   = 0x0,
   TYPE_QUAL_CONST    = 0x1,
   TYPE_QUAL_VOLATILE = 0x2,
   TYPE_QUAL_RESTRICT = 0x4,
-  TYPE_QUAL_ATOMIC   = 0x8
+  TYPE_QUAL_ATOMIC   = 0x8,
+
+  /* Useful as a mask.  */
+  TYPE_QUAL_ALL = (TYPE_QUAL_CONST
+		   | TYPE_QUAL_VOLATILE
+		   | TYPE_QUAL_RESTRICT
+		   | TYPE_QUAL_ATOMIC)
 };
 
+/* Convenience operator, making it so that the bit-ops of two CV-qualifiers is
+   also of type cv_qualifier, rather than 'int'.  This is sound for
+   CV-qualifiers as they act like sets (unlike general qualifier sets, which
+   are slightly more complex).  */
+
+constexpr cv_qualifier
+operator| (cv_qualifier l, cv_qualifier r)
+{
+  return (cv_qualifier) ((static_cast<unsigned char> (l)
+			  | static_cast<unsigned char> (r))
+			 & TYPE_QUAL_ALL);
+}
+constexpr cv_qualifier &
+operator|= (cv_qualifier &l, cv_qualifier r)
+{
+  return l = l | r;
+}
+
+constexpr cv_qualifier
+operator& (cv_qualifier l, cv_qualifier r)
+{
+  return (cv_qualifier) (static_cast<unsigned char> (l)
+			 & static_cast<unsigned char> (r)
+			 & TYPE_QUAL_ALL);
+}
+constexpr cv_qualifier &
+operator&= (cv_qualifier &l, cv_qualifier r)
+{
+  return l = l & r;
+}
+
+constexpr cv_qualifier
+operator^ (cv_qualifier l, cv_qualifier r)
+{
+  return (cv_qualifier) ((static_cast<unsigned char> (l)
+			  ^ static_cast<unsigned char> (r))
+			 & TYPE_QUAL_ALL);
+}
+constexpr cv_qualifier &
+operator^= (cv_qualifier &l, cv_qualifier r)
+{
+  return l = l ^ r;
+}
+
+
+constexpr cv_qualifier
+operator~ (cv_qualifier x)
+{ return (cv_qualifier) (~static_cast<unsigned char> (x) & TYPE_QUAL_ALL); }
+
 /* Standard named or nameless data types of the C compiler.  */
 enum tree_index : unsigned {
   TI_ERROR_MARK,
diff --git a/gcc/tree-dump.cc b/gcc/tree-dump.cc
index 6f96cceff56c..85487c992b9e 100644
--- a/gcc/tree-dump.cc
+++ b/gcc/tree-dump.cc
@@ -367,7 +367,9 @@ dequeue_and_dump (dump_info_p di)
   else if (code_class == tcc_type)
     {
       /* All types have qualifiers.  */
-      int quals = lang_hooks.tree_dump.type_quals (t);
+      cv_qualifier quals;
+      addr_space_t as;
+      std::tie (quals, as) = lang_hooks.tree_dump.type_quals (t).split ();
 
       if (quals != TYPE_UNQUALIFIED)
 	{
@@ -378,6 +380,9 @@ dequeue_and_dump (dump_info_p di)
 	  di->column += 14;
 	}
 
+      if (!ADDR_SPACE_GENERIC_P (as))
+	dump_int (di, "addr-space", as);
+
       /* All types have associated declarations.  */
       dump_child ("name", TYPE_NAME (t));
 
diff --git a/gcc/tree-inline.cc b/gcc/tree-inline.cc
index 8162b1f5051a..5061782b1962 100644
--- a/gcc/tree-inline.cc
+++ b/gcc/tree-inline.cc
@@ -426,7 +426,7 @@ remap_type_1 (tree type, copy_body_data *id)
       new_tree = build_pointer_type_for_mode (remap_type (TREE_TYPE (type), id),
 					 TYPE_MODE (type),
 					 TYPE_REF_CAN_ALIAS_ALL (type));
-      if (TYPE_ATTRIBUTES (type) || TYPE_QUALS (type))
+      if (TYPE_ATTRIBUTES (type) || TYPE_QUALS (type) != qualifier_set {})
 	new_tree = build_type_attribute_qual_variant (new_tree,
 						      TYPE_ATTRIBUTES (type),
 						      TYPE_QUALS (type));
@@ -438,7 +438,7 @@ remap_type_1 (tree type, copy_body_data *id)
       new_tree = build_reference_type_for_mode (remap_type (TREE_TYPE (type), id),
 					    TYPE_MODE (type),
 					    TYPE_REF_CAN_ALIAS_ALL (type));
-      if (TYPE_ATTRIBUTES (type) || TYPE_QUALS (type))
+      if (TYPE_ATTRIBUTES (type) || TYPE_QUALS (type) != qualifier_set {})
 	new_tree = build_type_attribute_qual_variant (new_tree,
 						      TYPE_ATTRIBUTES (type),
 						      TYPE_QUALS (type));
diff --git a/gcc/tree-pretty-print.cc b/gcc/tree-pretty-print.cc
index b470a9dee7b5..0d9ca91a28ab 100644
--- a/gcc/tree-pretty-print.cc
+++ b/gcc/tree-pretty-print.cc
@@ -2271,7 +2271,7 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
     case BITINT_TYPE:
     case OPAQUE_TYPE:
       {
-	unsigned int quals = TYPE_QUALS (node);
+	auto quals = TYPE_QUALS_NO_ADDR_SPACE (node);
 	enum tree_code_class tclass;
 
 	if (quals & TYPE_QUAL_ATOMIC)
@@ -2445,17 +2445,17 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 	}
       else
         {
-	  unsigned int quals = TYPE_QUALS (node);
+	  auto quals = TYPE_QUALS (node);
 
           dump_generic_node (pp, TREE_TYPE (node), spc, flags, false);
 	  pp_space (pp);
 	  pp_string (pp, str);
 
-	  if (quals & TYPE_QUAL_CONST)
+	  if (quals.has (TYPE_QUAL_CONST))
 	    pp_string (pp, " const");
-	  if (quals & TYPE_QUAL_VOLATILE)
+	  if (quals.has (TYPE_QUAL_VOLATILE))
 	    pp_string (pp, " volatile");
-	  if (quals & TYPE_QUAL_RESTRICT)
+	  if (quals.has (TYPE_QUAL_RESTRICT))
 	    pp_string (pp, " restrict");
 
 	  if (!ADDR_SPACE_GENERIC_P (TYPE_ADDR_SPACE (node)))
@@ -2481,7 +2481,7 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
 
     case ARRAY_TYPE:
       {
-	unsigned int quals = TYPE_QUALS (node);
+	auto quals = TYPE_QUALS_NO_ADDR_SPACE (node);
 	tree tmp;
 
 	if (quals & TYPE_QUAL_ATOMIC)
@@ -2511,7 +2511,7 @@ dump_generic_node (pretty_printer *pp, tree node, int spc, dump_flags_t flags,
     case UNION_TYPE:
     case QUAL_UNION_TYPE:
       {
-	unsigned int quals = TYPE_QUALS (node);
+	auto quals = TYPE_QUALS_NO_ADDR_SPACE (node);
 
 	if (quals & TYPE_QUAL_ATOMIC)
 	  pp_string (pp, "atomic ");
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index a03f1f3704fa..762317c4ab46 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -2049,7 +2049,7 @@ tree_profiling (void)
 		tree fntype = gimple_call_fntype (call);
 		if (fntype && TYPE_READONLY (fntype))
 		  {
-		    int quals = TYPE_QUALS (fntype) & ~TYPE_QUAL_CONST;
+		    auto quals = TYPE_QUALS (fntype).without (TYPE_QUAL_CONST);
 		    fntype = build_qualified_type (fntype, quals);
 		    gimple_call_set_fntype (call, fntype);
 		  }
diff --git a/gcc/tree-sra.cc b/gcc/tree-sra.cc
index f951a38442a5..173536f69df6 100644
--- a/gcc/tree-sra.cc
+++ b/gcc/tree-sra.cc
@@ -1904,8 +1904,7 @@ build_ref_for_offset (location_t loc, tree base, poly_int64 offset,
   addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (base));
   if (as != TYPE_ADDR_SPACE (exp_type))
     exp_type = build_qualified_type (exp_type,
-				     TYPE_QUALS (exp_type)
-				     | ENCODE_QUAL_ADDR_SPACE (as));
+				     TYPE_QUALS (exp_type).with_as (as));
 
   poly_int64 byte_offset = exact_div (offset, BITS_PER_UNIT);
   get_object_alignment_1 (base, &align, &misalign);
diff --git a/gcc/tree-ssa-address.cc b/gcc/tree-ssa-address.cc
index 30baf81d7892..d10de56b3acb 100644
--- a/gcc/tree-ssa-address.cc
+++ b/gcc/tree-ssa-address.cc
@@ -450,7 +450,6 @@ move_hint_to_base (tree type, struct mem_address *parts, tree base_hint,
 {
   unsigned i;
   tree val = NULL_TREE;
-  int qual;
 
   for (i = 0; i < addr->n; i++)
     {
@@ -469,7 +468,7 @@ move_hint_to_base (tree type, struct mem_address *parts, tree base_hint,
      to TYPE directly, as the back-end will assume registers of pointer
      type are aligned, and just the base itself may not actually be.
      We use void pointer to the type's address space instead.  */
-  qual = ENCODE_QUAL_ADDR_SPACE (TYPE_ADDR_SPACE (type));
+  qualifier_set qual {TYPE_UNQUALIFIED, TYPE_ADDR_SPACE (type)};
   type = build_qualified_type (void_type_node, qual);
   parts->base = fold_convert (build_pointer_type (type), val);
   aff_combination_remove_elt (addr, i);
diff --git a/gcc/tree-switch-conversion.cc b/gcc/tree-switch-conversion.cc
index 6fcb88f3ae05..e04423c9e980 100644
--- a/gcc/tree-switch-conversion.cc
+++ b/gcc/tree-switch-conversion.cc
@@ -1012,8 +1012,7 @@ switch_conversion::build_one_array (int num, tree arr_index_type,
 						    ARTIFICIAL_RODATA_CSWITCH);
       if (!ADDR_SPACE_GENERIC_P (as))
 	{
-	  int quals = (TYPE_QUALS_NO_ADDR_SPACE (value_type)
-		       | ENCODE_QUAL_ADDR_SPACE (as));
+	  qualifier_set quals {TYPE_QUALS_NO_ADDR_SPACE (value_type), as};
 	  value_type = build_qualified_type (value_type, quals);
 	  array_type = build_array_type (value_type, arr_index_type);
 	}
diff --git a/gcc/tree-vect-stmts.cc b/gcc/tree-vect-stmts.cc
index 76e24b2c1699..4a8074dabacb 100644
--- a/gcc/tree-vect-stmts.cc
+++ b/gcc/tree-vect-stmts.cc
@@ -13667,7 +13667,8 @@ get_related_vectype_for_scalar_type (machine_mode prevailing_mode,
      type.  */
   if (TYPE_ADDR_SPACE (orig_scalar_type) != TYPE_ADDR_SPACE (vectype))
     return build_qualified_type
-	     (vectype, KEEP_QUAL_ADDR_SPACE (TYPE_QUALS (orig_scalar_type)));
+      (vectype, qualifier_set {TYPE_UNQUALIFIED,
+			       TYPE_ADDR_SPACE (orig_scalar_type)});
 
   return vectype;
 }
diff --git a/gcc/tree.cc b/gcc/tree.cc
index 411752d14636..39e37ebf0ca6 100644
--- a/gcc/tree.cc
+++ b/gcc/tree.cc
@@ -28,6 +28,7 @@ along with GCC; see the file COPYING3.  If not see
    calls language-dependent routines.  */
 
 #include "config.h"
+#define INCLUDE_FUNCTIONAL // for expected.h
 #include "system.h"
 #include "coretypes.h"
 #include "backend.h"
@@ -77,6 +78,9 @@ along with GCC; see the file COPYING3.  If not see
 #include "ubsan.h"
 #include "attr-callback.h"
 
+/* For try_quals_merge.  */
+#include "util/expected.h"
+
 /* Names of tree components.
    Used for printing out the tree and error messages.  */
 #define DEFTREECODE(SYM, NAME, TYPE, LEN) NAME,
@@ -283,7 +287,7 @@ static GTY ((cache ("gt_value_expr_mark")))
 static GTY ((cache))
      hash_table<tree_vec_map_cache_hasher> *debug_args_for_decl;
 
-static void set_type_quals (tree, int);
+static void set_type_quals (tree, qualifier_set);
 static void print_type_hash_statistics (void);
 static void print_debug_expr_statistics (void);
 static void print_value_expr_statistics (void);
@@ -5672,17 +5676,126 @@ protected_set_expr_location_if_unset (tree t, location_t loc)
     protected_set_expr_location (t, loc);
 }
 
-/* Set the type qualifiers for TYPE to TYPE_QUALS, which is a bitmask
-   of the various TYPE_QUAL values.  */
+
+/* Documented next to declaration in tree.h.  */
+tl::expected<qualifier_set, qualifier_set::merge_error>
+qualifier_set::merge (qualifier_set other,
+		      bool strict_addr_space /* = false */) const
+{
+  using ME = qualifier_set::merge_error;
+
+  if (has (TYPE_QUAL_ATOMIC) != other.has (TYPE_QUAL_ATOMIC))
+    return tl::make_unexpected (ME::atomic_mismatch);
+
+  auto cv_merged = cv_quals () | other.cv_quals ();
+  auto as1 = addr_space ();
+  auto as2 = other.addr_space ();
+
+  addr_space_t as_super;
+  if (as1 == as2)
+    as_super = as1;
+  else if (!strict_addr_space
+	   && targetm.addr_space.subset_p (as1, as2))
+    as_super = as2;
+  else if (!strict_addr_space
+	   && targetm.addr_space.subset_p (as2, as1))
+    as_super = as1;
+  else
+    return tl::make_unexpected (ME::disjoint_address_spaces);
+
+  return qualifier_set {cv_merged, as_super};
+}
+
+tl::expected<qualifier_set, qualifier_set::join_error>
+qualifier_set::join (qualifier_set other) const
+{
+  using JE = qualifier_set::join_error;
+  auto cv_merged = cv_quals () | other.cv_quals ();
+  auto as1 = addr_space ();
+  auto as2 = other.addr_space ();
+
+  addr_space_t as = ADDR_SPACE_GENERIC;
+  if (as1 == as2)
+    as = as1;
+  else if (!ADDR_SPACE_GENERIC_P (as1) && !ADDR_SPACE_GENERIC_P (as2))
+    return tl::make_unexpected (JE::double_addr_space);
+  else if (ADDR_SPACE_GENERIC_P (as1))
+    as = as2;
+  else
+    as = as1;
+
+  return qualifier_set {cv_merged, as};
+}
+
+bool
+qualifier_set::can_qualify (qualifier_set subset,
+			    bool nop_only /* = false */) const
+{
+  /* Documented next to declaration in tree.h.  */
+
+  /* SUBSET is included in SUPERSET if its address space is a subset of that of
+     SUPERSET, and all the CV-quals of SUBSET are present on SUPERSET.  */
+  auto cv_sup = cv_quals ();
+  auto cv_sub = subset.cv_quals ();
+  auto as_sup = addr_space ();
+  auto as_sub = subset.addr_space ();
+  return ((cv_sup & cv_sub) == cv_sub
+	  /* Differences in the _Atomic qualifier cannot be crossed.  */
+	  && (cv_sup & TYPE_QUAL_ATOMIC) == (cv_sub & TYPE_QUAL_ATOMIC)
+	  && (as_sup == as_sub
+	      || (!nop_only
+		  && targetm.addr_space.subset_p (as_sub, as_sup))));
+}
+
+DEBUG_FUNCTION void
+qualifier_set::debug () const
+{
+  putc ('{', stderr);
+  cv_qualifier cv;
+  addr_space_t as;
+  std::tie (cv, as) = split ();
+  bool has_previous = false;
+  auto handle_bit = [&] (cv_qualifier bit, const char *lbl)
+  {
+    if (!(cv & bit))
+      return;
+
+    if (has_previous)
+      fputs (", ", stderr);
+
+    has_previous = true;
+    fputs (lbl, stderr);
+  };
+
+  handle_bit (TYPE_QUAL_CONST, "const");
+  handle_bit (TYPE_QUAL_VOLATILE, "volatile");
+  handle_bit (TYPE_QUAL_RESTRICT, "restrict");
+  handle_bit (TYPE_QUAL_ATOMIC, "_Atomic");
+
+  /* If new bits appear, let the developer know.  */
+  static_assert ((TYPE_QUAL_CONST | TYPE_QUAL_VOLATILE | TYPE_QUAL_RESTRICT
+		  | TYPE_QUAL_ATOMIC)
+		 == TYPE_QUAL_ALL,
+		"new qualifiers added, update handle_bit calls above");
+
+  if (has_previous)
+    fputs (", ", stderr);
+
+  fprintf (stderr, "AS%d", as);
+
+  fputs ("}\n", stderr);
+}
+
+/* Set the type qualifiers for TYPE to TYPE_QUALS.  */
 
 static void
-set_type_quals (tree type, int type_quals)
+set_type_quals (tree type, qualifier_set type_quals)
 {
-  TYPE_READONLY (type) = (type_quals & TYPE_QUAL_CONST) != 0;
-  TYPE_VOLATILE (type) = (type_quals & TYPE_QUAL_VOLATILE) != 0;
-  TYPE_RESTRICT (type) = (type_quals & TYPE_QUAL_RESTRICT) != 0;
-  TYPE_ATOMIC (type) = (type_quals & TYPE_QUAL_ATOMIC) != 0;
-  TYPE_ADDR_SPACE (type) = DECODE_QUAL_ADDR_SPACE (type_quals);
+  TYPE_READONLY (type) = type_quals.has (TYPE_QUAL_CONST);
+  TYPE_VOLATILE (type) = type_quals.has (TYPE_QUAL_VOLATILE);
+  TYPE_RESTRICT (type) = type_quals.has (TYPE_QUAL_RESTRICT);
+  TYPE_ATOMIC (type) = type_quals.has (TYPE_QUAL_ATOMIC);
+  TYPE_ADDR_SPACE (type) = type_quals.addr_space ();
 }
 
 /* Returns true iff CAND and BASE have equivalent language-specific
@@ -5758,7 +5871,7 @@ check_base_type (const_tree cand, const_tree base)
     return true;
   /* Atomic types increase minimal alignment.  We must to do so as well
      or we get duplicated canonical types. See PR88686.  */
-  if ((TYPE_QUALS (cand) & TYPE_QUAL_ATOMIC))
+  if (TYPE_QUALS (cand).has (TYPE_QUAL_ATOMIC))
     {
       /* See if this object can map to a basic atomic type.  */
       tree atomic_type = find_atomic_core_type (cand);
@@ -5771,7 +5884,7 @@ check_base_type (const_tree cand, const_tree base)
 /* Returns true iff CAND is equivalent to BASE with TYPE_QUALS.  */
 
 bool
-check_qualified_type (const_tree cand, const_tree base, int type_quals)
+check_qualified_type (const_tree cand, const_tree base, qualifier_set type_quals)
 {
   return (TYPE_QUALS (cand) == type_quals
 	  && check_base_type (cand, base)
@@ -5802,7 +5915,7 @@ check_aligned_type (const_tree cand, const_tree base, unsigned int align)
    return NULL_TREE.  */
 
 tree
-get_qualified_type (tree type, int type_quals)
+get_qualified_type (tree type, qualifier_set type_quals)
 {
   if (TYPE_QUALS (type) == type_quals)
     return type;
@@ -5834,7 +5947,7 @@ get_qualified_type (tree type, int type_quals)
    exist.  This function never returns NULL_TREE.  */
 
 tree
-build_qualified_type (tree type, int type_quals MEM_STAT_DECL)
+build_qualified_type (tree type, qualifier_set type_quals MEM_STAT_DECL)
 {
   tree t;
 
@@ -5847,7 +5960,7 @@ build_qualified_type (tree type, int type_quals MEM_STAT_DECL)
       t = build_variant_type_copy (type PASS_MEM_STAT);
       set_type_quals (t, type_quals);
 
-      if (((type_quals & TYPE_QUAL_ATOMIC) == TYPE_QUAL_ATOMIC))
+      if (type_quals.has (TYPE_QUAL_ATOMIC))
 	{
 	  /* See if this object can map to a basic atomic type.  */
 	  tree atomic_type = find_atomic_core_type (type);
@@ -9477,7 +9590,8 @@ make_vector_type (tree innertype, poly_int64 nunits, machine_mode mode)
 
   /* We have built a main variant, based on the main variant of the
      inner type. Use it to build the variant we return.  */
-  if ((TYPE_ATTRIBUTES (innertype) || TYPE_QUALS (innertype))
+  if ((TYPE_ATTRIBUTES (innertype)
+       || TYPE_QUALS (innertype) != qualifier_set{})
       && TREE_TYPE (t) != innertype)
     return build_type_attribute_qual_variant (t,
 					      TYPE_ATTRIBUTES (innertype),
@@ -9603,11 +9717,12 @@ build_atomic_base (tree type, unsigned int align)
   tree t;
 
   /* Make sure its not already registered.  */
-  if ((t = get_qualified_type (type, TYPE_QUAL_ATOMIC)))
+  qualifier_set atomic_quals {TYPE_QUAL_ATOMIC};
+  if ((t = get_qualified_type (type, atomic_quals)))
     return t;
 
   t = build_variant_type_copy (type);
-  set_type_quals (t, TYPE_QUAL_ATOMIC);
+  set_type_quals (t, atomic_quals);
 
   if (align)
     SET_TYPE_ALIGN (t, align);
diff --git a/gcc/tree.h b/gcc/tree.h
index 73a26dbe75c7..a1c8f55cc413 100644
--- a/gcc/tree.h
+++ b/gcc/tree.h
@@ -21,8 +21,10 @@ along with GCC; see the file COPYING3.  If not see
 #define GCC_TREE_H
 
 #include "tree-core.h"
+#include "coretypes.h"
 #include "options.h"
 #include "vec.h"
+#include "util/expected_fwd.h"
 
 /* Convert a target-independent built-in function code to a combined_fn.  */
 
@@ -2558,39 +2560,289 @@ extern tree vector_element_bits_tree (const_tree);
 /* The address space the type is in.  */
 #define TYPE_ADDR_SPACE(NODE) (TYPE_CHECK (NODE)->base.u.bits.address_space)
 
-/* Encode/decode the named memory support as part of the qualifier.  If more
-   than 8 qualifiers are added, these macros need to be adjusted.  */
-#define ENCODE_QUAL_ADDR_SPACE(NUM) (((NUM) & 0xFF) << 8)
-#define DECODE_QUAL_ADDR_SPACE(X) (((X) >> 8) & 0xFF)
+/* A qualifier set is the aggregate of all qualifiers on a given type.  It
+   consists of the 'const', 'volatile', 'restrict', and 'atomic' qualification,
+   which are all either present or absent, and an address space qualifier,
+   which is always present (but possibly ADDR_SPACE_GENERIC).  */
 
-/* Return all qualifiers except for the address space qualifiers.  */
-#define CLEAR_QUAL_ADDR_SPACE(X) ((X) & ~0xFF00)
+struct qualifier_set
+{
+  /* Construct an empty qualifier set with the generic address space.  Such a
+     qualifier set corresponds to unqualified types.  */
+  qualifier_set () = default;
+  static_assert (cv_qualifier {} == TYPE_UNQUALIFIED
+		 && addr_space_t {} == ADDR_SPACE_GENERIC,
+		 "We want the trivial default constructor to use those vals");
 
-/* Only keep the address space out of the qualifiers and discard the other
-   qualifiers.  */
-#define KEEP_QUAL_ADDR_SPACE(X) ((X) & 0xFF00)
+  /* Construct a qualifier set containing the qualifiers CV_QUALS and the
+     generic address space.  */
+  constexpr
+  qualifier_set (cv_qualifier cv_quals)
+    : m_cv_quals {cv_quals},
+      m_addr_space {ADDR_SPACE_GENERIC}
+  {}
 
-/* The set of type qualifiers for this type.  */
-#define TYPE_QUALS(NODE)					\
-  ((int) ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
-	  | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
-	  | (TYPE_ATOMIC (NODE) * TYPE_QUAL_ATOMIC)		\
-	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)		\
-	  | (ENCODE_QUAL_ADDR_SPACE (TYPE_ADDR_SPACE (NODE)))))
+  /* Construct a qualifier set containing the qualifiers CV_QUALS and the
+     address space AS.  */
+  constexpr
+  qualifier_set (cv_qualifier cv_quals, addr_space_t as)
+    : m_cv_quals {cv_quals},
+      m_addr_space {as}
+  {}
+
+  explicit qualifier_set (tree type); /* At end of file, due to macro mess. */
+
+  /* Add QUAL to this qualifier set.  */
+  constexpr void
+  add (cv_qualifier qual)
+  {
+    m_cv_quals |= qual;
+  }
+
+  /* Remove QUAL from this qualifier set.  */
+  constexpr void
+  remove (cv_qualifier qual)
+  {
+    m_cv_quals &= ~qual;
+  }
+
+  /* Set the address space of this qualifier set to AS.  */
+  constexpr void
+  set_as (addr_space_t as)
+  {
+    this->m_addr_space = as;
+  }
+
+  /* Returns a new qualifier set, with QUAL added (as with 'add').  */
+  constexpr qualifier_set
+  with (cv_qualifier qual) const
+  {
+    auto ret = *this;
+    ret.add (qual);
+    return ret;
+  }
+
+  /* Returns a new qualifier set, with QUAL removed (as with 'remove').  */
+  WARN_UNUSED_RESULT constexpr qualifier_set
+  without (cv_qualifier qual) const
+  {
+    auto ret = *this;
+    ret.remove (qual);
+    return ret;
+  }
+
+  /* Return this qualifier set, with address space set to AS.  */
+  WARN_UNUSED_RESULT constexpr qualifier_set
+  with_as (addr_space_t as) const
+  {
+    auto ret = *this;
+    ret.set_as (as);
+    return ret;
+  }
+
+  /* Get CV qualifiers of this qualifier set.
+
+     Using this getter alone is usually a mistake; most of the time, where
+     there is a qualifier_set, there should be handling for all its components,
+     rather than just one of them.  Prefer using 'split' at least once in a
+     given hunk.  */
+  constexpr cv_qualifier
+  cv_quals () const
+  { return m_cv_quals; }
+
+  /* Get address space qualifier of this qualifier set.
+
+     Using this getter alone is usually a mistake; most of the time, where
+     there is a qualifier_set, there should be handling for all its components,
+     rather than just one of them.  Prefer using 'split' at least once in a
+     given hunk.  */
+  constexpr addr_space_t
+  addr_space () const
+  { return m_addr_space; }
+
+  /* Return all qualifiers both in this qualifier set and in OTHER_CV.  */
+  constexpr cv_qualifier
+  intersect (cv_qualifier other_cv) const
+  { return cv_quals () & other_cv; }
+
+  /* Return true iff any of OTHER_CV are contained in THIS.  */
+  constexpr bool
+  has (cv_qualifier other_cv) const
+  { return intersect (other_cv); }
+
+  /* Get the symmetric difference of this qualifier set with CV-qualifiers
+     QUAL.  Of course, as QUAL lacks an address space, the address space of
+     this qualifier set is preserved.  */
+  WARN_UNUSED_RESULT constexpr qualifier_set
+  symmetric_difference (cv_qualifier qual) const
+  {
+    return {cv_quals () ^ qual, addr_space ()};
+  }
+
+  /* Returns true if qualifiers in SUBSET can be replaced with qualifiers in
+     THIS safely.
+
+     In general, this means that an object qualified per SUBSET can be used as
+     if it was qualified per this qualifier set (e.g. 'T' as 'const T', or
+     'const T' as 'const volatile AS1 T', presuming that AS1 is a superset of
+     the generic address space).
+
+     If NOP_ONLY, return 'true' iff a pointer with a pointee qualified via
+     SUBSET can be converted into a pointer with a pointee qualified via THIS
+     (i.e. if a NOP_EXPR conversion would be valid).  In particular, this means
+     address space mismatches are forbidden.  */
+  bool can_qualify (qualifier_set subset, bool nop_only = false) const;
+
+  enum class merge_error
+  {
+    /* The address spaces of the to-be-merged qualifier sets were disjoint,
+       i.e. neither contained the other.  */
+    disjoint_address_spaces,
+
+    /* The to-be-merged qualifier sets differed in TYPE_QUAL_ATOMIC.  */
+    atomic_mismatch,
+  };
+
+  /* Attempt to produce a qualifier_set that's a merge of qualifiers in THIS
+     and OTHER.  Such a qualifier set can be used instead of either THIS or
+     OTHERT safely.  (i.e. if a type was qualified by either THIS or OTHER, it
+     can be qualified by their merge instead safely, possibly through a
+     conversion)
+
+     This operation may fail.  In that case, the error value returned provides
+     reasoning for the failure.
+
+     If STRICT_ADDR_SPACE, then no address space mismatch is permitted.  This
+     is useful if merging below the top-level of pointers (i.e. in a case such
+     as 'AS1 T**' vs 'AS2 T**').
+
+     You'll need to include expected.h to use this.  */
+
+  tl::expected<qualifier_set, merge_error>
+  merge (qualifier_set other, bool strict_addr_space = false) const;
+
+  enum class join_error
+  {
+    /* The joined qualifier set would've contained two address space
+       qualifiers.  */
+    double_addr_space,
+  };
+
+  /* Return a qualifier set that has all the qualifiers of THIS and OTHER.
+     Unlike 'merge', this operation operates purely syntactically; if THIS is
+     {q1_1, q1_2, ..., q1_i} and OTHER {q2_1, q2_2, ..., q2_j}, then returns
+     the qualifier set obtained by concatenating the sequences q1 and q2
+     without duplicates, if such a qualifier set is valid.
+
+     Specifically, this implies that if THIS or OTHER both (syntactically)
+     contain an address space qualifier, and they're different, the operation
+     fails (even if one is subset of the other).
+
+     This operation may fail.  In that case, the error value returned provides
+     reasoning for the failure.
+
+     You'll need to include expected.h to use this.  */
+
+  tl::expected<qualifier_set, join_error> join (qualifier_set other) const;
+
+  constexpr bool
+  operator== (const qualifier_set &other) const
+  {
+    return ((cv_quals () == other.cv_quals ())
+	    && (addr_space () == other.addr_space ()));
+  }
+
+  constexpr bool
+  operator!= (const qualifier_set &other) const
+  {
+    return !operator== (other);
+  }
+
+  /* Split this qualifier set into its constituent parts.  Useful where you
+     need to make sure you've handled all the components of a qualifier
+     set.  */
+
+  constexpr std::pair<cv_qualifier, addr_space_t>
+  split () const
+  { return std::make_pair (cv_quals (), addr_space ()); }
+
+  /* True iff this qualifier set is different to {ADDR_SPACE_GENERIC} (i.e. if
+     it is syntactically non-empty).  */
+  constexpr bool
+  nonempty_p () const
+  { return *this != qualifier_set {}; }
+
+  /* Dump the contents of this qualifier set to stderr.  */
+  void debug() const;
+
+private:
+  cv_qualifier m_cv_quals;
+  addr_space_t m_addr_space;
+};
+static_assert (std::is_trivially_copyable<qualifier_set>::value, "");
+static_assert (std::is_trivially_default_constructible<qualifier_set>::value,
+	       "");
+static_assert (std::is_trivially_destructible<qualifier_set>::value, "");
+
+/* Operators & and &= are intentionally omitted, as they permit losing
+   information too easily, and silently changed the meaning of existing code.
+   Use 'can_qualify', 'intersect' and 'has' instead.  */
+
+/* For OR (union) and XOR (mutual difference) operations, we could keep an
+   address space, ergo we must return qualifier_sets.  */
+
+constexpr qualifier_set
+operator| (qualifier_set qs, cv_qualifier cvs)
+{
+  return qs.with (cvs);
+}
+
+constexpr qualifier_set
+operator| (cv_qualifier cvs, qualifier_set qs)
+{
+  return qs.with (cvs);
+}
+
+constexpr qualifier_set
+operator^ (qualifier_set qs, cv_qualifier cvs)
+{
+  return qs.symmetric_difference (cvs);
+}
+
+constexpr qualifier_set
+operator^ (cv_qualifier cvs, qualifier_set qs)
+{
+  return qs.symmetric_difference (cvs);
+}
+
+constexpr qualifier_set &
+operator|= (qualifier_set &qs, cv_qualifier quals)
+{
+  return qs = qs | quals;
+}
+constexpr qualifier_set &
+operator^= (qualifier_set &qs, cv_qualifier quals)
+{
+  return qs = qs ^ quals;
+}
 
 /* The same as TYPE_QUALS without the address space qualifications.  */
-#define TYPE_QUALS_NO_ADDR_SPACE(NODE)				\
-  ((int) ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
-	  | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
-	  | (TYPE_ATOMIC (NODE) * TYPE_QUAL_ATOMIC)		\
-	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)))
+#define TYPE_QUALS_NO_ADDR_SPACE(NODE)					\
+  (cv_qualifier ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
+		 | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
+		 | (TYPE_ATOMIC (NODE) * TYPE_QUAL_ATOMIC)		\
+		 | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)))
+
+/* The set of type qualifiers for this type.  */
+#define TYPE_QUALS(NODE)			\
+  qualifier_set {TYPE_QUALS_NO_ADDR_SPACE (NODE), TYPE_ADDR_SPACE (NODE)}
 
 /* The same as TYPE_QUALS without the address space and atomic
    qualifications.  */
-#define TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC(NODE)		\
-  ((int) ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
-	  | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
-	  | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)))
+#define TYPE_QUALS_NO_ADDR_SPACE_NO_ATOMIC(NODE)			\
+  (cv_qualifier ((TYPE_READONLY (NODE) * TYPE_QUAL_CONST)		\
+		 | (TYPE_VOLATILE (NODE) * TYPE_QUAL_VOLATILE)		\
+		 | (TYPE_RESTRICT (NODE) * TYPE_QUAL_RESTRICT)))
 
 /* These flags are available for each language front end to use internally.  */
 #define TYPE_LANG_FLAG_0(NODE) (TYPE_CHECK (NODE)->type_common.lang_flag_0)
@@ -5260,18 +5512,18 @@ extern bool check_base_type (const_tree cand, const_tree base);
 /* Check whether CAND is suitable to be returned from get_qualified_type
    (BASE, TYPE_QUALS).  */
 
-extern bool check_qualified_type (const_tree, const_tree, int);
+extern bool check_qualified_type (const_tree, const_tree, qualifier_set);
 
 /* Return a version of the TYPE, qualified as indicated by the
    TYPE_QUALS, if one exists.  If no qualified version exists yet,
    return NULL_TREE.  */
 
-extern tree get_qualified_type (tree, int);
+extern tree get_qualified_type (tree, qualifier_set);
 
 /* Like get_qualified_type, but creates the type if it does not
    exist.  This function never returns NULL_TREE.  */
 
-extern tree build_qualified_type (tree, int CXX_MEM_STAT_INFO);
+extern tree build_qualified_type (tree, qualifier_set CXX_MEM_STAT_INFO);
 
 /* Create a variant of type T with alignment ALIGN.  */
 
@@ -5283,9 +5535,10 @@ extern tree build_aligned_type (tree, unsigned int);
    build_qualified_type instead.  */
 
 #define build_type_variant(TYPE, CONST_P, VOLATILE_P)			\
-  build_qualified_type ((TYPE),						\
-			((CONST_P) ? TYPE_QUAL_CONST : 0)		\
-			| ((VOLATILE_P) ? TYPE_QUAL_VOLATILE : 0))
+  (build_qualified_type							\
+   ((TYPE),								\
+    (((CONST_P) ? TYPE_QUAL_CONST : TYPE_UNQUALIFIED)			\
+     | ((VOLATILE_P) ? TYPE_QUAL_VOLATILE : TYPE_UNQUALIFIED))))
 
 /* Make a copy of a type node.  */
 
@@ -7272,4 +7525,11 @@ make_expanded_omp_iterator (void)
   return make_tree_vec (10);
 }
 
+/* Construct a qualifier set containing all the qualifiers of TYPE.  */
+inline
+qualifier_set::qualifier_set (tree type)
+  : m_cv_quals {TYPE_QUALS_NO_ADDR_SPACE (type)}
+  , m_addr_space {TYPE_ADDR_SPACE (type)}
+{}
+
 #endif  /* GCC_TREE_H  */
diff --git a/gcc/ubsan.cc b/gcc/ubsan.cc
index 79a863b46ce6..74b7cb45e26f 100644
--- a/gcc/ubsan.cc
+++ b/gcc/ubsan.cc
@@ -1775,8 +1775,8 @@ instrument_bool_enum_load (gimple_stmt_iterator *gsi)
 
   addr_space_t as = TYPE_ADDR_SPACE (TREE_TYPE (rhs));
   if (as != TYPE_ADDR_SPACE (utype))
-    utype = build_qualified_type (utype, TYPE_QUALS (utype)
-					 | ENCODE_QUAL_ADDR_SPACE (as));
+    utype = build_qualified_type (utype,
+				  TYPE_QUALS (utype) .with_as(as));
   bool ends_bb = stmt_ends_bb_p (stmt);
   location_t loc = gimple_location (stmt);
   tree lhs = gimple_assign_lhs (stmt);
diff --git a/gcc/vtable-verify.cc b/gcc/vtable-verify.cc
index 05e14b24788d..5a8b4a7b437f 100644
--- a/gcc/vtable-verify.cc
+++ b/gcc/vtable-verify.cc
@@ -378,7 +378,6 @@ vtbl_map_get_node (tree class_type)
 
   tree class_type_decl;
   tree class_name;
-  unsigned int type_quals;
 
   if (!vtbl_map_hash)
     return NULL;
@@ -390,8 +389,8 @@ vtbl_map_get_node (tree class_type)
   class_type_decl = TYPE_NAME (class_type);
 
   /* Verify that there aren't any qualifiers on the type.  */
-  type_quals = TYPE_QUALS (TREE_TYPE (class_type_decl));
-  gcc_assert (type_quals == TYPE_UNQUALIFIED);
+  auto type_quals = TYPE_QUALS (TREE_TYPE (class_type_decl));
+  gcc_assert (type_quals == qualifier_set {});
 
   /* Get the mangled name for the unqualified type.  */
   gcc_assert (HAS_DECL_ASSEMBLER_NAME_P (class_type_decl));
@@ -417,7 +416,6 @@ find_or_create_vtbl_map_node (tree base_class_type)
   struct vtbl_map_node *node;
   struct vtbl_map_node **slot;
   tree class_type_decl;
-  unsigned int type_quals;
 
   if (!vtbl_map_hash)
     vtbl_map_hash = new vtbl_map_table_type (10);
@@ -426,8 +424,8 @@ find_or_create_vtbl_map_node (tree base_class_type)
   class_type_decl = TYPE_NAME (base_class_type);
 
   /* Verify that there aren't any type qualifiers on type.  */
-  type_quals = TYPE_QUALS (TREE_TYPE (class_type_decl));
-  gcc_assert (type_quals == TYPE_UNQUALIFIED);
+  auto type_quals = TYPE_QUALS (TREE_TYPE (class_type_decl));
+  gcc_assert (type_quals == qualifier_set {});
 
   gcc_assert (HAS_DECL_ASSEMBLER_NAME_P (class_type_decl));
   key.class_name = DECL_ASSEMBLER_NAME (class_type_decl);
diff --git a/libcc1/libcc1plugin.cc b/libcc1/libcc1plugin.cc
index 8b875eb0605b..cf5585e3d828 100644
--- a/libcc1/libcc1plugin.cc
+++ b/libcc1/libcc1plugin.cc
@@ -699,7 +699,7 @@ plugin_build_qualified_type (cc1_plugin::connection *,
 			     enum gcc_qualifiers qualifiers)
 {
   tree unqualified_type = convert_in (unqualified_type_in);
-  int quals = 0;
+  qualifier_set quals {};
 
   if ((qualifiers & GCC_QUALIFIER_CONST) != 0)
     quals |= TYPE_QUAL_CONST;
diff --git a/libcc1/libcp1plugin.cc b/libcc1/libcp1plugin.cc
index e62c6ef9b9bd..b34a546df576 100644
--- a/libcc1/libcp1plugin.cc
+++ b/libcc1/libcp1plugin.cc
@@ -1999,7 +1999,7 @@ plugin_build_method_type (cc1_plugin::connection *self,
 {
   tree class_type = convert_in (class_type_in);
   tree func_type = convert_in (func_type_in);
-  cp_cv_quals quals = 0;
+  cp_cv_quals quals {};
   cp_ref_qualifier rquals;
 
   if ((quals_in & GCC_CP_QUALIFIER_CONST) != 0)
@@ -3401,7 +3401,7 @@ plugin_build_qualified_type (cc1_plugin::connection *,
 			     enum gcc_cp_qualifiers qualifiers)
 {
   tree unqualified_type = convert_in (unqualified_type_in);
-  cp_cv_quals quals = 0;
+  cp_cv_quals quals = {};
 
   if ((qualifiers & GCC_CP_QUALIFIER_CONST) != 0)
     quals |= TYPE_QUAL_CONST;
-- 
2.54.0