Linux SPARSE checker discussions
 help / color / mirror / Atom feed
From: Bart Van Assche <bvanassche@acm.org>
To: linux-sparse@vger.kernel.org
Cc: Linus Torvalds <torvalds@linux-foundation.org>,
	Bart Van Assche <bvanassche@acm.org>
Subject: [PATCH 61/73] c23: Add support for _BitInt(N)
Date: Mon,  9 Nov 2026 12:32:47 -0800	[thread overview]
Message-ID: <20261109203259.362845-62-bvanassche@acm.org> (raw)
In-Reply-To: <20261109203259.362845-1-bvanassche@acm.org>

Implement ISO C23 bit-precise integer types _BitInt(N) and unsigned
_BitInt(N) up to __BITINT_MAXWIDTH__ (128):
- Parse _BitInt(N) specifiers, validate signed (N >= 2) and unsigned
  (N >= 1) width bounds, and parse case-matching wb/WB and uwb/UWB
  integer constant suffixes while rejecting mixed-case wB/Wb.
- Keep _BitInt(N) unpromoted during integer promotions per C23
  6.3.1.1p2 and implement C23 6.3.1.8 usual arithmetic conversions
  between _BitInt(N) and standard integer types.
- Mask and sign-extend constant expressions to effective_int_width()
  and store the upper 64 bits in struct expression (value_hi) so
  65..128-bit constants parse, fold, and compare with full precision.
- Enforce _BitInt bit-field width/sign rules, reject string-literal
  array initialization of _BitInt(8)[], and check pointer width/rank
  compatibility.

Signed-off-by: Bart Van Assche <bvanassche@acm.org>
---
 evaluate.c                       | 105 ++++++++-
 expand.c                         | 366 +++++++++++++++++++++++++++----
 expression.c                     | 103 ++++++++-
 expression.h                     |   2 +
 ident-list.h                     |   1 +
 linearize.c                      |  23 +-
 parse.c                          |  89 +++++++-
 predefine.c                      |   1 +
 show-parse.c                     |   9 +
 sparse-llvm.c                    |   8 +
 symbol.c                         |  54 ++++-
 symbol.h                         |  61 +++++-
 validation/c23-bitint-error.c    |  61 ++++++
 validation/c23-bitint-pedantic.c |  13 ++
 validation/c23-bitint.c          | 214 ++++++++++++++++++
 validation/expand-bitint.c       |  49 +++++
 16 files changed, 1086 insertions(+), 73 deletions(-)
 create mode 100644 validation/c23-bitint-error.c
 create mode 100644 validation/c23-bitint-pedantic.c
 create mode 100644 validation/c23-bitint.c
 create mode 100644 validation/expand-bitint.c

diff --git a/evaluate.c b/evaluate.c
index 32621d04a17e..1c169ad60382 100644
--- a/evaluate.c
+++ b/evaluate.c
@@ -187,6 +187,8 @@ static inline struct symbol *integer_promotion(struct symbol *type)
 		type = type->ctype.base_type;
 	}
 	mod = type->ctype.modifiers;
+	if (is_bitint_type(type))
+		return type;
 	if (width < bits_in_int)
 		return &int_ctype;
 
@@ -229,6 +231,43 @@ static struct symbol *bigger_int_type(struct symbol *left, struct symbol *right)
 	if (left == right)
 		return left;
 
+	if (is_bitint_type(left) || is_bitint_type(right)) {
+		int lwidth = effective_int_width(left);
+		int rwidth = effective_int_width(right);
+		int lunsigned = (left->ctype.modifiers & MOD_UNSIGNED) != 0;
+		int runsigned = (right->ctype.modifiers & MOD_UNSIGNED) != 0;
+		int uwidth, swidth;
+
+		if (lunsigned == runsigned) {
+			if (lwidth > rwidth)
+				return left;
+			if (rwidth > lwidth)
+				return right;
+			return is_bitint_type(left) ? right : left;
+		}
+
+		if (lunsigned) {
+			utype = left;
+			stype = right;
+			uwidth = lwidth;
+			swidth = rwidth;
+		} else {
+			utype = right;
+			stype = left;
+			uwidth = rwidth;
+			swidth = lwidth;
+		}
+
+		if (uwidth > swidth)
+			return utype;
+		if (uwidth == swidth) {
+			if (!is_bitint_type(utype) || is_bitint_type(stype))
+				return utype;
+			return unsigned_types[stype->rank];
+		}
+		return stype;
+	}
+
 	lmod = left->ctype.modifiers;
 	rmod = right->ctype.modifiers;
 	if (((lmod ^ rmod) & MOD_UNSIGNED) == 0)
@@ -879,8 +918,15 @@ const char *type_difference(struct ctype *c1, struct ctype *c2,
 				return "different base types";
 			if (!base1 && t1->bit_size != t2->bit_size)
 				return "different base types";
-			if (t1->rank != t2->rank)
-				return "different type sizes";
+			if (is_bitint_type(t1) || is_bitint_type(t2)) {
+				if (!is_bitint_type(t1) || !is_bitint_type(t2))
+					return "different base types";
+				if (t1->bitint_width != t2->bitint_width)
+					return "different bit widths";
+			} else {
+				if (t1->rank != t2->rank)
+					return "different type sizes";
+			}
 			diff = (mod1 ^ mod2) & ~MOD_IGNORE;
 			if (!diff)
 				return NULL;
@@ -1601,6 +1647,13 @@ static int whitelist_pointers(struct symbol *t1, struct symbol *t2)
 		return 0;
 	if (t1 == t2)
 		return 1;
+	if (is_bitint_type(t1) || is_bitint_type(t2)) {
+		if (!is_bitint_type(t1) || !is_bitint_type(t2))
+			return 0;
+		if (t1->bitint_width != t2->bitint_width)
+			return 0;
+		return !Wtypesign;
+	}
 	if (t1->rank == -2 && t2->rank == -2)
 		return !Wtypesign && !Wpedantic && standard < STANDARD_C23;
 	if (t1->rank != t2->rank)
@@ -3101,19 +3154,49 @@ static void check_constexpr_scalar_init(struct expression *orig,
 				     "non-constant initializer for constexpr object");
 			return;
 		}
-		sw = stype->bit_size;
+		sw = effective_int_width(stype);
 		s_unsigned = (stype->ctype.modifiers & MOD_UNSIGNED) != 0;
-		s_neg = !s_unsigned && sw > 0 && sw <= 64 &&
-			(tmp.value & sign_bit(sw)) != 0;
+		s_neg = !s_unsigned &&
+			(sw > 64 ? tmp.value_neg :
+				   (sw > 0 && (tmp.value & sign_bit(sw)) != 0));
 		if (is_bool_type(ttype)) {
-			if (s_neg || (tmp.value != 0 && tmp.value != 1))
+			if (s_neg || tmp.value_hi != 0 ||
+			    (tmp.value != 0 && tmp.value != 1))
 				sparse_error(
 					orig->pos,
 					"constexpr initializer not representable in target type");
 			return;
 		}
-		tw = ttype->bit_size;
+		tw = effective_int_width(ttype);
 		t_unsigned = (ttype->ctype.modifiers & MOD_UNSIGNED) != 0;
+#ifdef __SIZEOF_INT128__
+		if (s_neg) {
+			__int128 sval =
+				sw > 64 ?
+					(__int128)(((unsigned __int128)tmp.value_hi << 64) |
+						   tmp.value) :
+					(__int128)bits_extend(tmp.value, sw, 1);
+			if (t_unsigned ||
+			    (tw < 128 && sval < -((__int128)1 << (tw - 1))))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+		} else {
+			unsigned __int128 uval =
+				sw > 64 ?
+					(((unsigned __int128)tmp.value_hi << 64) |
+					 tmp.value) :
+					(unsigned __int128)(unsigned long long)zero_extend(
+						tmp.value, sw);
+			int max_bits = t_unsigned ? tw : (tw - 1);
+			if ((max_bits <= 0 && uval != 0) ||
+			    (max_bits > 0 && max_bits < 128 &&
+			     (uval >> max_bits) != 0))
+				sparse_error(
+					orig->pos,
+					"constexpr initializer not representable in target type");
+		}
+#else
 		if (s_neg) {
 			long long sval = sw > 64 ? (long long)tmp.value :
 						   bits_extend(tmp.value, sw, 1);
@@ -3134,6 +3217,7 @@ static void check_constexpr_scalar_init(struct expression *orig,
 					orig->pos,
 					"constexpr initializer not representable in target type");
 		}
+#endif
 		return;
 	}
 
@@ -3166,10 +3250,11 @@ static void check_constexpr_scalar_init(struct expression *orig,
 						"constexpr initializer not representable in target type");
 			}
 		} else if (is_int(sclass)) {
-			int sw = stype->bit_size;
+			int sw = effective_int_width(stype);
 			bool s_unsigned = (stype->ctype.modifiers & MOD_UNSIGNED) != 0;
-			bool s_neg = !s_unsigned && sw > 0 && sw <= 64 &&
-				     (tmp.value & sign_bit(sw)) != 0;
+			bool s_neg = !s_unsigned &&
+				     (sw > 64 ? tmp.value_neg :
+						(sw > 0 && (tmp.value & sign_bit(sw)) != 0));
 			if (tmp.type != EXPR_VALUE) {
 				sparse_error(
 					orig->pos,
diff --git a/expand.c b/expand.c
index 7c85b66ab02f..815ff24ac487 100644
--- a/expand.c
+++ b/expand.c
@@ -80,17 +80,85 @@ static int expand_symbol_expression(struct expression *expr)
 	return (sym->ctype.modifiers & (MOD_STATIC | MOD_EXTERN)) ? 2 : 1;
 }
 
+static inline int clamp_mask_bits(int bits)
+{
+	if (bits <= 0)
+		return 0;
+	if (bits > 64)
+		return 64;
+	return bits;
+}
+
+static bool expr_is_neg_int(const struct expression *e)
+{
+	int w = effective_int_width(e->ctype);
+
+	if (e->ctype->ctype.modifiers & MOD_UNSIGNED)
+		return false;
+	if (w > 64)
+		return e->value_neg;
+	return w > 0 && (e->value & sign_bit(w)) != 0;
+}
+
+#ifdef __SIZEOF_INT128__
+static unsigned __int128 get_uint128(const struct expression *e)
+{
+	int w = effective_int_width(e->ctype);
+
+	if (w > 64) {
+		if (e->value_neg && !e->value_hi)
+			return (unsigned __int128)(__int128)(long long)e->value;
+		return ((unsigned __int128)e->value_hi << 64) | e->value;
+	}
+	if (e->ctype->ctype.modifiers & MOD_UNSIGNED)
+		return (unsigned __int128)(unsigned long long)zero_extend(e->value, w);
+	return (unsigned __int128)(__int128)bits_extend(e->value, w, 1);
+}
+
+static bool store_int128(struct expression *expr, struct symbol *ctype,
+			 unsigned __int128 uval)
+{
+	int w = effective_int_width(ctype);
+	int is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
+	unsigned __int128 mask = w >= 128 ? ~(unsigned __int128)0 :
+					    (((unsigned __int128)1 << w) - 1);
+	unsigned long long lo, hi;
+	int neg;
+
+	uval &= mask;
+	if (is_signed && (uval & ((unsigned __int128)1 << (w - 1)))) {
+		uval |= ~mask;
+		neg = 1;
+	} else {
+		neg = 0;
+	}
+	lo = (unsigned long long)uval;
+	hi = (unsigned long long)(uval >> 64);
+	if (!is_bitint_type(ctype) &&
+	    (hi != (neg ? ~0ULL : 0ULL) || (neg && !(lo & (1ULL << 63)))))
+		return false;
+	expr->value = lo;
+	expr->value_hi = hi;
+	expr->value_neg = neg;
+	return true;
+}
+#endif
+
 static long long get_longlong(struct expression *expr)
 {
+	int w = effective_int_width(expr->ctype);
 	int is_signed = !(expr->ctype->ctype.modifiers & MOD_UNSIGNED);
-	return bits_extend(expr->value, expr->ctype->bit_size, is_signed);
+
+	if (w > 64)
+		return (long long)expr->value;
+	return bits_extend(expr->value, w, is_signed);
 }
 
 void cast_value(struct expression *expr, struct symbol *newtype, struct expression *old)
 {
 	struct symbol *oldtype = old->ctype;
-	int old_size = oldtype->bit_size;
-	int new_size = newtype->bit_size;
+	int old_size = effective_int_width(oldtype);
+	int new_size = effective_int_width(newtype);
 	long long value;
 	unsigned long long mask, signmask;
 	unsigned long long oldmask, oldsignmask, dropped;
@@ -98,28 +166,44 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	if (is_float_type(newtype) || is_float_type(oldtype))
 		goto Float;
 
-	if (!is_bool_type(newtype) && new_size > 64 && old_size >= 64 &&
-	    new_size > old_size && (oldtype->ctype.modifiers & MOD_UNSIGNED) &&
-	    (old->value & (1ULL << 63)))
+	if (!is_bool_type(newtype) && new_size > 64) {
+#ifdef __SIZEOF_INT128__
+		struct expression tmp = *expr;
+		if (!store_int128(&tmp, newtype, get_uint128(old)))
+			return;
+		expr->type = EXPR_VALUE;
+		expr->taint = old->taint;
+		expr->ctype = newtype;
+		expr->value = tmp.value;
+		expr->value_hi = tmp.value_hi;
+		expr->value_neg = tmp.value_neg;
+#endif
 		return;
+	}
 
 	// For pointers and integers, we can just move the value around
 	expr->type = EXPR_VALUE;
 	expr->taint = old->taint;
-	if (old_size == new_size && !is_bool_type(newtype)) {
-		expr->value = old->value;
+	if (old_size == new_size && old_size <= 64 && !is_bool_type(newtype)) {
+		expr->value = old->value & bits_mask(new_size);
+		expr->value_hi = 0;
+		expr->value_neg = 0;
 		expr->ctype = newtype;
 		return;
 	}
 
 	if (is_bool_type(newtype)) {
-		bool non_zero = old->value != 0;
-		bool is_one = old->value == 1;
+		bool non_zero = old->value != 0 || old->value_hi != 0;
+		bool is_one = old->value == 1 && old->value_hi == 0 &&
+			      !expr_is_neg_int(old);
+		unsigned long long oval = old->value;
 		expr->ctype = newtype;
 		expr->value = non_zero;
+		expr->value_hi = 0;
+		expr->value_neg = 0;
 		if (!conservative && non_zero && !is_one)
 			warning(old->pos, "odd constant _Bool cast (%llx becomes 1)",
-				old->value);
+				oval);
 		return;
 	}
 
@@ -128,6 +212,8 @@ void cast_value(struct expression *expr, struct symbol *newtype, struct expressi
 	expr->ctype = newtype;
 
 Int:
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	// _Bool requires a zero test rather than truncation.
 	if (is_bool_type(newtype)) {
 		expr->value = !!value;
@@ -136,13 +222,28 @@ Int:
 		return;
 	}
 
+	if (new_size <= 0) {
+		expr->value = 0;
+		return;
+	}
+
+	if (new_size > 64) {
+		expr->value = value;
+		expr->value_neg = !(newtype->ctype.modifiers & MOD_UNSIGNED) &&
+				  value < 0;
+		expr->value_hi = expr->value_neg ? ~0ULL : 0ULL;
+		return;
+	}
+
 	// Truncate it to the new size
 	signmask = sign_bit(new_size);
 	mask = bits_mask(new_size);
 	expr->value = value & mask;
 
 	// Stop here unless checking for truncation
-	if (!Wcast_truncate || conservative)
+	if (!Wcast_truncate || conservative || old_size <= 0)
+		return;
+	if (old_size > 64 && !old->value_neg && !old->value_hi && new_size == 64)
 		return;
 	
 	// Check if we dropped any bits..
@@ -168,6 +269,8 @@ Float:
 			expr->taint = 0;
 			expr->ctype = newtype;
 			expr->value = (fval != 0);
+			expr->value_hi = 0;
+			expr->value_neg = 0;
 			return;
 		}
 		if (new_size <= 0 || new_size > 64 || !__builtin_isfinite(fval))
@@ -207,10 +310,19 @@ Float:
 	}
 
 	if (!is_float_type(oldtype)) {
-		if ((oldtype->ctype.modifiers & MOD_UNSIGNED) &&
-		    old_size > 64 && (old->value & (1ULL << 63)))
-			return;
-		if (oldtype->ctype.modifiers & MOD_UNSIGNED)
+		if (old_size > 64) {
+#ifdef __SIZEOF_INT128__
+			if (old->value_neg)
+				expr->fvalue = (long double)(__int128)get_uint128(old);
+			else
+				expr->fvalue = (long double)get_uint128(old);
+#else
+			if (old->value_neg)
+				expr->fvalue = (long double)(long long)old->value;
+			else
+				expr->fvalue = (long double)(unsigned long long)old->value;
+#endif
+		} else if (oldtype->ctype.modifiers & MOD_UNSIGNED)
 			expr->fvalue = (long double)(unsigned long long)get_longlong(old);
 		else
 			expr->fvalue = (long double)get_longlong(old);
@@ -231,10 +343,12 @@ Float:
 static bool check_shift_count(struct expression *expr, struct expression *right)
 {
 	struct symbol *ctype = expr->ctype;
-	long long count = get_longlong(right);
+	int width = effective_int_width(ctype);
 
-	if (count >= 0 && count < ctype->bit_size)
-		return true;
+	if (expr_is_neg_int(right) || right->value_hi != 0)
+		return false;
+	if (right->value < (unsigned long long)width && right->value < 128)
+		return width > 64 || right->value < 64;
 	return false;
 }
 
@@ -250,7 +364,7 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	struct expression *left = expr->left, *right = expr->right;
 	unsigned long long v, l, r, mask;
 	signed long long sl, sr;
-	int is_signed;
+	int is_signed, bits, raw_bits;
 
 	if (right->type != EXPR_VALUE)
 		return 0;
@@ -261,11 +375,92 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	}
 	if (left->type != EXPR_VALUE)
 		return 0;
-	if (!ctype || ctype->bit_size <= 0 || ctype->bit_size > 64)
+	raw_bits = effective_int_width(ctype);
+	if (raw_bits <= 0)
 		return 0;
-	l = left->value; r = right->value;
 	is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
-	mask = sign_bit(ctype->bit_size);
+	if (raw_bits > 64) {
+#ifdef __SIZEOF_INT128__
+		unsigned __int128 ul = get_uint128(left);
+		unsigned __int128 ur = get_uint128(right);
+		__int128 sl128 = (__int128)ul, sr128 = (__int128)ur;
+		unsigned __int128 sbit128 = (unsigned __int128)1 << (raw_bits - 1);
+		unsigned __int128 ures;
+
+		switch (CONVERT(expr->op, is_signed)) {
+		case SIGNED('+'):
+		case UNSIGNED('+'):
+			ures = ul + ur;
+			break;
+		case SIGNED('-'):
+		case UNSIGNED('-'):
+			ures = ul - ur;
+			break;
+		case SIGNED('&'):
+		case UNSIGNED('&'):
+			ures = ul & ur;
+			break;
+		case SIGNED('|'):
+		case UNSIGNED('|'):
+			ures = ul | ur;
+			break;
+		case SIGNED('^'):
+		case UNSIGNED('^'):
+			ures = ul ^ ur;
+			break;
+		case SIGNED('*'):
+		case UNSIGNED('*'):
+			ures = ul * ur;
+			break;
+		case SIGNED('/'):
+			if (!ur)
+				goto Div;
+			if ((ul & ((sbit128 << 1) - 1)) == sbit128 && sr128 == -1)
+				goto Overflow;
+			ures = (unsigned __int128)(sl128 / sr128);
+			break;
+		case UNSIGNED('/'):
+			if (!ur)
+				goto Div;
+			ures = ul / ur;
+			break;
+		case SIGNED('%'):
+			if (!ur)
+				goto Div;
+			if ((ul & ((sbit128 << 1) - 1)) == sbit128 && sr128 == -1)
+				goto Overflow;
+			ures = (unsigned __int128)(sl128 % sr128);
+			break;
+		case UNSIGNED('%'):
+			if (!ur)
+				goto Div;
+			ures = ul % ur;
+			break;
+		case SIGNED(SPECIAL_LEFTSHIFT):
+		case UNSIGNED(SPECIAL_LEFTSHIFT):
+			ures = r < 128 ? (ul << r) : 0;
+			break;
+		case SIGNED(SPECIAL_RIGHTSHIFT):
+			ures = (unsigned __int128)(sl128 >> (r < 127 ? r : 127));
+			break;
+		case UNSIGNED(SPECIAL_RIGHTSHIFT):
+			ures = r < 128 ? (ul >> r) : 0;
+			break;
+		default:
+			return 0;
+		}
+		if (!store_int128(expr, ctype, ures))
+			return 0;
+		expr->type = EXPR_VALUE;
+		expr->taint = left->taint | right->taint;
+		return 1;
+#else
+		return 0;
+#endif
+	}
+	bits = clamp_mask_bits(raw_bits);
+	l = left->value; r = right->value;
+	mask = sign_bit(bits);
 	sl = l; sr = r;
 	if (is_signed && (sl & mask))
 		sl |= ~(mask-1);
@@ -345,8 +540,10 @@ static int simplify_int_binop(struct expression *expr, struct symbol *ctype)
 	default:
 		return 0;
 	}
-	mask = bits_mask(ctype->bit_size);
+	mask = bits_mask(bits);
 	expr->value = v & mask;
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = left->taint | right->taint;
 	return 1;
@@ -365,13 +562,43 @@ static int simplify_cmp_binop(struct expression *expr, struct symbol *ctype)
 	struct expression *left = expr->left, *right = expr->right;
 	unsigned long long l, r, mask;
 	signed long long sl, sr;
+	int bits, raw_bits;
 
 	if (left->type != EXPR_VALUE || right->type != EXPR_VALUE)
 		return 0;
-	if (!ctype || ctype->bit_size <= 0 || ctype->bit_size > 64)
+	raw_bits = effective_int_width(ctype);
+	if (raw_bits <= 0)
 		return 0;
+	if (raw_bits > 64) {
+#ifdef __SIZEOF_INT128__
+		unsigned __int128 ul = get_uint128(left);
+		unsigned __int128 ur = get_uint128(right);
+		__int128 sl128 = (__int128)ul, sr128 = (__int128)ur;
+
+		switch (expr->op) {
+		case '<':		expr->value = sl128 < sr128; break;
+		case '>':		expr->value = sl128 > sr128; break;
+		case SPECIAL_LTE:	expr->value = sl128 <= sr128; break;
+		case SPECIAL_GTE:	expr->value = sl128 >= sr128; break;
+		case SPECIAL_EQUAL:	expr->value = ul == ur; break;
+		case SPECIAL_NOTEQUAL:	expr->value = ul != ur; break;
+		case SPECIAL_UNSIGNED_LT:expr->value = ul < ur; break;
+		case SPECIAL_UNSIGNED_GT:expr->value = ul > ur; break;
+		case SPECIAL_UNSIGNED_LTE:expr->value = ul <= ur; break;
+		case SPECIAL_UNSIGNED_GTE:expr->value = ul >= ur; break;
+		}
+		expr->value_hi = 0;
+		expr->value_neg = 0;
+		expr->type = EXPR_VALUE;
+		expr->taint = left->taint | right->taint;
+		return 1;
+#else
+		return 0;
+#endif
+	}
+	bits = clamp_mask_bits(raw_bits);
 	l = left->value; r = right->value;
-	mask = sign_bit(ctype->bit_size);
+	mask = sign_bit(bits);
 	sl = l; sr = r;
 	if (sl & mask)
 		sl |= ~(mask-1);
@@ -389,6 +616,8 @@ static int simplify_cmp_binop(struct expression *expr, struct symbol *ctype)
 	case SPECIAL_UNSIGNED_LTE:expr->value = l <= r; break;
 	case SPECIAL_UNSIGNED_GTE:expr->value = l >= r; break;
 	}
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = left->taint | right->taint;
 	return 1;
@@ -461,6 +690,8 @@ static int simplify_float_cmp(struct expression *expr, struct symbol *ctype)
 	case SPECIAL_EQUAL:	expr->value = l == r; break;
 	case SPECIAL_NOTEQUAL:	expr->value = l != r; break;
 	}
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = 0;
 	return 1;
@@ -489,16 +720,20 @@ static int expand_logical(struct expression *expr)
 	cost = expand_expression(left);
 	if (left->type == EXPR_VALUE) {
 		if (expr->op == SPECIAL_LOGICAL_AND) {
-			if (!left->value) {
+			if (!left->value && !left->value_hi) {
 				expr->type = EXPR_VALUE;
 				expr->value = 0;
+				expr->value_hi = 0;
+				expr->value_neg = 0;
 				expr->taint = left->taint;
 				return 0;
 			}
 		} else {
-			if (left->value) {
+			if (left->value || left->value_hi) {
 				expr->type = EXPR_VALUE;
 				expr->value = 1;
+				expr->value_hi = 0;
+				expr->value_neg = 0;
 				expr->taint = left->taint;
 				return 0;
 			}
@@ -513,7 +748,9 @@ static int expand_logical(struct expression *expr)
 		 * otherwise we would have short-circuited it..
 		 */
 		expr->type = EXPR_VALUE;
-		expr->value = right->value != 0;
+		expr->value = (right->value != 0 || right->value_hi != 0);
+		expr->value_hi = 0;
+		expr->value_neg = 0;
 		expr->taint = left->taint | right->taint;
 		return 0;
 	}
@@ -586,6 +823,8 @@ static int expand_compare(struct expression *expr)
 			int op = expr->op;
 			expr->type = EXPR_VALUE;
 			expr->value = compare_types(op, left->symbol, right->symbol);
+			expr->value_hi = 0;
+			expr->value_neg = 0;
 			expr->taint = 0;
 			return 0;
 		}
@@ -607,7 +846,7 @@ static int expand_conditional(struct expression *expr)
 	cond_cost = expand_expression(cond);
 	if (cond->type == EXPR_VALUE) {
 		unsigned flags = expr->flags;
-		if (!cond->value)
+		if (!cond->value && !cond->value_hi)
 			valt = valf;
 		if (!valt)
 			valt = cond;
@@ -838,7 +1077,8 @@ static int expand_dereference(struct expression *expr)
 
 		/* Const symbol with a constant initializer? */
 		if (value && value->ctype) {
-			if (ctype->bit_size != value->ctype->bit_size)
+			if (ctype->bit_size != value->ctype->bit_size ||
+			    effective_int_width(ctype) != effective_int_width(value->ctype))
 				return UNSAFE;
 			if (value->type == EXPR_VALUE) {
 				if (!is_integral_type(ctype))
@@ -847,6 +1087,8 @@ static int expand_dereference(struct expression *expr)
 					return UNSAFE;
 				expr->type = EXPR_VALUE;
 				expr->value = value->value;
+				expr->value_hi = value->value_hi;
+				expr->value_neg = value->value_neg;
 				expr->taint = 0;
 				if (sym->ctype.modifiers & MOD_CONSTEXPR)
 					expr->flags = is_int_type(ctype) ? CEF_SET_ICE : CEF_ADDR;
@@ -882,13 +1124,48 @@ int early_simplify_preop(struct expression *expr)
 {
 	struct expression *op = expr->unop;
 	unsigned long long v, mask;
+	int bits, raw_bits;
 
 	if (op->type != EXPR_VALUE)
 		return 0;
-	if (!expr->ctype || expr->ctype->bit_size <= 0 || expr->ctype->bit_size > 64)
+
+	raw_bits = effective_int_width(expr->ctype);
+	if (raw_bits <= 0)
 		return 0;
+	if (raw_bits > 64) {
+#ifdef __SIZEOF_INT128__
+		unsigned __int128 u = get_uint128(op);
 
-	mask = sign_bit(expr->ctype->bit_size);
+		switch (expr->op) {
+		case '+':
+			break;
+		case '!':
+			expr->value = (u == 0);
+			expr->value_hi = 0;
+			expr->value_neg = 0;
+			expr->type = EXPR_VALUE;
+			expr->taint = op->taint;
+			return 1;
+		case '-':
+			u = -u;
+			break;
+		case '~':
+			u = ~u;
+			break;
+		default:
+			return 0;
+		}
+		if (!store_int128(expr, expr->ctype, u))
+			return 0;
+		expr->type = EXPR_VALUE;
+		expr->taint = op->taint;
+		return 1;
+#else
+		return 0;
+#endif
+	}
+	bits = clamp_mask_bits(raw_bits);
+	mask = sign_bit(bits);
 	v = op->value;
 	switch (expr->op) {
 	case '+': break;
@@ -901,8 +1178,10 @@ int early_simplify_preop(struct expression *expr)
 	case '~': v = ~v; break;
 	default: return 0;
 	}
-	mask = bits_mask(expr->ctype->bit_size);
+	mask = bits_mask(bits);
 	expr->value = v & mask;
+	expr->value_hi = 0;
+	expr->value_neg = 0;
 	expr->type = EXPR_VALUE;
 	expr->taint = op->taint;
 	return 1;
@@ -998,6 +1277,11 @@ static int expand_cast(struct expression *expr)
 
 	/* Simplify normal integer casts.. */
 	if (target->type == EXPR_VALUE || target->type == EXPR_FVALUE) {
+		if (target->type == EXPR_VALUE &&
+		    effective_int_width(expr->ctype) > 64 &&
+		    (expr->ctype->ctype.modifiers & MOD_UNSIGNED) &&
+		    expr_is_neg_int(target))
+			return cost + 1;
 		cast_value(expr, expr->ctype, target);
 		if (expr->type == EXPR_VALUE || expr->type == EXPR_FVALUE)
 			return 0;
@@ -1508,6 +1792,7 @@ static long long __get_expression_value(struct expression *expr, int strict)
 	long long value;
 	unsigned long long mask;
 	struct symbol *ctype;
+	int bits;
 
 	if (!expr)
 		return 0;
@@ -1528,13 +1813,18 @@ static long long __get_expression_value(struct expression *expr, int strict)
 	}
 
 	value = expr->value;
-	mask = sign_bit(ctype->bit_size);
+	if (effective_int_width(ctype) > 64)
+		return value;
+	bits = clamp_mask_bits(effective_int_width(ctype));
+	if (!bits)
+		return value;
+	mask = sign_bit(bits);
 
 	if (value & mask) {
 		while (ctype->type != SYM_BASETYPE)
 			ctype = ctype->ctype.base_type;
 		if (!(ctype->ctype.modifiers & MOD_UNSIGNED))
-			value = value | mask | ~(mask-1);
+			value = value | mask | ~(mask - 1);
 	}
 	return value;
 }
@@ -1577,7 +1867,7 @@ redo:
 		expr = expr->right;
 		goto redo;
 	case EXPR_VALUE:
-		return expr->value != 0;
+		return expr->value != 0 || expr->value_hi != 0;
 	case EXPR_FVALUE:
 		return expr->fvalue != 0;
 	default:
@@ -1591,7 +1881,7 @@ int is_zero_constant(struct expression *expr)
 	conservative = 1;
 	expand_expression(expr);
 	conservative = saved;
-	return expr->type == EXPR_VALUE && !expr->value;
+	return expr->type == EXPR_VALUE && !expr->value && !expr->value_hi;
 }
 
 void expand_constexpr_initializer(struct expression *expr)
diff --git a/expression.c b/expression.c
index 65efff3b61fe..15b9eca351cb 100644
--- a/expression.c
+++ b/expression.c
@@ -260,6 +260,49 @@ static unsigned long long parse_num(const char *nptr, char **end)
 	return strtoull(nptr, end, 0);
 }
 
+#ifdef __SIZEOF_INT128__
+static bool parse_num128(const char *nptr, unsigned long long *lo,
+			 unsigned long long *hi)
+{
+	unsigned __int128 val = 0;
+	int base = 10;
+	const char *p = nptr;
+
+	if (p[0] == '0') {
+		if (tolower((unsigned char)p[1]) == 'x') {
+			base = 16;
+			p += 2;
+		} else if (tolower((unsigned char)p[1]) == 'b') {
+			base = 2;
+			p += 2;
+		} else {
+			base = 8;
+		}
+	}
+	for (; *p; p++) {
+		unsigned int d;
+		unsigned char c = *p;
+
+		if (c >= '0' && c <= '9')
+			d = c - '0';
+		else if (c >= 'a' && c <= 'f')
+			d = c - 'a' + 10;
+		else if (c >= 'A' && c <= 'F')
+			d = c - 'A' + 10;
+		else
+			break;
+		if (d >= (unsigned int)base)
+			break;
+		if (val > (~(unsigned __int128)0 - d) / base)
+			return false;
+		val = val * base + d;
+	}
+	*lo = (unsigned long long)val;
+	*hi = (unsigned long long)(val >> 64);
+	return true;
+}
+#endif
+
 static int is_digit_for_base(int c, int base)
 {
 	if (base == 2)
@@ -417,6 +460,7 @@ static void get_number_value(struct expression *expr, struct token *token)
 	unsigned long long value;
 	char *end;
 	int size = 0, want_unsigned = 0;
+	int is_bitint = 0;
 	int overflow = 0, do_warn = 0;
 	int try_unsigned = 1;
 	int bits;
@@ -444,12 +488,19 @@ static void get_number_value(struct expression *expr, struct token *token)
 		char c = *end++;
 		if (!c) {
 			break;
+		} else if (c == 'w' || c == 'W') {
+			if (is_bitint || size)
+				goto Enoint;
+			if (*end != (c == 'w' ? 'b' : 'B'))
+				goto Enoint;
+			end++;
+			is_bitint = 1;
 		} else if (c == 'u' || c == 'U') {
 			if (want_unsigned)
 				goto Enoint;
 			want_unsigned = 1;
 		} else if (c == 'l' || c == 'L') {
-			if (size)
+			if (size || is_bitint)
 				goto Enoint;
 			size = 1;
 			if (*end == c) {
@@ -463,6 +514,56 @@ static void get_number_value(struct expression *expr, struct token *token)
 			goto Enoint;
 		}
 	}
+	if (is_bitint) {
+		unsigned long long value_hi = 0;
+		int width;
+
+		if (overflow) {
+#ifdef __SIZEOF_INT128__
+			if (!parse_num128(str, &value, &value_hi))
+				goto Ebitint_overflow;
+#else
+			goto Ebitint_overflow;
+#endif
+		}
+		if (standard < STANDARD_C23 && Wpedantic)
+			warning(expr->pos,
+				"bit-precise integer suffix is a C23 feature");
+		if (value_hi != 0) {
+			int mag_bits = 128 - __builtin_clzll(value_hi);
+
+			width = want_unsigned ? mag_bits : (mag_bits + 1);
+			if (width > bits_in_longlonglong)
+				goto Ebitint_overflow;
+		} else if (want_unsigned) {
+			if (value == 0)
+				width = 1;
+			else
+				width = 64 - __builtin_clzll(value);
+		} else {
+			if (value == 0)
+				width = 2;
+			else
+				width = 64 - __builtin_clzll(value) + 1;
+		}
+		expr->type = EXPR_VALUE;
+		expr->flags = CEF_SET_INT;
+		expr->ctype = get_bitint_type(width, !want_unsigned);
+		expr->value = value;
+		expr->value_hi = value_hi;
+		return;
+Ebitint_overflow:
+		sparse_error(expr->pos,
+			     "constant %s is too big for %s_BitInt(%d)",
+			     show_token(token),
+			     want_unsigned ? "unsigned " : "",
+			     bits_in_longlonglong);
+		expr->type = EXPR_VALUE;
+		expr->flags = CEF_SET_ICE;
+		expr->ctype = get_bitint_type(bits_in_longlonglong,
+					      !want_unsigned);
+		return;
+	}
 	if (overflow)
 		goto Eoverflow;
 	/* OK, it's a valid integer */
diff --git a/expression.h b/expression.h
index 213c31d246aa..83857cffc7dc 100644
--- a/expression.h
+++ b/expression.h
@@ -160,6 +160,7 @@ struct expression {
 	enum expression_type type:8;
 	unsigned flags:8;
 	unsigned zero_init:1;
+	unsigned value_neg:1;
 	int op;
 	struct position pos;
 	struct symbol *ctype;
@@ -167,6 +168,7 @@ struct expression {
 		// EXPR_VALUE
 		struct {
 			unsigned long long value;
+			unsigned long long value_hi;
 			unsigned taint;
 		};
 
diff --git a/ident-list.h b/ident-list.h
index b4e01e0a423c..4427527580ee 100644
--- a/ident-list.h
+++ b/ident-list.h
@@ -44,6 +44,7 @@ IDENT_RESERVED(_Static_assert);
 IDENT(_Thread_local);
 
 /* C23 keywords */
+IDENT(_BitInt);
 IDENT(alignas);
 IDENT(alignof);
 IDENT(bool);
diff --git a/linearize.c b/linearize.c
index 268466f07886..f43502577649 100644
--- a/linearize.c
+++ b/linearize.c
@@ -1824,7 +1824,7 @@ static pseudo_t linearize_cond_branch(struct entrypoint *ep, struct expression *
 
 	case EXPR_STRING:
 	case EXPR_VALUE:
-		add_goto(ep, expr->value ? bb_true : bb_false);
+		add_goto(ep, (expr->value || expr->value_hi) ? bb_true : bb_false);
 		break;
 	case EXPR_FVALUE:
 		add_goto(ep, expr->fvalue ? bb_true : bb_false);
@@ -1920,6 +1920,27 @@ static pseudo_t linearize_expression(struct entrypoint *ep, struct expression *e
 		return add_symbol_address(ep, expr);
 
 	case EXPR_VALUE:
+		if (effective_int_width(expr->ctype) > 64) {
+			if (expr->value_hi != (expr->value_neg ? ~0ULL : 0ULL) ||
+			    (expr->value_neg && !(expr->value & (1ULL << 63)))) {
+				pseudo_t hi = cast_pseudo(
+					ep, value_pseudo(expr->value_hi),
+					&ullong_ctype, expr->ctype);
+				hi = add_binary_op(ep, expr->ctype, OP_SHL, hi,
+						   value_pseudo(64));
+				if (expr->value) {
+					pseudo_t lo = cast_pseudo(
+						ep, value_pseudo(expr->value),
+						&ullong_ctype, expr->ctype);
+					hi = add_binary_op(ep, expr->ctype,
+							   OP_OR, hi, lo);
+				}
+				return hi;
+			}
+			if (!expr->value_neg && (expr->value & (1ULL << 63)))
+				return cast_pseudo(ep, value_pseudo(expr->value),
+						   &ullong_ctype, expr->ctype);
+		}
 		return value_pseudo(expr->value);
 
 	case EXPR_STRING:
diff --git a/parse.c b/parse.c
index 0787ad2af576..d89f60113152 100644
--- a/parse.c
+++ b/parse.c
@@ -66,6 +66,7 @@ static declarator_t
 	storage_specifier, thread_specifier;
 static declarator_t generic_qualifier;
 static declarator_t autotype_specifier, auto_specifier, constexpr_specifier;
+static declarator_t bitint_specifier;
 
 static struct token *parse_if_statement(struct token *token, struct statement *stmt);
 static struct token *parse_return_statement(struct token *token, struct statement *stmt);
@@ -113,7 +114,9 @@ enum {
 	Set_Long = 512,
 	Set_Vlong = 1024,
 	Set_Int128 = 2048,
-	Set_Any = Set_T | Set_Short | Set_Long | Set_Signed | Set_Unsigned
+	Set_BitInt = 4096,
+	Set_Any = Set_T | Set_Short | Set_Long | Set_Signed | Set_Unsigned |
+		Set_BitInt
 };
 
 enum {
@@ -282,7 +285,8 @@ static struct symbol_op float_op = {
 
 static struct symbol_op short_op = {
 	.type = KW_SPECIFIER,
-	.test = Set_S|Set_Char|Set_Float|Set_Double|Set_Long|Set_Short,
+	.test = Set_S | Set_Char | Set_Float | Set_Double | Set_Long |
+		Set_Short | Set_BitInt,
 	.set = Set_Short,
 };
 
@@ -302,17 +306,27 @@ static struct symbol_op unsigned_op = {
 
 static struct symbol_op long_op = {
 	.type = KW_SPECIFIER,
-	.test = Set_S|Set_Char|Set_Float|Set_Short|Set_Vlong,
+	.test = Set_S | Set_Char | Set_Float | Set_Short | Set_Vlong |
+		Set_BitInt,
 	.set = Set_Long,
 };
 
 static struct symbol_op int128_op = {
 	.type = KW_SPECIFIER,
-	.test = Set_S|Set_T|Set_Char|Set_Short|Set_Int|Set_Float|Set_Double|Set_Long|Set_Vlong|Set_Int128,
-	.set =  Set_T|Set_Int128|Set_Vlong,
+	.test = Set_S | Set_T | Set_Char | Set_Short | Set_Int | Set_Float |
+		Set_Double | Set_Long | Set_Vlong | Set_Int128 | Set_BitInt,
+	.set = Set_T | Set_Int128 | Set_Vlong,
 	.class = CInt,
 };
 
+static struct symbol_op bitint_op = {
+	.type = KW_SPECIFIER,
+	.test = Set_S | Set_T | Set_Char | Set_Short | Set_Int | Set_Float |
+		Set_Double | Set_Long | Set_Vlong | Set_Int128 | Set_BitInt,
+	.set = Set_T | Set_BitInt,
+	.declarator = bitint_specifier,
+};
+
 static struct symbol_op if_op = {
 	.statement = parse_if_statement,
 };
@@ -531,6 +545,7 @@ static struct init_keyword {
 	N("__int128",		&int128_op),
 	N("_Bool",		&spec_op,	.type = &bool_ctype),
 	N("bool",		&spec_op,	.type = &bool_ctype, .std = STANDARD_C23),
+	N("_BitInt",		&bitint_op),
 
 	/* Predeclared types */
 	N("__builtin_va_list",	&spec_op,	.type = &ptr_ctype),
@@ -843,7 +858,7 @@ static struct token *struct_union_enum_specifier(enum type type,
 			check_typename_storage(underlying);
 			underlying = examine_symbol_type(underlying);
 			if (!underlying || !is_int_type(underlying) ||
-			    is_enum_type(underlying)) {
+			    is_enum_type(underlying) || is_bitint_type(underlying)) {
 				sparse_error(token->pos,
 					     "invalid 'enum' underlying type");
 				underlying = &int_ctype;
@@ -927,7 +942,7 @@ static struct token *struct_union_enum_specifier(enum type type,
 			check_typename_storage(underlying);
 			underlying = examine_symbol_type(underlying);
 			if (!underlying || !is_int_type(underlying) ||
-			    is_enum_type(underlying)) {
+			    is_enum_type(underlying) || is_bitint_type(underlying)) {
 				sparse_error(token->pos,
 					     "invalid 'enum' underlying type");
 				underlying = &int_ctype;
@@ -1421,6 +1436,32 @@ static void apply_constexpr_const(struct symbol *sym)
 	}
 }
 
+static struct token *bitint_specifier(struct token *token, struct symbol *sym,
+				      struct decl_state *ctx)
+{
+	struct expression *expr = NULL;
+
+	if (standard < STANDARD_C23 && Wpedantic)
+		warning(token->pos, "'_BitInt' is a C23 feature");
+
+	ctx->bitint_pos = token->pos;
+	if (!match_op(token, '(')) {
+		sparse_error(token->pos, "expected '(' after '_BitInt'");
+		ctx->bitint_width = -1;
+		return token;
+	}
+	token = conditional_expression(token->next, &expr);
+	if (!expr) {
+		sparse_error(token->pos,
+			     "expected expression for _BitInt width");
+		ctx->bitint_width = -1;
+	} else {
+		ctx->bitint_width = const_expression_value(expr);
+	}
+	token = expect(token, ')', "after _BitInt width");
+	return token;
+}
+
 static struct token *ignore_attribute(struct token *token, struct symbol *attr, struct decl_state *ctx)
 {
 	struct expression *expr = NULL;
@@ -1837,7 +1878,7 @@ static void apply_ctype(struct position pos, struct ctype *dst, struct ctype *sr
 static void specifier_conflict(struct position pos, int what, struct ident *new)
 {
 	const char *old;
-	if (what & (Set_S | Set_T))
+	if (what & (Set_S | Set_T | Set_BitInt))
 		goto Catch_all;
 	if (what & Set_Char)
 		old = "char";
@@ -1984,6 +2025,37 @@ static struct token *declaration_specifiers(struct token *token, struct decl_sta
 		}
 	}
 
+	if (seen & Set_BitInt) {
+		int is_signed = !(seen & Set_Unsigned);
+		long long width = ctx->bitint_width;
+		struct position pos = ctx->bitint_pos;
+
+		if (width <= 0) {
+			sparse_error(pos, "invalid bit width %lld for _BitInt",
+				     width);
+			ctx->ctype.base_type = &bad_ctype;
+		} else if (is_signed && width < 2) {
+			sparse_error(pos,
+				     "signed _BitInt width must be at least 2");
+			ctx->ctype.base_type = &bad_ctype;
+		} else if (!is_signed && width < 1) {
+			sparse_error(
+				pos,
+				"unsigned _BitInt width must be at least 1");
+			ctx->ctype.base_type = &bad_ctype;
+		} else if (width > bits_in_longlonglong) {
+			sparse_error(
+				pos,
+				"_BitInt width %lld exceeds __BITINT_MAXWIDTH__ (%d)",
+				width, bits_in_longlonglong);
+			ctx->ctype.base_type = &bad_ctype;
+		} else {
+			ctx->ctype.base_type =
+				get_bitint_type(width, is_signed);
+		}
+		seen |= Set_S;
+	}
+
 	if (!(seen & Set_S)) {	/* not set explicitly? */
 		struct symbol *base = &incomplete_ctype;
 		if (seen & Set_Any)
@@ -2425,6 +2497,7 @@ static struct token *handle_bitfield(struct token *token, struct decl_state *ctx
 		}
 		if (Wdefault_bitfield_sign &&
 		    bitfield_type->type != SYM_ENUM &&
+		    !is_bitint_type(bitfield_type) &&
 		    !(bitfield_type->ctype.modifiers & MOD_EXPLICITLY_SIGNED) &&
 		    is_signed) {
 			// The sign of bitfields is unspecified by default.
diff --git a/predefine.c b/predefine.c
index 5639fc6862e3..3ead07003637 100644
--- a/predefine.c
+++ b/predefine.c
@@ -128,6 +128,7 @@ void predefined_macros(void)
 	predefine("__STDC_EMBED_EMPTY__", 1, "2");
 
 	predefine("__CHAR_BIT__", 1, "%d", bits_in_char);
+	predefine("__BITINT_MAXWIDTH__", 1, "%d", bits_in_longlonglong);
 	if (funsigned_char)
 		predefine("__CHAR_UNSIGNED__", 1, "1");
 
diff --git a/show-parse.c b/show-parse.c
index 0a277ef67aac..9ed95f141cfe 100644
--- a/show-parse.c
+++ b/show-parse.c
@@ -350,6 +350,15 @@ deeper:
 	if (!sym)
 		goto out;
 
+	if (sym->type == SYM_BASETYPE && is_bitint_type(sym)) {
+		if (sym->ctype.modifiers & MOD_UNSIGNED)
+			prepend(name, "unsigned _BitInt(%d)",
+				sym->bitint_width);
+		else
+			prepend(name, "_BitInt(%d)", sym->bitint_width);
+		goto out;
+	}
+
 	if ((typename = builtin_typename(sym))) {
 		if (name->start != name->end)
 			prepend(name, " ");
diff --git a/sparse-llvm.c b/sparse-llvm.c
index 7d15d7a70e54..b6a0b6ce9134 100644
--- a/sparse-llvm.c
+++ b/sparse-llvm.c
@@ -1270,6 +1270,14 @@ static LLVMValueRef expr_to_const_val(LLVMModuleRef module, struct symbol *sym,
 
 	switch (initializer->type) {
 	case EXPR_VALUE:
+		if (effective_int_width(initializer->ctype) > 64 &&
+		    LLVMGetTypeKind(dtype) == LLVMIntegerTypeKind) {
+			uint64_t words[2] = {
+				initializer->value,
+				initializer->value_hi,
+			};
+			return LLVMConstIntOfArbitraryPrecision(dtype, 2, words);
+		}
 		return constant_value(initializer->value, dtype);
 	case EXPR_FVALUE:
 		return LLVMConstReal(dtype, initializer->fvalue);
diff --git a/symbol.c b/symbol.c
index 270e91d9c480..721dd1e81ae5 100644
--- a/symbol.c
+++ b/symbol.c
@@ -389,7 +389,7 @@ static struct symbol *examine_bitfield_type(struct symbol *sym)
 
 	if (!base_type)
 		return sym;
-	if (sym->bit_size > base_type->bit_size) {
+	if (sym->bit_size > effective_int_width(base_type)) {
 		sparse_error(sym->pos, "bitfield '%s' is wider (%d) than its type (%s)",
 			show_ident(sym->ident), sym->bit_size, show_typename(base_type));
 		sym->bit_size = -1;
@@ -401,7 +401,8 @@ static struct symbol *examine_bitfield_type(struct symbol *sym)
 	modifiers = base_type->ctype.modifiers;
 
 	/* use -funsigned-bitfields to determine the sign if not explicit */
-	if (!(modifiers & MOD_EXPLICITLY_SIGNED) && funsigned_bitfields)
+	if (!(modifiers & MOD_EXPLICITLY_SIGNED) &&
+	    !is_bitint_type(base_type) && funsigned_bitfields)
 		modifiers = (modifiers & ~MOD_SIGNED) | MOD_UNSIGNED;
 	sym->ctype.modifiers |= modifiers & MOD_SIGNEDNESS;
 	return sym;
@@ -456,7 +457,7 @@ static int count_array_initializer(struct symbol *t, struct expression *expr)
 	 * on T - if it's a character type, we get the length of string literal
 	 * (including NUL), otherwise we have one element here.
 	 */
-	if (t->ctype.base_type == &int_type && t->rank == -2)
+	if (t->ctype.base_type == &int_type && t->rank == -2 && !is_bitint_type(t))
 		is_char = 1;
 	else if (t == wchar_ctype && is_wstring_expr(expr))
 		is_char = 1;
@@ -581,6 +582,7 @@ static struct symbol * examine_node_type(struct symbol *sym)
 	}
 	
 	sym->bit_size = bit_size;
+	sym->bitint_width = base_type->bitint_width;
 	sym->rank = base_type->rank;
 	return sym;
 }
@@ -1135,3 +1137,49 @@ void init_ctype(void)
 	const_wchar_ctype.ctype.alignment = wchar_ctype->ctype.alignment;
 	const_wchar_ctype.bit_size = wchar_ctype->bit_size;
 }
+
+struct symbol *get_bitint_type(int width, int is_signed)
+{
+	static struct symbol *bitint_types[2][129];
+	struct position pos = { .stream = 0 };
+	struct symbol *sym;
+	int u = !is_signed;
+
+	if (width < 1 || width > 128)
+		return &bad_ctype;
+
+	sym = bitint_types[u][width];
+	if (sym)
+		return sym;
+
+	sym = alloc_symbol(pos, SYM_BASETYPE);
+	sym->ctype.base_type = &int_type;
+	sym->ctype.modifiers = (is_signed ? MOD_SIGNED : MOD_UNSIGNED) |
+			       MOD_BITINT;
+	sym->bitint_width = width;
+
+	if (width <= 8) {
+		sym->bit_size = 8;
+		sym->ctype.alignment = 1;
+		sym->rank = -2;
+	} else if (width <= 16) {
+		sym->bit_size = 16;
+		sym->ctype.alignment = 2;
+		sym->rank = -1;
+	} else if (width <= 32) {
+		sym->bit_size = 32;
+		sym->ctype.alignment = 4;
+		sym->rank = 0;
+	} else if (width <= 64) {
+		sym->bit_size = 64;
+		sym->ctype.alignment = 8;
+		sym->rank = 2;
+	} else {
+		sym->bit_size = 128;
+		sym->ctype.alignment = 8;
+		sym->rank = 3;
+	}
+
+	bitint_types[u][width] = sym;
+	return sym;
+}
diff --git a/symbol.h b/symbol.h
index 435fa18a4990..bb063dc622e0 100644
--- a/symbol.h
+++ b/symbol.h
@@ -122,6 +122,8 @@ struct decl_state {
 	unsigned char designated_init;
 	unsigned char transparent_union;
 	unsigned char opaque_fixed_enum;
+	long long bitint_width;
+	struct position bitint_pos;
 	struct position opaque_fixed_enum_pos;
 };
 
@@ -189,14 +191,25 @@ struct symbol {
 		struct /* NS_SYMBOL */ {
 			unsigned long	offset;
 			int		bit_size;
-			unsigned int bit_offset : 8, bogus_linear : 1,
-				variadic : 1, initialized : 1, examined : 1,
-				expanding : 1, evaluated : 1,
-				has_flex_array : 1, string : 1,
-				designated_init : 1, forced_arg : 1,
-				accessed : 1, builtin : 1, torename : 1,
-				packed : 1, transparent_union : 1,
-				fixed_type : 1, explicit_typedef : 1;
+			int bitint_width;
+			unsigned int bit_offset : 8;
+			unsigned int bogus_linear : 1;
+			unsigned int variadic : 1;
+			unsigned int initialized : 1;
+			unsigned int examined : 1;
+			unsigned int expanding : 1;
+			unsigned int evaluated : 1;
+			unsigned int has_flex_array : 1;
+			unsigned int string : 1;
+			unsigned int designated_init : 1;
+			unsigned int forced_arg : 1;
+			unsigned int accessed : 1;
+			unsigned int builtin : 1;
+			unsigned int torename : 1;
+			unsigned int packed : 1;
+			unsigned int transparent_union : 1;
+			unsigned int fixed_type : 1;
+			unsigned int explicit_typedef : 1;
 			unsigned int has_alignas : 1;
 			unsigned int kr_args : 1;
 			unsigned int underspecified : 1;
@@ -252,7 +265,7 @@ struct symbol {
 #define MOD_GNU_INLINE		0x00010000
 #define MOD_USERTYPE		0x00020000
 #define MOD_CONSTEXPR 0x00040000
-// MOD UNUSED		0x00080000
+#define MOD_BITINT 0x00080000
 // MOD UNUSED		0x00100000
 // MOD UNUSED		0x00200000
 
@@ -271,7 +284,7 @@ struct symbol {
 #define MOD_STORAGE	(MOD_AUTO | MOD_REGISTER | MOD_STATIC | MOD_EXTERN | MOD_INLINE | MOD_TOPLEVEL)
 #define MOD_ESIGNED	(MOD_SIGNED | MOD_EXPLICITLY_SIGNED)
 #define MOD_SIGNEDNESS	(MOD_SIGNED | MOD_UNSIGNED | MOD_EXPLICITLY_SIGNED)
-#define MOD_SPECIFIER	MOD_SIGNEDNESS
+#define MOD_SPECIFIER (MOD_SIGNEDNESS | MOD_BITINT)
 #define MOD_IGNORE	(MOD_STORAGE | MOD_ACCESS | MOD_USERTYPE | MOD_EXPLICITLY_SIGNED | MOD_EXT_VISIBLE | MOD_UNUSED | MOD_GNU_INLINE | MOD_CONSTEXPR)
 #define MOD_QUALIFIER	(MOD_CONST | MOD_VOLATILE | MOD_RESTRICT)
 /* modifiers for function attributes */
@@ -412,6 +425,29 @@ static inline int is_signed_type(struct symbol *sym)
 	return !(sym->ctype.modifiers & MOD_UNSIGNED);
 }
 
+static inline int is_bitint_type(const struct symbol *sym)
+{
+	while (sym && sym->type == SYM_NODE)
+		sym = sym->ctype.base_type;
+	return sym && (sym->ctype.modifiers & MOD_BITINT);
+}
+
+static inline int effective_int_width(const struct symbol *type)
+{
+	if (!type)
+		return 0;
+	while (type->type == SYM_NODE) {
+		type = type->ctype.base_type;
+		if (!type)
+			return 0;
+	}
+	if ((type->ctype.modifiers & MOD_BITINT) && type->bitint_width > 0)
+		return type->bitint_width;
+	return type->bit_size;
+}
+
+extern struct symbol *get_bitint_type(int width, int is_signed);
+
 static inline int is_type_type(struct symbol *type)
 {
 	return type == &type_ctype;
@@ -468,7 +504,8 @@ static inline int is_float_type(struct symbol *type)
 
 static inline int is_byte_type(struct symbol *type)
 {
-	return type->bit_size == bits_in_char && type->type != SYM_BITFIELD;
+	return type->bit_size == bits_in_char && type->type != SYM_BITFIELD &&
+	       !is_bitint_type(type);
 }
 
 static inline int is_wchar_type(struct symbol *type)
@@ -575,7 +612,7 @@ static inline int get_sym_type(struct symbol *type)
 static inline long long extend_value(long long val, struct symbol *ctype)
 {
 	int is_signed = !(ctype->ctype.modifiers & MOD_UNSIGNED);
-	unsigned size = ctype->bit_size;
+	unsigned size = effective_int_width(ctype);
 
 	return bits_extend(val, size, is_signed);
 }
diff --git a/validation/c23-bitint-error.c b/validation/c23-bitint-error.c
new file mode 100644
index 000000000000..1172334b3a71
--- /dev/null
+++ b/validation/c23-bitint-error.c
@@ -0,0 +1,61 @@
+static _BitInt(1) a;
+static unsigned _BitInt(0) b;
+static _BitInt(200) c;
+static _BitInt(8) bad_str[] = "abc";
+static struct bad_bf {
+	_BitInt(5) x : 7;
+} bf_instance;
+static _BitInt(8) bad_wb1 = 1wB;
+static _BitInt(8) bad_wb2 = 1Wb;
+enum E_bitint : _BitInt(8) { BI = 0 };
+static constexpr _BitInt(4) bad_ce_bi = 8;
+
+static void ptr_errors(_BitInt(8) *p8, _BitInt(5) *p5, char *pc,
+		       signed char *psc, _BitInt(7) *p7)
+{
+	p8 = pc;
+	p8 = psc;
+	p5 = p7;
+}
+
+static int _BitInt(5) d;
+static _BitInt(5) short e;
+static _BitInt(128) bad_wb_ovf = 0x80000000000000000000000000000000wb;
+static unsigned _BitInt(128) bad_uwb_ovf = 0x100000000000000000000000000000000uwb;
+
+/*
+ * check-name: C23 _BitInt errors
+ * check-command: sparse -std=c23 -Wsparse-error $file
+ * check-exit-value: 1
+ *
+ * check-error-start
+c23-bitint-error.c:1:15: error: signed _BitInt width must be at least 2
+c23-bitint-error.c:2:24: error: invalid bit width 0 for _BitInt
+c23-bitint-error.c:3:15: error: _BitInt width 200 exceeds __BITINT_MAXWIDTH__ (128)
+c23-bitint-error.c:8:29: error: constant 1wB is not a valid number
+c23-bitint-error.c:9:29: error: constant 1Wb is not a valid number
+c23-bitint-error.c:10:28: error: invalid 'enum' underlying type
+c23-bitint-error.c:21:12: error: two or more data types in declaration specifiers
+c23-bitint-error.c:21:20: error: Expected ) in function declarator
+c23-bitint-error.c:21:20: error: got 5
+c23-bitint-error.c:21:12: error: Trying to use reserved word '_BitInt' as identifier
+c23-bitint-error.c:22:19: error: two or more data types in declaration specifiers
+c23-bitint-error.c:22:19: error: Trying to use reserved word 'short' as identifier
+c23-bitint-error.c:22:25: error: Expected ; at end of declaration
+c23-bitint-error.c:22:25: error: got e
+c23-bitint-error.c:23:34: error: constant 0x80000000000000000000000000000000wb is too big for _BitInt(128)
+c23-bitint-error.c:24:44: error: constant 0x100000000000000000000000000000000uwb is too big for unsigned _BitInt(128)
+c23-bitint-error.c:4:31: error: invalid initializer
+c23-bitint-error.c:6:22: error: bitfield 'x' is wider (7) than its type (_BitInt(5))
+c23-bitint-error.c:11:41: error: constexpr initializer not representable in target type
+c23-bitint-error.c:16:12: error: incorrect type in assignment (different base types)
+c23-bitint-error.c:16:12:    expected _BitInt(8)*p8
+c23-bitint-error.c:16:12:    got char *pc
+c23-bitint-error.c:17:12: error: incorrect type in assignment (different base types)
+c23-bitint-error.c:17:12:    expected _BitInt(8)*p8
+c23-bitint-error.c:17:12:    got signed char *psc
+c23-bitint-error.c:18:12: error: incorrect type in assignment (different bit widths)
+c23-bitint-error.c:18:12:    expected _BitInt(5)*p5
+c23-bitint-error.c:18:12:    got _BitInt(7)*p7
+ * check-error-end
+ */
diff --git a/validation/c23-bitint-pedantic.c b/validation/c23-bitint-pedantic.c
new file mode 100644
index 000000000000..d564d3c134b9
--- /dev/null
+++ b/validation/c23-bitint-pedantic.c
@@ -0,0 +1,13 @@
+static _BitInt(10) a;
+static auto b = 42wb;
+
+/*
+ * check-name: C23 _BitInt pedantic
+ * check-command: sparse -std=c17 -Wpedantic $file
+ *
+ * check-error-start
+c23-bitint-pedantic.c:1:15: warning: '_BitInt' is a C23 feature
+c23-bitint-pedantic.c:2:13: warning: 'auto' type inference is a C23 feature
+c23-bitint-pedantic.c:2:17: warning: bit-precise integer suffix is a C23 feature
+ * check-error-end
+ */
diff --git a/validation/c23-bitint.c b/validation/c23-bitint.c
new file mode 100644
index 000000000000..dd86d23ac48e
--- /dev/null
+++ b/validation/c23-bitint.c
@@ -0,0 +1,214 @@
+/*
+ * Check C23 bit-precise integer types: _BitInt(N) and unsigned _BitInt(N)
+ */
+
+_Static_assert(__BITINT_MAXWIDTH__ >= 64, "BITINT_MAXWIDTH");
+
+/* Basic declarations */
+static _BitInt(2) s2;
+static _BitInt(7) s7;
+static _BitInt(8) s8;
+static _BitInt(9) s9;
+static _BitInt(16) s16;
+static _BitInt(24) s24;
+static _BitInt(32) s32;
+static _BitInt(40) s40;
+static _BitInt(64) s64;
+static _BitInt(65) s65;
+static _BitInt(128) s128;
+
+static unsigned _BitInt(1) u1;
+static unsigned _BitInt(7) u7;
+static unsigned _BitInt(8) u8;
+static unsigned _BitInt(9) u9;
+static unsigned _BitInt(16) u16;
+static unsigned _BitInt(24) u24;
+static unsigned _BitInt(32) u32;
+static unsigned _BitInt(40) u40;
+static unsigned _BitInt(64) u64;
+static unsigned _BitInt(65) u65;
+static unsigned _BitInt(128) u128;
+
+/* Sizing */
+_Static_assert(sizeof(s2) == 1, "s2 size");
+_Static_assert(sizeof(s7) == 1, "s7 size");
+_Static_assert(sizeof(s8) == 1, "s8 size");
+_Static_assert(sizeof(s9) == 2, "s9 size");
+_Static_assert(sizeof(s16) == 2, "s16 size");
+_Static_assert(sizeof(s24) == 4, "s24 size");
+_Static_assert(sizeof(s32) == 4, "s32 size");
+_Static_assert(sizeof(s40) == 8, "s40 size");
+_Static_assert(sizeof(s64) == 8, "s64 size");
+_Static_assert(sizeof(s65) == 16, "s65 size");
+_Static_assert(sizeof(s128) == 16, "s128 size");
+
+_Static_assert(sizeof(u1) == 1, "u1 size");
+_Static_assert(sizeof(u7) == 1, "u7 size");
+_Static_assert(sizeof(u8) == 1, "u8 size");
+_Static_assert(sizeof(u9) == 2, "u9 size");
+_Static_assert(sizeof(u16) == 2, "u16 size");
+_Static_assert(sizeof(u24) == 4, "u24 size");
+_Static_assert(sizeof(u32) == 4, "u32 size");
+_Static_assert(sizeof(u40) == 8, "u40 size");
+_Static_assert(sizeof(u64) == 8, "u64 size");
+_Static_assert(sizeof(u65) == 16, "u65 size");
+_Static_assert(sizeof(u128) == 16, "u128 size");
+
+/* Alignment */
+_Static_assert(_Alignof(_BitInt(7)) == 1, "align 7");
+_Static_assert(_Alignof(_BitInt(12)) == 2, "align 12");
+_Static_assert(_Alignof(_BitInt(24)) == 4, "align 24");
+_Static_assert(_Alignof(_BitInt(40)) == 8, "align 40");
+_Static_assert(_Alignof(_BitInt(65)) == 8, "align 65");
+
+/* wb and uwb literal suffixes */
+_Static_assert(_Generic(0wb, _BitInt(2): 1, default: 0) == 1, "0wb");
+_Static_assert(_Generic(1wb, _BitInt(2): 1, default: 0) == 1, "1wb");
+_Static_assert(_Generic(1WB, _BitInt(2): 1, default: 0) == 1, "1WB");
+_Static_assert(_Generic(0uwb, unsigned _BitInt(1): 1, default: 0) == 1, "0uwb");
+_Static_assert(_Generic(1uwb, unsigned _BitInt(1): 1, default: 0) == 1, "1uwb");
+_Static_assert(_Generic(1UWB, unsigned _BitInt(1): 1, default: 0) == 1, "1UWB");
+_Static_assert(_Generic(1wbU, unsigned _BitInt(1): 1, default: 0) == 1, "1wbU");
+_Static_assert(_Generic(42wb, _BitInt(7): 1, default: 0) == 1, "42wb");
+_Static_assert(_Generic(42uwb, unsigned _BitInt(6): 1, default: 0) == 1,
+	       "42uwb");
+
+/* Type compatibility and distinctness */
+_Static_assert(_Generic(s8, _BitInt(8): 1, char: 0, signed char: 0, default: 0) == 1,
+	       "distinct s8");
+_Static_assert(_Generic(u8, unsigned _BitInt(8): 1, unsigned char: 0, default: 0) == 1,
+	       "distinct u8");
+_Static_assert(_Generic(s24, _BitInt(24): 1, default: 0) == 1, "distinct s24");
+_Static_assert(_Generic(s32, _BitInt(32): 1, int: 0, default: 0) == 1,
+	       "distinct s32");
+_Static_assert(_Generic(u32,
+			       unsigned _BitInt(32): 1,
+			       unsigned int: 0,
+			       default: 0) == 1,
+	       "distinct u32");
+
+/* Compound types and bit-fields */
+struct S {
+	_BitInt(7) a;
+	unsigned _BitInt(9) b;
+};
+_Static_assert(sizeof(struct S) == 4, "struct size");
+
+struct BF {
+	_BitInt(5) sbf : 3;
+	unsigned _BitInt(5) ubf : 3;
+};
+static struct BF bf_val;
+_Static_assert(_Generic(+bf_val.sbf, _BitInt(5): 1, default: 0) == 1,
+	       "signed _BitInt bitfield");
+_Static_assert(_Generic(+bf_val.ubf, unsigned _BitInt(5): 1, default: 0) == 1,
+	       "unsigned _BitInt bitfield");
+
+static _BitInt(10) arr[5];
+_Static_assert(sizeof(arr) == 5 * sizeof(_BitInt(10)), "array size");
+
+/* Exact-width masking and >64-bit constant folding */
+_Static_assert((unsigned _BitInt(3))7uwb + 1uwb == 0uwb, "u3 wrap");
+_Static_assert(~0uwb == 1uwb, "u1 ~0");
+_Static_assert((unsigned _BitInt(5))~0wb == 31uwb, "u5 ~0wb");
+_Static_assert((unsigned _BitInt(33))0x1ffffffffULL + 1uwb == 0uwb, "u33 wrap");
+_Static_assert((_BitInt(4))-8 == -8, "s4 min");
+
+_Static_assert(_Generic(0x8000000000000000wb, _BitInt(65): 1, default: 0) == 1,
+	       "2^63 wb width is 65");
+_Static_assert(0x8000000000000000wb > 0wb, "+2^63 > 0");
+_Static_assert(0x8000000000000000wb > -1wb, "+2^63 > -1");
+_Static_assert((_BitInt(65))0xffffffffffffffffULL > 0wb, "2^64-1 in s65 > 0");
+_Static_assert((_BitInt(65))0xffffffffffffffffULL != -1wb, "2^64-1 in s65 != -1");
+_Static_assert(-0x8000000000000000wb < 0wb, "-2^63 < 0");
+_Static_assert(0x8000000000000000wb + (-0x8000000000000000wb) == 0wb,
+	       "+2^63 + -2^63 == 0");
+_Static_assert(_Generic(0x1'00000000'00000000uwb,
+			unsigned _BitInt(65): 1, default: 0) == 1,
+	       "2^64 uwb width is 65");
+_Static_assert(_Generic(0x1'00000000'00000000wb,
+			_BitInt(66): 1, default: 0) == 1,
+	       "2^64 wb width is 66");
+_Static_assert(_Generic(0x7fffffffffffffff'ffffffffffffffffwb,
+			_BitInt(128): 1, default: 0) == 1,
+	       "2^127-1 wb width is 128");
+_Static_assert(_Generic(0xffffffffffffffff'ffffffffffffffffuwb,
+			unsigned _BitInt(128): 1, default: 0) == 1,
+	       "2^128-1 uwb width is 128");
+_Static_assert(0x1'00000000'00000000uwb > 0xffffffffffffffffuwb, "2^64 > 2^64-1");
+_Static_assert((0x1'00000000'00000000uwb >> 64) == 1uwb, "2^64 >> 64 == 1");
+_Static_assert(((unsigned _BitInt(128))1 << 100) >> 100 == 1, "1 << 100 >> 100");
+_Static_assert(~(unsigned _BitInt(100))0 ==
+	       ((unsigned _BitInt(128))1 << 100) - 1, "~u100");
+
+/* Arithmetic conversions and lack of integer promotion (C23 6.3.1.1, 6.3.1.8) */
+static void test_ops(void)
+{
+	_BitInt(40) a = 1;
+	_BitInt(50) b = 2;
+	_Static_assert(_Generic(a + b, _BitInt(50): 1, default: 0) == 1,
+		       "a + b");
+
+	unsigned _BitInt(40) u = 1;
+	_Static_assert(_Generic(u + a, unsigned _BitInt(40): 1, default: 0) ==
+			       1,
+		       "u + a");
+	_Static_assert(_Generic(u + b, _BitInt(50): 1, default: 0) == 1,
+		       "u + b");
+
+	_BitInt(10) x = 1;
+	_BitInt(10) y = 2;
+	_Static_assert(_Generic(x + y, _BitInt(10): 1, default: 0) == 1,
+		       "narrow _BitInt does not promote to int");
+	_Static_assert(_Generic(+x, _BitInt(10): 1, default: 0) == 1,
+		       "unary + does not promote _BitInt");
+
+	_BitInt(32) z = 1;
+	_Static_assert(_Generic(z + z, _BitInt(32): 1, default: 0) == 1,
+		       "no promotion");
+
+	int si = 1;
+	unsigned int ui = 1;
+	unsigned _BitInt(32) uz = 1;
+	unsigned _BitInt(31) u31 = 1;
+	_BitInt(33) s33 = 1;
+	unsigned _BitInt(8) ub8 = 1;
+	_BitInt(16) sb16 = 1;
+	unsigned _BitInt(16) ub16 = 1;
+
+	_Static_assert(_Generic(z + si, int: 1, default: 0) == 1,
+		       "_BitInt(32) + int -> int");
+	_Static_assert(_Generic(uz + ui, unsigned int: 1, default: 0) == 1,
+		       "u_BitInt(32) + uint -> uint");
+	_Static_assert(_Generic(z + ui, unsigned int: 1, default: 0) == 1,
+		       "_BitInt(32) + uint -> uint");
+	_Static_assert(_Generic(uz + si, unsigned int: 1, default: 0) == 1,
+		       "u_BitInt(32) + int -> uint");
+	_Static_assert(_Generic(u31 + si, int: 1, default: 0) == 1,
+		       "u_BitInt(31) + int -> int");
+	_Static_assert(_Generic(s33 + si, _BitInt(33): 1, default: 0) == 1,
+		       "_BitInt(33) + int -> _BitInt(33)");
+	_Static_assert(_Generic(ub8 + sb16, _BitInt(16): 1, default: 0) == 1,
+		       "u_BitInt(8) + _BitInt(16) -> _BitInt(16)");
+	_Static_assert(_Generic(ub16 + sb16, unsigned _BitInt(16): 1, default: 0) == 1,
+		       "u_BitInt(16) + _BitInt(16) -> u_BitInt(16)");
+
+	constexpr _BitInt(8) ce_bi8 = -128wb;
+	constexpr _BitInt(70) ce_bi70 = 0x8000000000000000wb;
+	_Static_assert(ce_bi8 == -128, "constexpr _BitInt(8)");
+	_Static_assert(ce_bi70 > 0, "constexpr _BitInt(70) >64-bit positive");
+}
+
+struct bitint128_bf {
+	unsigned _BitInt(128) f : 100;
+};
+
+static unsigned _BitInt(65) get_bitint65(void)
+{
+	return 1uwb;
+}
+
+/*
+ * check-name: C23 _BitInt(N)
+ * check-command: sparse -std=c23 $file
+ */
diff --git a/validation/expand-bitint.c b/validation/expand-bitint.c
new file mode 100644
index 000000000000..99122f0f401f
--- /dev/null
+++ b/validation/expand-bitint.c
@@ -0,0 +1,49 @@
+/*
+ * Expand constant folding tests for C23 _BitInt(N) widths != ABI bit_size
+ * and widths > 64 bits.
+ */
+
+_Static_assert((~(unsigned _BitInt(37))0) == 0x1fffffffffULL, "~u37");
+_Static_assert(((unsigned _BitInt(37))0x1fffffffffULL +
+		(unsigned _BitInt(37))1) == 0, "wrap u37");
+_Static_assert((~(_BitInt(37))0) == -1, "~s37");
+_Static_assert(-(_BitInt(128))0x8000000000000000ULL < 0, "-2^63 < 0");
+
+int cmp_u128_max(void)
+{
+	unsigned _BitInt(128) hi = (unsigned _BitInt(128))-1;
+	unsigned _BitInt(128) lo = (unsigned _BitInt(128))0xffffffffffffffffULL;
+
+	return hi == lo;
+}
+
+int neg_s128_bit63(void)
+{
+	_BitInt(128) x = (_BitInt(128))0x8000000000000000ULL;
+	_BitInt(128) y = -x;
+
+	return y < 0;
+}
+
+_BitInt(128) neg_s128_var(_BitInt(128) x)
+{
+	return -x;
+}
+
+double cast_u128_max_to_double(void)
+{
+	unsigned _BitInt(128) hi = (unsigned _BitInt(128))-1;
+
+	return (double)hi;
+}
+
+/*
+ * check-name: expand-bitint
+ * check-command: test-linearize -Wno-decl -std=c23 $file
+ *
+ * check-output-ignore
+ * check-output-contains: seteq\\.
+ * check-output-contains: ret\\.32\\s*\\$1
+ * check-output-contains: neg\\.128
+ * check-output-contains: ucvtf\\.64
+ */

  parent reply	other threads:[~2026-11-09 20:34 UTC|newest]

Thread overview: 74+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-09 20:31 Bart Van Assche
2026-11-09 20:31 ` [PATCH 01/73] Makefile: Pass '--' to rm in clean target to handle dash-prefixed files Bart Van Assche
2026-11-09 20:31 ` [PATCH 02/73] sparse-llvm: Use LLVMCreateBuilderForModule() with LLVM 24 and later Bart Van Assche
2026-11-09 20:31 ` [PATCH 03/73] typeof_unqual: Strip qualifiers and address space from types and arrays Bart Van Assche
2026-11-09 20:31 ` [PATCH 04/73] bitfield: Pack consecutive _Bool bitfields into byte containers Bart Van Assche
2026-11-09 20:31 ` [PATCH 05/73] allocate: Harden slab allocation bounds, zeroing, and mmap checks Bart Van Assche
2026-11-09 20:31 ` [PATCH 06/73] tokenize: Support 4096-byte identifiers and fix escape bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 07/73] pre-process: Fix macro expansion and include directive handling Bart Van Assche
2026-11-09 20:31 ` [PATCH 08/73] options, lib: Harden warning buffers and command-line option parsing Bart Van Assche
2026-11-09 20:31 ` [PATCH 09/73] builtin: Fix atomic builtin type checking and object_size bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 10/73] parse, show-parse: Fix attribute propagation, enum overflow, and bounds Bart Van Assche
2026-11-09 20:31 ` [PATCH 11/73] target-openrisc: Set target_openrisc.mach to MACH_OPENRISC Bart Van Assche
2026-11-09 20:31 ` [PATCH 12/73] evaluate: Fix pointer arithmetic, incomplete types, and qualifiers Bart Van Assche
2026-11-09 20:31 ` [PATCH 13/73] expand: Fix static initializer folding and floating-point casts Bart Van Assche
2026-11-09 20:32 ` [PATCH 14/73] inline: Copy union cast types and VLA bounds when inlining Bart Van Assche
2026-11-09 20:32 ` [PATCH 15/73] linearize, flow: Fix asm operands, switch case bounds, and DSE Bart Van Assche
2026-11-09 20:32 ` [PATCH 16/73] memops, ssa: Walk parent insns in reverse and skip mixed-size SSA Bart Van Assche
2026-11-09 20:32 ` [PATCH 17/73] simplify: Prevent overflow on LLONG_MIN negation and fix simplifications Bart Van Assche
2026-11-09 20:32 ` [PATCH 18/73] sparse: Gate context failure warning on Wcontext and catch underflow Bart Van Assche
2026-11-09 20:32 ` [PATCH 19/73] ast-model: Return AST_N_COLUMNS and inspect child nodes lazily Bart Van Assche
2026-11-09 20:32 ` [PATCH 20/73] c2xml: Guard against recursive symbols and free xmlGetProp() string Bart Van Assche
2026-11-09 20:32 ` [PATCH 21/73] compile-i386: Fix buffer bounds, unsigned '<=' codegen, and null checks Bart Van Assche
2026-11-09 20:32 ` [PATCH 22/73] ctags: Fix line comparison order and handle SYM_RESTRICT and SYM_LABEL Bart Van Assche
2026-11-09 20:32 ` [PATCH 23/73] dissect: Unwrap array element type via base_type() Bart Van Assche
2026-11-09 20:32 ` [PATCH 24/73] example: Fix show_op() rotating buffer dimensions Bart Van Assche
2026-11-09 20:32 ` [PATCH 25/73] graph: Check PSEUDO_SYM before dereferencing insn->func->sym Bart Van Assche
2026-11-09 20:32 ` [PATCH 26/73] scheck: Fix slice() upper bit index calculation Bart Van Assche
2026-11-09 20:32 ` [PATCH 27/73] sparse-llvm: Support arbitrary struct member counts and fix operand types Bart Van Assche
2026-11-09 20:32 ` [PATCH 28/73] inline: Duplicate generic expressions instead of evaluating them Bart Van Assche
2026-11-09 20:32 ` [PATCH 29/73] lib: Avoid mutating fmt before va_start() in warning() Bart Van Assche
2026-11-09 20:32 ` [PATCH 30/73] cse, canon: Cast pointers to uintptr_t before relational comparison Bart Van Assche
2026-11-09 20:32 ` [PATCH 31/73] context: Add "conditional context increment" model Bart Van Assche
2026-11-09 20:32 ` [PATCH 32/73] expand: Simplify signed constants early Bart Van Assche
2026-11-09 20:32 ` [PATCH 33/73] bitwise: Allow all-bits-set as a restricted value Bart Van Assche
2026-11-09 20:32 ` [PATCH 34/73] bitwise: Allow restricted ordered compares with unrestricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 35/73] bitwise: Keep unrestricted values unrestricted after a cast Bart Van Assche
2026-11-09 20:32 ` [PATCH 36/73] bitwise: Don't remove MOD_SPECIFIER from 'bitwise' types Bart Van Assche
2026-11-09 20:32 ` [PATCH 37/73] bitwise: Allow both regular casts and forced casts for restricted values Bart Van Assche
2026-11-09 20:32 ` [PATCH 38/73] clang-format: Add .clang-format configuration file Bart Van Assche
2026-11-09 20:32 ` [PATCH 39/73] options: Add support for -std=c23, -std=c2x and -std=gnu23 Bart Van Assche
2026-11-09 20:32 ` [PATCH 40/73] preprocessor: Add support for #elifdef and #elifndef Bart Van Assche
2026-11-09 20:32 ` [PATCH 41/73] preprocessor: Add support for __has_include and __has_include_next Bart Van Assche
2026-11-09 20:32 ` [PATCH 42/73] c23: Add support for static_assert, alignas, alignof, thread_local Bart Van Assche
2026-11-09 20:32 ` [PATCH 43/73] c23: Add support for bool, true, and false keywords Bart Van Assche
2026-11-09 20:32 ` [PATCH 44/73] c23: Add support for nullptr and nullptr_t Bart Van Assche
2026-11-09 20:32 ` [PATCH 45/73] c23: Add support for digit separators in numeric literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 46/73] c23: Support unnamed parameters in function definitions Bart Van Assche
2026-11-09 20:32 ` [PATCH 47/73] c23: Support variadic functions without named parameters Bart Van Assche
2026-11-09 20:32 ` [PATCH 48/73] c23: Support labels before declarations and at block ends Bart Van Assche
2026-11-09 20:32 ` [PATCH 49/73] c23: Support auto type inference Bart Van Assche
2026-11-09 20:32 ` [PATCH 50/73] c23: Support constexpr object specifier Bart Van Assche
2026-11-09 20:32 ` [PATCH 51/73] c23: Support enumerations with fixed underlying types Bart Van Assche
2026-11-09 20:32 ` [PATCH 52/73] c23: Support standard [[]] attribute syntax Bart Van Assche
2026-11-09 20:32 ` [PATCH 53/73] preprocessor: Add support for __has_c_attribute Bart Van Assche
2026-11-09 20:32 ` [PATCH 54/73] c23: Support empty initializer braces {} Bart Van Assche
2026-11-09 20:32 ` [PATCH 55/73] preprocessor: Add support for __has_embed Bart Van Assche
2026-11-09 20:32 ` [PATCH 56/73] preprocessor: Add support for #embed directive Bart Van Assche
2026-11-09 20:32 ` [PATCH 57/73] c23: Add support for u8 character constants and string literals Bart Van Assche
2026-11-09 20:32 ` [PATCH 58/73] preprocessor: Add support for __VA_OPT__ Bart Van Assche
2026-11-09 20:32 ` [PATCH 59/73] c23: Treat empty parameter list () as (void) and reject K&R functions Bart Van Assche
2026-11-09 20:32 ` [PATCH 60/73] c23: Support storage-class specifiers in compound literals Bart Van Assche
2026-11-09 20:32 ` Bart Van Assche [this message]
2026-11-09 20:32 ` [PATCH 62/73] enum: Support machine mode attributes on enumeration types Bart Van Assche
2026-11-09 20:32 ` [PATCH 63/73] enum: Support __attribute__((packed)) " Bart Van Assche
2026-11-09 20:32 ` [PATCH 64/73] expand: Ignore top-level non-array address space in compare_types() Bart Van Assche
2026-11-09 20:32 ` [PATCH 65/73] builtin: Support __builtin_infer_alloc_token() as a size_t ICE Bart Van Assche
2026-11-09 20:32 ` [PATCH 66/73] builtin: Add support for Linux kernel compiler builtins Bart Van Assche
2026-11-09 20:32 ` [PATCH 67/73] builtin: Add support for __builtin_has_attribute() Bart Van Assche
2026-11-09 20:32 ` [PATCH 68/73] cleanup: Support __attribute__((cleanup(...))) in linearize and -Wcontext Bart Van Assche
2026-11-09 20:32 ` [PATCH 69/73] builtin: Do not evaluate __builtin_clz(0) and __builtin_ctz(0) Bart Van Assche
2026-11-09 20:32 ` [PATCH 70/73] pre-process: Support long token and string expansions dynamically Bart Van Assche
2026-11-09 20:32 ` [PATCH 71/73] x86: Undefine conflicting bitness macros when parsing -m flags Bart Van Assche
2026-11-09 20:32 ` [PATCH 72/73] evaluate: Accept conditional expressions with only one void side Bart Van Assche
2026-11-09 20:32 ` [PATCH 73/73] ci: Add Linux kernel GitHub Actions workflow and build script Bart Van Assche

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261109203259.362845-62-bvanassche@acm.org \
    --to=bvanassche@acm.org \
    --cc=linux-sparse@vger.kernel.org \
    --cc=torvalds@linux-foundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox