Skip to content

Commit 6adb56d

Browse files
coord-eclaude
andauthored
Make seq concatenation operate on sequence tuples (#162)
* Make seq concatenation operate on sequence tuples Rename `ArrayConcat`/`ArrayConcatTerm` to `SeqConcat`/`SeqConcatTerm` and the SMT `concat_int_array` definition to `seq_concat`, and have them carry the two sequences as whole `(array, length)` tuples (`seq1`/`seq2`) rather than four destructured array/length terms. The `seq_concat` definition now takes two tuple parameters and projects their fields internally instead of accepting the array and length as separate parameters, and the select peephole projects the tuple fields accordingly. The `(array, length)` tuple datatype is now always declared for every int-array element sort a `seq_concat` definition is emitted for, so the definition type-checks even when no sequence value of that element sort otherwise appears in the system. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GzapTuen2mKHkkN3B7d8qi * fixup! Make seq concatenation operate on sequence tuples --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent d8a1085 commit 6adb56d

6 files changed

Lines changed: 70 additions & 100 deletions

File tree

src/analyze/annot_fn.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -714,17 +714,9 @@ impl<'a, 'tcx> AnnotFnTranslator<'a, 'tcx> {
714714
let elem_sort = self.adt_arg_type_at(receiver, 0).to_sort();
715715
let t = self.to_term(receiver);
716716
let other = self.to_term(&args[0]);
717-
let a_arr = t.clone().tuple_proj(0);
718-
let a_len = t.tuple_proj(1);
719-
let b_arr = other.clone().tuple_proj(0);
720-
let b_len = other.tuple_proj(1);
721-
let new_arr = chc::Term::array_concat(
722-
elem_sort,
723-
a_arr,
724-
a_len.clone(),
725-
b_arr,
726-
b_len.clone(),
727-
);
717+
let a_len = t.clone().tuple_proj(1);
718+
let b_len = other.clone().tuple_proj(1);
719+
let new_arr = chc::Term::seq_concat(elem_sort, t, other);
728720
let new_len = a_len.add(b_len);
729721
return FormulaOrTerm::Term(chc::Term::tuple(vec![new_arr, new_len]));
730722
}

src/chc.rs

Lines changed: 30 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -437,14 +437,12 @@ impl Function {
437437
}
438438

439439
#[derive(Debug, Clone)]
440-
pub struct ArrayConcatTerm<V = TermVarIdx> {
441-
pub array1: Term<V>,
442-
pub len1: Term<V>,
443-
pub array2: Term<V>,
444-
pub len2: Term<V>,
440+
pub struct SeqConcatTerm<V = TermVarIdx> {
441+
pub seq1: Term<V>,
442+
pub seq2: Term<V>,
445443
}
446444

447-
impl<'a, D, V> Pretty<'a, D, termcolor::ColorSpec> for &ArrayConcatTerm<V>
445+
impl<'a, D, V> Pretty<'a, D, termcolor::ColorSpec> for &SeqConcatTerm<V>
448446
where
449447
V: Var,
450448
D: pretty::DocAllocator<'a, termcolor::ColorSpec>,
@@ -454,44 +452,30 @@ where
454452
allocator
455453
.text("concat")
456454
.append(allocator.line())
457-
.append(self.array1.pretty_atom(allocator))
455+
.append(self.seq1.pretty_atom(allocator))
458456
.append(allocator.text(","))
459457
.append(allocator.line())
460-
.append(self.len1.pretty_atom(allocator))
461-
.append(allocator.text(","))
462-
.append(allocator.line())
463-
.append(self.array2.pretty_atom(allocator))
464-
.append(allocator.text(","))
465-
.append(allocator.line())
466-
.append(self.len2.pretty_atom(allocator))
458+
.append(self.seq2.pretty_atom(allocator))
467459
.parens()
468460
}
469461
}
470462

471-
impl<V> ArrayConcatTerm<V> {
463+
impl<V> SeqConcatTerm<V> {
472464
pub fn iter_args(&self) -> impl Iterator<Item = &Term<V>> {
473-
std::iter::once(&self.array1)
474-
.chain(std::iter::once(&self.len1))
475-
.chain(std::iter::once(&self.array2))
476-
.chain(std::iter::once(&self.len2))
465+
std::iter::once(&self.seq1).chain(std::iter::once(&self.seq2))
477466
}
478467

479468
pub fn iter_args_mut(&mut self) -> impl Iterator<Item = &mut Term<V>> {
480-
std::iter::once(&mut self.array1)
481-
.chain(std::iter::once(&mut self.len1))
482-
.chain(std::iter::once(&mut self.array2))
483-
.chain(std::iter::once(&mut self.len2))
469+
std::iter::once(&mut self.seq1).chain(std::iter::once(&mut self.seq2))
484470
}
485471

486-
pub fn subst_var<F, W>(self, mut f: F) -> ArrayConcatTerm<W>
472+
pub fn subst_var<F, W>(self, mut f: F) -> SeqConcatTerm<W>
487473
where
488474
F: FnMut(V) -> Term<W>,
489475
{
490-
ArrayConcatTerm {
491-
array1: self.array1.subst_var(&mut f),
492-
len1: self.len1.subst_var(&mut f),
493-
array2: self.array2.subst_var(&mut f),
494-
len2: self.len2.subst_var(f),
476+
SeqConcatTerm {
477+
seq1: self.seq1.subst_var(&mut f),
478+
seq2: self.seq2.subst_var(f),
495479
}
496480
}
497481
}
@@ -511,7 +495,7 @@ pub enum Term<V = TermVarIdx> {
511495
MutFinal(Box<Term<V>>),
512496
App(Function, Vec<Term<V>>),
513497
ArrayEmpty(Sort, Sort),
514-
ArrayConcat(Sort, Box<ArrayConcatTerm<V>>),
498+
SeqConcat(Sort, Box<SeqConcatTerm<V>>),
515499
Tuple(Vec<Term<V>>),
516500
TupleProj(Box<Term<V>>, usize),
517501
DatatypeCtor(DatatypeSort, DatatypeSymbol, Vec<Term<V>>),
@@ -564,7 +548,7 @@ where
564548
}
565549
}
566550
Term::ArrayEmpty(_, _) => allocator.text("[]"),
567-
Term::ArrayConcat(_, t) => t.pretty(allocator),
551+
Term::SeqConcat(_, t) => t.pretty(allocator),
568552
Term::Tuple(ts) => {
569553
let separator = allocator.text(",").append(allocator.line());
570554
if ts.len() == 1 {
@@ -629,7 +613,7 @@ impl<V> Term<V> {
629613
Term::App(fun, args.into_iter().map(|t| t.subst_var(&mut f)).collect())
630614
}
631615
Term::ArrayEmpty(s1, s2) => Term::ArrayEmpty(s1, s2),
632-
Term::ArrayConcat(s, t) => Term::ArrayConcat(s, Box::new(t.subst_var(f))),
616+
Term::SeqConcat(s, t) => Term::SeqConcat(s, Box::new(t.subst_var(f))),
633617
Term::Tuple(ts) => Term::Tuple(ts.into_iter().map(|t| t.subst_var(&mut f)).collect()),
634618
Term::TupleProj(t, i) => Term::TupleProj(Box::new(t.subst_var(f)), i),
635619
Term::DatatypeCtor(sort, c_sym, args) => Term::DatatypeCtor(
@@ -677,7 +661,7 @@ impl<V> Term<V> {
677661
fun.sort(args.iter().map(|t| t.sort(&mut var_sort)))
678662
}
679663
Term::ArrayEmpty(index, elem) => Sort::array(index.clone(), elem.clone()),
680-
Term::ArrayConcat(elem, _) => Sort::array(Sort::int(), elem.clone()),
664+
Term::SeqConcat(elem, _) => Sort::array(Sort::int(), elem.clone()),
681665
Term::Tuple(ts) => {
682666
// TODO: remove this
683667
let mut var_sort: Box<dyn FnMut(&V) -> Sort> = Box::new(var_sort);
@@ -705,7 +689,7 @@ impl<V> Term<V> {
705689
Term::MutCurrent(t) => t.fv_impl(),
706690
Term::MutFinal(t) => t.fv_impl(),
707691
Term::App(_, args) => Box::new(args.iter().flat_map(|t| t.fv_impl())),
708-
Term::ArrayConcat(_, t) => Box::new(t.iter_args().flat_map(|t| t.fv_impl())),
692+
Term::SeqConcat(_, t) => Box::new(t.iter_args().flat_map(|t| t.fv_impl())),
709693
Term::Tuple(ts) => Box::new(ts.iter().flat_map(|t| t.fv_impl())),
710694
Term::TupleProj(t, _) => t.fv_impl(),
711695
Term::DatatypeCtor(_, _, args) => Box::new(args.iter().flat_map(|t| t.fv_impl())),
@@ -772,22 +756,8 @@ impl<V> Term<V> {
772756
Term::ArrayEmpty(index, elem)
773757
}
774758

775-
pub fn array_concat(
776-
elem_sort: Sort,
777-
array1: Term<V>,
778-
len1: Term<V>,
779-
array2: Term<V>,
780-
len2: Term<V>,
781-
) -> Self {
782-
Term::ArrayConcat(
783-
elem_sort,
784-
Box::new(ArrayConcatTerm {
785-
array1,
786-
len1,
787-
array2,
788-
len2,
789-
}),
790-
)
759+
pub fn seq_concat(elem_sort: Sort, seq1: Term<V>, seq2: Term<V>) -> Self {
760+
Term::SeqConcat(elem_sort, Box::new(SeqConcatTerm { seq1, seq2 }))
791761
}
792762

793763
pub fn boxed(self) -> Self {
@@ -893,18 +863,21 @@ impl<V> Term<V> {
893863
],
894864
);
895865
}
896-
// Peephole 2: inline one step of the `concat_int_array` recursive definitions to reduce
866+
// Peephole 2: inline one step of the `seq_concat` recursive definitions to reduce
897867
// indexed access to terms over the underlying sequences. The SMT-defined functions are
898868
// still emitted (so the rewrites use exactly their unfolded form), but pcsat can prove
899869
// indexed properties against the inlined ITE for *any* recursion bound, where unfolding
900870
// through `define-fun-rec` would require an inductive invariant pcsat can't find.
901871
//
902-
// `select(concat_int_array(sa, sn, ta, tn), i)
903-
// ↦ ite(i < sn, select(sa, i), select(ta, i - sn))`
904-
if let Term::ArrayConcat(_, t) = self {
905-
let cond = index.clone().lt(t.len1.clone());
906-
let then_ = t.array1.select(index.clone());
907-
let else_ = t.array2.select(index.sub(t.len1));
872+
// `select(seq_concat(s, t), i)
873+
// ↦ ite(i < len(s), select(array(s), i), select(array(t), i - len(s)))`
874+
// where `s`/`t` are `(array, length)` tuples.
875+
if let Term::SeqConcat(_, t) = self {
876+
let SeqConcatTerm { seq1, seq2 } = *t;
877+
let len1 = seq1.clone().tuple_proj(1);
878+
let cond = index.clone().lt(len1.clone());
879+
let then_ = seq1.tuple_proj(0).select(index.clone());
880+
let else_ = seq2.tuple_proj(0).select(index.sub(len1));
908881
return Term::ite(cond, then_, else_);
909882
}
910883
Term::App(Function::SELECT, vec![self, index])

src/chc/format_context.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ fn term_sorts(clause: &chc::Clause, t: &chc::Term, sorts: &mut BTreeSet<chc::Sor
4747
}
4848
}
4949
chc::Term::ArrayEmpty(_, _) => {}
50-
chc::Term::ArrayConcat(_, t) => {
50+
chc::Term::SeqConcat(_, t) => {
5151
for arg in t.iter_args() {
5252
term_sorts(clause, arg, sorts);
5353
}
@@ -269,7 +269,7 @@ fn monomorphize_datatype(
269269

270270
impl FormatContext {
271271
pub fn from_system(system: &chc::System) -> Self {
272-
let sorts = collect_sorts(system);
272+
let mut sorts = collect_sorts(system);
273273
let mut datatypes = system.datatypes.clone();
274274
for sort in sorts.iter().flat_map(|s| s.as_datatype()) {
275275
if let Some(mono_datatype) = monomorphize_datatype(sort, &datatypes) {
@@ -283,6 +283,14 @@ impl FormatContext {
283283
_ => None,
284284
})
285285
.collect();
286+
// The `seq_concat` definitions operate on `(array, length)` sequence tuples, so
287+
// make sure that tuple datatype is declared for every element sort we emit one for
288+
for elem in &int_array_elem_sorts {
289+
sorts.insert(chc::Sort::tuple(vec![
290+
chc::Sort::array(chc::Sort::int(), elem.clone()),
291+
chc::Sort::int(),
292+
]));
293+
}
286294
let datatypes: Vec<_> = sorts
287295
.into_iter()
288296
.flat_map(builtin_sort_datatype)
@@ -369,9 +377,9 @@ impl FormatContext {
369377
format!("matcher_pred<{}>", self.fmt_datatype_symbol(sym))
370378
}
371379

372-
pub fn concat_int_array(&self, elem: &chc::Sort) -> impl std::fmt::Display {
380+
pub fn seq_concat(&self, elem: &chc::Sort) -> impl std::fmt::Display {
373381
let elem = SortSymbol::new(elem);
374-
format!("concat_int_array<{}>", elem)
382+
format!("seq_concat<{}>", elem)
375383
}
376384

377385
fn fmt_sort_impl(&self, sort: &chc::Sort) -> Box<dyn std::fmt::Display> {

src/chc/smtlib2.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,8 @@ impl<'ctx, 'a> std::fmt::Display for Term<'ctx, 'a> {
169169
Term::new(self.ctx, self.clause, &default)
170170
)
171171
}
172-
chc::Term::ArrayConcat(elem, t) => {
173-
let name = self.ctx.concat_int_array(elem);
172+
chc::Term::SeqConcat(elem, t) => {
173+
let name = self.ctx.seq_concat(elem);
174174
write!(
175175
f,
176176
"({} {})",
@@ -639,17 +639,26 @@ impl<'a> std::fmt::Display for System<'a> {
639639
}
640640

641641
for elem in self.ctx.int_array_elem_sorts() {
642-
let name = self.ctx.concat_int_array(elem);
642+
let name = self.ctx.seq_concat(elem);
643643
let elem_ty = self.ctx.fmt_sort(elem);
644+
// The sequences are passed as `(array, length)` tuples
645+
let seq_fields = [
646+
chc::Sort::array(chc::Sort::int(), elem.clone()),
647+
chc::Sort::int(),
648+
];
649+
let seq_ty = self.ctx.fmt_sort(&chc::Sort::tuple(seq_fields.to_vec()));
650+
let ctor = self.ctx.tuple_ctor(&seq_fields);
651+
let array = self.ctx.tuple_proj(&seq_fields, 0);
652+
let len = self.ctx.tuple_proj(&seq_fields, 1);
644653
writeln!(
645654
f,
646655
"(define-fun-rec {name} \
647-
((sa (Array Int {elem_ty})) (sn Int) (ta (Array Int {elem_ty})) (tn Int)) \
656+
((s {seq_ty}) (t {seq_ty})) \
648657
(Array Int {elem_ty}) \
649-
(ite (<= tn 0) sa \
650-
(store ({name} sa sn ta (- tn 1)) \
651-
(+ sn (- tn 1)) \
652-
(select ta (- tn 1)))))\n",
658+
(ite (<= ({len} t) 0) ({array} s) \
659+
(store ({name} s ({ctor} ({array} t) (- ({len} t) 1))) \
660+
(+ ({len} s) (- ({len} t) 1)) \
661+
(select ({array} t) (- ({len} t) 1)))))\n",
653662
)?;
654663
}
655664

src/chc/unbox.rs

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,11 @@
22
33
use super::*;
44

5-
fn unbox_array_concat_term(t: ArrayConcatTerm) -> ArrayConcatTerm {
6-
let ArrayConcatTerm {
7-
array1,
8-
len1,
9-
array2,
10-
len2,
11-
} = t;
12-
let array1 = unbox_term(array1);
13-
let len1 = unbox_term(len1);
14-
let array2 = unbox_term(array2);
15-
let len2 = unbox_term(len2);
16-
ArrayConcatTerm {
17-
array1,
18-
len1,
19-
array2,
20-
len2,
21-
}
5+
fn unbox_seq_concat_term(t: SeqConcatTerm) -> SeqConcatTerm {
6+
let SeqConcatTerm { seq1, seq2 } = t;
7+
let seq1 = unbox_term(seq1);
8+
let seq2 = unbox_term(seq2);
9+
SeqConcatTerm { seq1, seq2 }
2210
}
2311

2412
fn unbox_term(term: Term) -> Term {
@@ -31,8 +19,8 @@ fn unbox_term(term: Term) -> Term {
3119
Term::MutFinal(t) => Term::MutFinal(Box::new(unbox_term(*t))),
3220
Term::App(fun, args) => Term::App(fun, args.into_iter().map(unbox_term).collect()),
3321
Term::ArrayEmpty(s1, s2) => Term::ArrayEmpty(unbox_sort(s1), unbox_sort(s2)),
34-
Term::ArrayConcat(s, t) => {
35-
Term::ArrayConcat(unbox_sort(s), Box::new(unbox_array_concat_term(*t)))
22+
Term::SeqConcat(s, t) => {
23+
Term::SeqConcat(unbox_sort(s), Box::new(unbox_seq_concat_term(*t)))
3624
}
3725
Term::Tuple(ts) => Term::Tuple(ts.into_iter().map(unbox_term).collect()),
3826
Term::TupleProj(t, i) => Term::TupleProj(Box::new(unbox_term(*t)), i),

src/rty.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1978,7 +1978,7 @@ fn subst_ty_params_in_term<T, V>(term: &mut chc::Term<V>, subst: &TypeParamSubst
19781978
subst_ty_params_in_sort(s1, subst);
19791979
subst_ty_params_in_sort(s2, subst);
19801980
}
1981-
chc::Term::ArrayConcat(sort, t) => {
1981+
chc::Term::SeqConcat(sort, t) => {
19821982
subst_ty_params_in_sort(sort, subst);
19831983
for arg in t.iter_args_mut() {
19841984
subst_ty_params_in_term(arg, subst);

0 commit comments

Comments
 (0)