Skip to content

Commit b96394f

Browse files
Translate sizeof/_Alignof to opaque c_sizeof/c_alignof
Previously, the proposed sizeof support (on the 'sizeof' branch) constant-folded sizeof / _Alignof to integer literals using clang's target ABI. This commits to a specific platform layout in the emitted F* code, which we want to avoid. This change introduces a small Pulse library 'Pulse.Lib.C.Sizeof' that declares: - a reified 'c_type' inductive (Void / Bool / SizeT / PtrdiffT / Int / Pointer / Array / Named) - opaque c_sizeof : c_type -> SizeT.t - opaque c_alignof : c_type -> SizeT.t - axioms relating these to standard C facts (positivity, array decomposition, sign-independence, sizeof(intN_t) == N/8). The translation pipeline now lowers C 'sizeof(T)' / '_Alignof(T)' to opaque calls, with the type structure preserved as a c_type term (e.g. sizeof(int) -> c_sizeof (C_Int true 32) sizeof(T[8]) -> c_sizeof (C_Array (C_Int true 32) 8) sizeof(s) -> c_sizeof (C_Named "ty_s") for typedefs/structs). Hauntedc (the spec mini-parser) understands sizeof(<type>) and _Alignof(<type>) inside _ensures / _requires, so verified specs can refer to the same opaque values as the function body. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 3f11c61 commit b96394f

14 files changed

Lines changed: 487 additions & 0 deletions

File tree

cpp/iface.zng

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use ::std::vec::Vec as Vec;
2323
use crate::ir::Ident as Ident;
2424
use crate::ir::SourceInfo as SourceInfo;
2525
use crate::ir::Type as Type;
26+
use crate::ir::CTypeExpr as CTypeExpr;
2627
use crate::ir::Stmt as Stmt;
2728
use crate::ir::Expr as Expr;
2829
use crate::ir::UnOp as UnOp;
@@ -44,6 +45,7 @@ mod crate::ir {
4445
type SourceInfo { #only_by_ref; }
4546
type Ident { wellknown_traits(?Sized); }
4647
type Type { #only_by_ref; }
48+
type CTypeExpr { #only_by_ref; }
4749
type Expr { #only_by_ref; }
4850
type Stmt { #only_by_ref; }
4951

@@ -86,6 +88,11 @@ mod crate::ir {
8688
fn deref(&self) -> &Type;
8789
}
8890

91+
type Rc<CTypeExpr> {
92+
#layout(size=8, align=8);
93+
fn deref(&self) -> &CTypeExpr;
94+
}
95+
8996
type Rc<Ident> {
9097
#layout(size=8, align=8);
9198
fn deref(&self) -> &Ident;
@@ -249,6 +256,17 @@ mod crate::clang {
249256
fn mk_type_arrayptr(Rc<SourceInfo>, Rc<Type>) -> Rc<Type>;
250257
fn mk_type_err(Rc<SourceInfo>) -> Rc<Type>;
251258

259+
fn mk_ctype_void(Rc<SourceInfo>) -> Rc<CTypeExpr>;
260+
fn mk_ctype_bool(Rc<SourceInfo>) -> Rc<CTypeExpr>;
261+
fn mk_ctype_sizet(Rc<SourceInfo>) -> Rc<CTypeExpr>;
262+
fn mk_ctype_ptrdifft(Rc<SourceInfo>) -> Rc<CTypeExpr>;
263+
fn mk_ctype_int(Rc<SourceInfo>, bool, u32) -> Rc<CTypeExpr>;
264+
fn mk_ctype_pointer(Rc<SourceInfo>, Rc<CTypeExpr>) -> Rc<CTypeExpr>;
265+
fn mk_ctype_array(Rc<SourceInfo>, Rc<CTypeExpr>, Rc<BigInt>) -> Rc<CTypeExpr>;
266+
fn mk_ctype_named_typedef(Rc<SourceInfo>, Rc<Ident>) -> Rc<CTypeExpr>;
267+
fn mk_ctype_named_struct(Rc<SourceInfo>, Rc<Ident>) -> Rc<CTypeExpr>;
268+
fn mk_ctype_named_union(Rc<SourceInfo>, Rc<Ident>) -> Rc<CTypeExpr>;
269+
252270
fn mk_bigint(&str) -> Rc<BigInt>;
253271

254272
fn mk_bool_lit(Rc<SourceInfo>, bool) -> Rc<Expr>;
@@ -274,6 +292,9 @@ mod crate::clang {
274292
fn mk_live(Rc<SourceInfo>, Rc<Expr>) -> Rc<Expr>;
275293
fn mk_assign_expr(Rc<SourceInfo>, Rc<Expr>, Rc<Expr>) -> Rc<Expr>;
276294

295+
fn mk_sizeof(Rc<SourceInfo>, Rc<CTypeExpr>) -> Rc<Expr>;
296+
fn mk_alignof(Rc<SourceInfo>, Rc<CTypeExpr>) -> Rc<Expr>;
297+
277298
fn mk_lvalue_var(Rc<SourceInfo>, Rc<Ident>) -> Rc<Expr>;
278299
fn mk_lvalue_member(Rc<SourceInfo>, Rc<Expr>, Rc<Ident>) -> Rc<Expr>;
279300
fn mk_index(Rc<SourceInfo>, Rc<Expr>, Rc<Expr>) -> Rc<Expr>;

cpp/impl.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,75 @@ class PALConsumer : public ASTConsumer {
348348
return mk_type_err(std::move(loc));
349349
}
350350

351+
/// Build a reified [CTypeExpr] for the type appearing as the operand of
352+
/// `sizeof` / `_Alignof`. Mirrors [trQualType] but produces values that
353+
/// are arguments to the opaque `c_sizeof` / `c_alignof` functions, and
354+
/// preserves constant array lengths (which are dropped by [trQualType]).
355+
Rc<ir::CTypeExpr> trCTypeFromQualType(QualType t, SourceRange range) {
356+
t = t.IgnoreParens();
357+
auto loc = getRange(range);
358+
359+
if (t.getAsString() == "size_t") {
360+
return mk_ctype_sizet(std::move(loc));
361+
} else if (t.getAsString() == "ptrdiff_t") {
362+
return mk_ctype_ptrdifft(std::move(loc));
363+
} else if (auto tydef = dyn_cast<TypedefType>(t)) {
364+
auto id = ctx.mk_ident(toStr(tydef->getDecl()->getName()), loc.clone());
365+
return mk_ctype_named_typedef(std::move(loc), std::move(id));
366+
#if LLVM_VERSION_MAJOR < 22
367+
} else if (auto elab = dyn_cast<ElaboratedType>(t)) {
368+
return trCTypeFromQualType(elab->desugar(), range);
369+
#endif
370+
} else if (auto ptr = dyn_cast<PointerType>(t)) {
371+
return mk_ctype_pointer(
372+
std::move(loc), trCTypeFromQualType(ptr->getPointeeType(), range));
373+
} else if (auto adj = dyn_cast<AdjustedType>(t)) {
374+
return trCTypeFromQualType(adj->getOriginalType(), range);
375+
} else if (auto carr = dyn_cast<ConstantArrayType>(t)) {
376+
auto elem = trCTypeFromQualType(carr->getElementType(), range);
377+
SmallString<32> nStr;
378+
carr->getSize().toString(nStr, 10, /*Signed=*/false);
379+
auto n = mk_bigint(toStr(StringRef(nStr)));
380+
return mk_ctype_array(std::move(loc), std::move(elem), std::move(n));
381+
} else if (auto rec = dyn_cast<RecordType>(t)) {
382+
auto decl = rec->getDecl();
383+
if (!decl->getIdentifier()) {
384+
reportUnsupported(range, loc,
385+
"sizeof/_Alignof on anonymous struct/union outside "
386+
"of typedef is unsupported",
387+
"");
388+
return mk_ctype_named_struct(loc.clone(),
389+
ctx.mk_ident("__anon__"_rs, loc.clone()));
390+
}
391+
auto name = ctx.mk_ident(toStr(decl->getName()), loc.clone());
392+
switch (decl->getTagKind()) {
393+
case TagTypeKind::Struct:
394+
return mk_ctype_named_struct(std::move(loc), std::move(name));
395+
case TagTypeKind::Union:
396+
return mk_ctype_named_union(std::move(loc), std::move(name));
397+
default:
398+
reportUnsupported(range, loc, "unsupported record kind in sizeof", "");
399+
return mk_ctype_named_struct(std::move(loc), std::move(name));
400+
}
401+
}
402+
if (t->isVoidType()) {
403+
return mk_ctype_void(std::move(loc));
404+
}
405+
if (isBoolType(t)) {
406+
return mk_ctype_bool(std::move(loc));
407+
}
408+
if (t->isSignedIntegerType() || t->isUnsignedIntegerType()) {
409+
bool isSigned = t->isSignedIntegerType();
410+
unsigned width = astCtx->getIntWidth(t);
411+
return mk_ctype_int(std::move(loc), isSigned, width);
412+
}
413+
414+
reportUnsupported(range, loc, "unsupported type in sizeof/_Alignof ",
415+
t->getTypeClassName());
416+
return mk_ctype_named_struct(
417+
loc.clone(), ctx.mk_ident("__unsupported__"_rs, loc.clone()));
418+
}
419+
351420
Rc<ir::Type> trTypeAttrs(AttrVec const &attrs, Rc<ir::Type> &&ty) {
352421
for (auto it = attrs.rbegin(); it != attrs.rend(); ++it) {
353422
if (auto ann = dyn_cast<AnnotateAttr>(*it)) {
@@ -797,6 +866,32 @@ class PALConsumer : public ASTConsumer {
797866
}
798867
// Other DeclRefExpr in rvalue context: treat as lvalue read
799868
return mk_rvalue_lvalue(std::move(loc), trLValue(e));
869+
} else if (auto *u = dyn_cast<UnaryExprOrTypeTraitExpr>(e)) {
870+
// sizeof / _Alignof: translated to opaque calls to c_sizeof /
871+
// c_alignof on a reified C-type term. We deliberately do *not*
872+
// commit to a specific numeric value here; the verifier sees an
873+
// abstract size_t whose only known properties come from the axioms
874+
// in Pulse.Lib.C.Sizeof. VLAs and other non-constant cases are
875+
// unsupported.
876+
if (u->getKind() == UETT_SizeOf || u->getKind() == UETT_AlignOf ||
877+
u->getKind() == UETT_PreferredAlignOf) {
878+
QualType argTy = u->getTypeOfArgument();
879+
if (argTy->isVariableArrayType() || argTy->isDependentType() ||
880+
argTy->isIncompleteType()) {
881+
reportUnsupported(e->getSourceRange(), loc,
882+
"unsupported sizeof/_Alignof on incomplete, "
883+
"dependent, or variable-length type",
884+
"");
885+
return mk_rvalue_err(std::move(loc),
886+
trQualType(e->getType(), e->getSourceRange()));
887+
}
888+
auto ctype = trCTypeFromQualType(argTy, u->getSourceRange());
889+
if (u->getKind() == UETT_AlignOf ||
890+
u->getKind() == UETT_PreferredAlignOf) {
891+
return mk_alignof(std::move(loc), std::move(ctype));
892+
}
893+
return mk_sizeof(std::move(loc), std::move(ctype));
894+
}
800895
}
801896

802897
reportUnsupported(e->getSourceRange(), loc,

pulse/Pulse.Lib.C.Sizeof.fsti

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
module Pulse.Lib.C.Sizeof
2+
3+
open FStar.SizeT
4+
5+
/// Reified C types used as arguments to opaque [c_sizeof] / [c_alignof].
6+
/// These values do not commit to any specific platform layout — they
7+
/// only identify the C type whose size or alignment is being asked for.
8+
noeq type c_type =
9+
| C_Void
10+
| C_Bool
11+
| C_SizeT
12+
| C_PtrdiffT
13+
| C_Int : signed:bool -> width:nat -> c_type
14+
| C_Pointer : c_type -> c_type
15+
| C_Array : c_type -> nat -> c_type
16+
| C_Named : string -> c_type
17+
18+
/// Opaque size of a C type, in bytes. We commit to no specific value.
19+
val c_sizeof : c_type -> t
20+
21+
/// Opaque alignment of a C type, in bytes. We commit to no specific value.
22+
val c_alignof : c_type -> t
23+
24+
/// Sizes and alignments are always strictly positive on any conforming C
25+
/// implementation.
26+
val c_sizeof_pos (ty: c_type)
27+
: Lemma (v (c_sizeof ty) > 0)
28+
[SMTPat (v (c_sizeof ty))]
29+
30+
val c_alignof_pos (ty: c_type)
31+
: Lemma (v (c_alignof ty) > 0)
32+
[SMTPat (v (c_alignof ty))]
33+
34+
/// Array-type size decomposition: sizeof(T[n]) == n * sizeof(T).
35+
val c_sizeof_array (ty: c_type) (n: nat)
36+
: Lemma (fits (n * v (c_sizeof ty)) /\
37+
v (c_sizeof (C_Array ty n)) == n * v (c_sizeof ty))
38+
[SMTPat (c_sizeof (C_Array ty n))]
39+
40+
/// Sign of an integer type does not affect its size (C standard).
41+
val c_sizeof_int_sign (s1 s2: bool) (w: nat)
42+
: Lemma (c_sizeof (C_Int s1 w) == c_sizeof (C_Int s2 w))
43+
44+
/// For fixed-width integer types, the size in bytes is the width in bits
45+
/// divided by the bits-per-byte (8) — i.e. `sizeof(intN_t) == N/8`. This
46+
/// holds on any conforming C implementation where `CHAR_BIT == 8`.
47+
val c_sizeof_int_width (s: bool) (w: nat)
48+
: Lemma (requires (w % 8 == 0))
49+
(ensures (v (c_sizeof (C_Int s w)) == w / 8))
50+
[SMTPat (c_sizeof (C_Int s w))]

pulse/Pulse.Lib.C.fsti

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ include Pulse.Class.PtsTo
99
include FStar.Int.Cast
1010
include Pulse.Lib.C.Casts
1111
include Pulse.Lib.C.UnaryOps
12+
include Pulse.Lib.C.Sizeof
1213
include Pulse.Lib.WithPure
1314
open Pulse.Lib.Core
1415
let _Bool = bool

src/clang.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,37 @@ fn mk_type_err(loc: Rc<SourceInfo>) -> Rc<Type> {
603603
mk_ast(loc, TypeT::Error)
604604
}
605605

606+
fn mk_ctype_void(loc: Rc<SourceInfo>) -> Rc<CTypeExpr> {
607+
mk_ast(loc, CTypeExprT::Void)
608+
}
609+
fn mk_ctype_bool(loc: Rc<SourceInfo>) -> Rc<CTypeExpr> {
610+
mk_ast(loc, CTypeExprT::Bool)
611+
}
612+
fn mk_ctype_sizet(loc: Rc<SourceInfo>) -> Rc<CTypeExpr> {
613+
mk_ast(loc, CTypeExprT::SizeT)
614+
}
615+
fn mk_ctype_ptrdifft(loc: Rc<SourceInfo>) -> Rc<CTypeExpr> {
616+
mk_ast(loc, CTypeExprT::PtrdiffT)
617+
}
618+
fn mk_ctype_int(loc: Rc<SourceInfo>, signed: bool, width: u32) -> Rc<CTypeExpr> {
619+
mk_ast(loc, CTypeExprT::Int { signed, width })
620+
}
621+
fn mk_ctype_pointer(loc: Rc<SourceInfo>, to: Rc<CTypeExpr>) -> Rc<CTypeExpr> {
622+
mk_ast(loc, CTypeExprT::Pointer(to))
623+
}
624+
fn mk_ctype_array(loc: Rc<SourceInfo>, elem: Rc<CTypeExpr>, n: Rc<BigInt>) -> Rc<CTypeExpr> {
625+
mk_ast(loc, CTypeExprT::Array(elem, n))
626+
}
627+
fn mk_ctype_named_typedef(loc: Rc<SourceInfo>, name: Rc<Ident>) -> Rc<CTypeExpr> {
628+
mk_ast(loc, CTypeExprT::Named(TypeRefKind::Typedef(name)))
629+
}
630+
fn mk_ctype_named_struct(loc: Rc<SourceInfo>, name: Rc<Ident>) -> Rc<CTypeExpr> {
631+
mk_ast(loc, CTypeExprT::Named(TypeRefKind::Struct(name)))
632+
}
633+
fn mk_ctype_named_union(loc: Rc<SourceInfo>, name: Rc<Ident>) -> Rc<CTypeExpr> {
634+
mk_ast(loc, CTypeExprT::Named(TypeRefKind::Union(name)))
635+
}
636+
606637
fn mk_bigint(s: &str) -> Rc<BigInt> {
607638
Rc::from(BigInt::from_str(s).unwrap())
608639
}
@@ -681,6 +712,13 @@ fn mk_assign_expr(loc: Rc<SourceInfo>, lhs: Rc<Expr>, rhs: Rc<Expr>) -> Rc<Expr>
681712
mk_ast(loc, ExprT::AssignExpr(lhs, rhs))
682713
}
683714

715+
fn mk_sizeof(loc: Rc<SourceInfo>, ty: Rc<CTypeExpr>) -> Rc<Expr> {
716+
mk_ast(loc, ExprT::SizeOf(ty))
717+
}
718+
fn mk_alignof(loc: Rc<SourceInfo>, ty: Rc<CTypeExpr>) -> Rc<Expr> {
719+
mk_ast(loc, ExprT::AlignOf(ty))
720+
}
721+
684722
pub struct StructInitBuilder {
685723
loc: Rc<SourceInfo>,
686724
name: Rc<Ident>,

src/env.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ impl Env {
358358
},
359359
ExprT::Cast(_, ty) => Ok(ty.clone().into()),
360360
ExprT::Error(ty) => Ok(ty.clone().into()),
361+
ExprT::SizeOf(_) | ExprT::AlignOf(_) => {
362+
Ok(TypeT::SizeT.with_loc_core(expr.loc.clone()).into())
363+
}
361364
ExprT::Malloc(ty) | ExprT::Calloc(ty) => Ok(expr
362365
.reuse_loc(TypeT::Pointer(ty.clone(), PointerKind::Ref))
363366
.into()),

src/hauntedc.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,28 @@ fn mk_binop(binop: BinOp, lhs: Expr, rhs: Expr, loc: Rc<SourceInfo>) -> Expr {
354354
.into()
355355
}
356356

357+
/// Convert a TypeT (as parsed by hauntedc's `type_name`) into a reified
358+
/// CTypeExprT for use as the argument of `sizeof` / `_Alignof`. Returns
359+
/// None if the type cannot be reified (e.g. SLProp, SpecInt, Refine, ...).
360+
fn type_t_to_ctype(ty: &TypeT) -> Option<CTypeExprT> {
361+
Some(match ty {
362+
TypeT::Void => CTypeExprT::Void,
363+
TypeT::Bool => CTypeExprT::Bool,
364+
TypeT::SizeT => CTypeExprT::SizeT,
365+
TypeT::PtrdiffT => CTypeExprT::PtrdiffT,
366+
TypeT::Int { signed, width } => CTypeExprT::Int {
367+
signed: *signed,
368+
width: *width,
369+
},
370+
TypeT::Pointer(inner, _) => {
371+
let inner_ct = type_t_to_ctype(&inner.val)?;
372+
CTypeExprT::Pointer(inner_ct.with_loc(inner.loc.clone()))
373+
}
374+
TypeT::TypeRef(tk) => CTypeExprT::Named(tk.clone()),
375+
_ => return None,
376+
})
377+
}
378+
357379
type Extra<'tokens, 'src> = extra::Err<Rich<'tokens, Token<'src>, SimpleSpan>>;
358380

359381
fn type_parser<
@@ -686,6 +708,48 @@ fn expr_parser<
686708
.with_loc(loc)
687709
.into()
688710
}),
711+
// sizeof(<type>) / sizeof(<type>[N]) and _Alignof(<type>)
712+
select! {
713+
Token::Ident("sizeof") => true,
714+
Token::Ident("_Alignof") => false,
715+
Token::Ident("__alignof__") => false,
716+
}
717+
.padded_by(ws())
718+
.then(
719+
type_name
720+
.clone()
721+
.then(
722+
select! { Token::Integer(i, _) => i }
723+
.delimited_by(punct(Punct::LBracket), punct(Punct::RBracket))
724+
.or_not(),
725+
)
726+
.delimited_by(punct(Punct::LParen), punct(Punct::RParen)),
727+
)
728+
.try_map(move |(is_sizeof, (ty, arr_opt)), span: SimpleSpan| {
729+
let loc = sift.resolve_source_info(&span);
730+
let Some(ct_inner) = type_t_to_ctype(&ty.val) else {
731+
return Err(Rich::custom(
732+
span,
733+
format!("unsupported type in sizeof/_Alignof"),
734+
));
735+
};
736+
let inner_ctype = ct_inner.with_loc(ty.loc.clone());
737+
let ct = match arr_opt {
738+
None => inner_ctype,
739+
Some(n_str) => {
740+
let n = BigInt::from_str(n_str)
741+
.map_err(|e| Rich::custom(span, format!("{}", e)))?;
742+
CTypeExprT::Array(inner_ctype, Rc::new(n)).with_loc(loc.clone())
743+
}
744+
};
745+
let expr_t = if is_sizeof {
746+
ExprT::SizeOf(ct)
747+
} else {
748+
ExprT::AlignOf(ct)
749+
};
750+
Ok::<Expr, Rich<'_, Token<'_>, SimpleSpan>>(expr_t.with_loc(loc).into())
751+
})
752+
.boxed(),
689753
ident
690754
.clone() // TODO: function should be postfix_expression
691755
.then(

src/ir/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,28 @@ pub enum ExprT {
304304
/// Assignment expression: assigns rhs to lhs, evaluates to rhs.
305305
/// Lowered to Assign statement + value in elab pass.
306306
AssignExpr(Rc<Expr>, Rc<Expr>),
307+
/// `sizeof(T)` — translated to an opaque `c_sizeof` call.
308+
SizeOf(Rc<CTypeExpr>),
309+
/// `_Alignof(T)` / `__alignof__(T)` — translated to an opaque `c_alignof` call.
310+
AlignOf(Rc<CTypeExpr>),
307311
Error(Rc<Type>),
308312
}
309313

314+
/// Reified C type used as the argument of [ExprT::SizeOf] / [ExprT::AlignOf].
315+
/// Mirrors the `c_type` inductive declared in `Pulse.Lib.C.Sizeof`.
316+
pub type CTypeExpr = Ast<CTypeExprT>;
317+
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
318+
pub enum CTypeExprT {
319+
Void,
320+
Bool,
321+
SizeT,
322+
PtrdiffT,
323+
Int { signed: bool, width: u32 },
324+
Pointer(Rc<CTypeExpr>),
325+
Array(Rc<CTypeExpr>, Rc<BigInt>),
326+
Named(TypeRefKind),
327+
}
328+
310329
pub type IdentT = str;
311330
pub type Ident = Ast<Rc<IdentT>>;
312331

0 commit comments

Comments
 (0)