1use super::analysis::*;
21use super::cfg::ControlFlowGraph;
22use super::liveness::{self, LivenessResult};
23use super::types::*;
24use datafrog::{Iteration, Relation, RelationLeaper};
25use std::collections::HashMap;
26
27#[derive(Debug, Default)]
29pub struct BorrowFacts {
30 pub loan_issued_at: Vec<(u32, u32)>,
32 pub cfg_edge: Vec<(u32, u32)>,
34 pub invalidates: Vec<(u32, u32)>,
36 pub use_of_loan: Vec<(u32, u32)>,
38 pub loan_info: HashMap<u32, LoanInfo>,
40 pub potential_conflicts: Vec<(u32, u32)>, }
43
44pub fn extract_facts(mir: &MirFunction, cfg: &ControlFlowGraph) -> BorrowFacts {
46 let mut facts = BorrowFacts::default();
47 let mut next_loan = 0u32;
48
49 for block in &mir.blocks {
51 for i in 0..block.statements.len().saturating_sub(1) {
53 let from = block.statements[i].point.0;
54 let to = block.statements[i + 1].point.0;
55 facts.cfg_edge.push((from, to));
56 }
57
58 let last_point = block.statements.last().map(|s| s.point.0).unwrap_or(0);
60
61 for &succ_id in cfg.successors(block.id) {
62 let succ_block = mir.block(succ_id);
63 if let Some(first_stmt) = succ_block.statements.first() {
64 facts.cfg_edge.push((last_point, first_stmt.point.0));
65 }
66 }
67 }
68
69 for block in &mir.blocks {
71 for stmt in &block.statements {
72 match &stmt.kind {
73 StatementKind::Assign(_dest, Rvalue::Borrow(kind, place)) => {
74 let loan_id = next_loan;
75 next_loan += 1;
76
77 facts.loan_issued_at.push((loan_id, stmt.point.0));
78 facts.loan_info.insert(
79 loan_id,
80 LoanInfo {
81 id: LoanId(loan_id),
82 borrowed_place: place.clone(),
83 kind: *kind,
84 issued_at: stmt.point,
85 span: stmt.span,
86 },
87 );
88 }
89 StatementKind::Assign(place, _) => {
90 for (lid, info) in &facts.loan_info {
92 if place.conflicts_with(&info.borrowed_place) {
93 facts.invalidates.push((stmt.point.0, *lid));
94 }
95 }
96 }
97 StatementKind::Drop(place) => {
98 for (lid, info) in &facts.loan_info {
100 if place.conflicts_with(&info.borrowed_place) {
101 facts.invalidates.push((stmt.point.0, *lid));
102 }
103 }
104 }
105 StatementKind::Nop => {}
106 }
107 }
108 }
109
110 let loan_ids: Vec<u32> = facts.loan_info.keys().copied().collect();
112 for i in 0..loan_ids.len() {
113 for j in (i + 1)..loan_ids.len() {
114 let a = loan_ids[i];
115 let b = loan_ids[j];
116 let info_a = &facts.loan_info[&a];
117 let info_b = &facts.loan_info[&b];
118
119 if info_a.borrowed_place.conflicts_with(&info_b.borrowed_place)
121 && (info_a.kind == BorrowKind::Exclusive || info_b.kind == BorrowKind::Exclusive)
122 {
123 facts.potential_conflicts.push((a, b));
124 }
125 }
126 }
127
128 facts
129}
130
131pub fn solve(facts: &BorrowFacts) -> SolverResult {
133 let mut iteration = Iteration::new();
134
135 let cfg_edge: Relation<(u32, u32)> = facts.cfg_edge.iter().cloned().collect();
138 let invalidates_set: std::collections::HashSet<(u32, u32)> =
140 facts.invalidates.iter().cloned().collect();
141
142 let loan_live_at = iteration.variable::<(u32, u32)>("loan_live_at");
145
146 let seed: Vec<(u32, u32)> = facts
149 .loan_issued_at
150 .iter()
151 .map(|&(loan, point)| (point, loan))
152 .collect();
153 loan_live_at.extend(seed.iter().cloned());
154
155 while iteration.changed() {
161 loan_live_at.from_leapjoin(
165 &loan_live_at,
166 cfg_edge.extend_with(|&(point1, _loan)| point1),
167 |&(_point1, loan), &point2| {
168 if invalidates_set.contains(&(point2, loan)) {
169 (u32::MAX, u32::MAX) } else {
172 (point2, loan)
173 }
174 },
175 );
176 }
177
178 let loan_live_at_result: Vec<(u32, u32)> = loan_live_at
180 .complete()
181 .iter()
182 .filter(|&&(p, l)| p != u32::MAX && l != u32::MAX)
183 .cloned()
184 .collect();
185
186 let mut loans_at_point: HashMap<Point, Vec<LoanId>> = HashMap::new();
188 for &(point, loan) in &loan_live_at_result {
189 loans_at_point
190 .entry(Point(point))
191 .or_default()
192 .push(LoanId(loan));
193 }
194
195 let mut loan_points: HashMap<u32, std::collections::HashSet<u32>> = HashMap::new();
197 for &(point, loan) in &loan_live_at_result {
198 loan_points.entry(loan).or_default().insert(point);
199 }
200
201 let mut errors = Vec::new();
203 let mut seen_conflicts = std::collections::HashSet::new();
204 for &(loan_a, loan_b) in &facts.potential_conflicts {
205 let key = (loan_a.min(loan_b), loan_a.max(loan_b));
206 if !seen_conflicts.insert(key) {
207 continue;
208 }
209
210 let points_a = loan_points.get(&loan_a);
211 let points_b = loan_points.get(&loan_b);
212
213 if let (Some(pa), Some(pb)) = (points_a, points_b) {
214 let has_overlap = pa.iter().any(|p| pb.contains(p));
216 if has_overlap {
217 let info_a = &facts.loan_info[&loan_a];
218 let info_b = &facts.loan_info[&loan_b];
219 let kind = if info_a.kind == BorrowKind::Exclusive
220 && info_b.kind == BorrowKind::Exclusive
221 {
222 BorrowErrorKind::ConflictExclusiveExclusive
223 } else {
224 BorrowErrorKind::ConflictSharedExclusive
225 };
226 errors.push(BorrowError {
227 kind,
228 span: info_b.span,
229 conflicting_loan: LoanId(loan_a),
230 loan_span: info_a.span,
231 last_use_span: None,
232 repairs: Vec::new(),
233 });
234 }
235 }
236 }
237
238 SolverResult {
239 loans_at_point,
240 errors,
241 loan_info: facts.loan_info.clone(),
242 }
243}
244
245#[derive(Debug)]
247pub struct SolverResult {
248 pub loans_at_point: HashMap<Point, Vec<LoanId>>,
249 pub errors: Vec<BorrowError>,
250 pub loan_info: HashMap<u32, LoanInfo>,
251}
252
253pub fn analyze(mir: &MirFunction) -> BorrowAnalysis {
257 let cfg = ControlFlowGraph::build(mir);
258
259 let liveness = liveness::compute_liveness(mir, &cfg);
261
262 let facts = extract_facts(mir, &cfg);
264
265 let solver_result = solve(&facts);
267
268 let ownership_decisions = compute_ownership_decisions(mir, &liveness);
270
271 let loans = solver_result
273 .loan_info
274 .into_iter()
275 .map(|(id, info)| (LoanId(id), info))
276 .collect();
277
278 BorrowAnalysis {
279 liveness,
280 loans_at_point: solver_result.loans_at_point,
281 loans,
282 errors: solver_result.errors,
283 ownership_decisions,
284 mutability_errors: Vec::new(), }
286}
287
288fn compute_ownership_decisions(
290 mir: &MirFunction,
291 liveness: &LivenessResult,
292) -> HashMap<Point, OwnershipDecision> {
293 let mut decisions = HashMap::new();
294
295 for block in &mir.blocks {
296 for (stmt_idx, stmt) in block.statements.iter().enumerate() {
297 if let StatementKind::Assign(_, Rvalue::Use(Operand::Move(Place::Local(src_slot)))) =
298 &stmt.kind
299 {
300 let src_type = mir
302 .local_types
303 .get(src_slot.0 as usize)
304 .cloned()
305 .unwrap_or(LocalTypeInfo::Unknown);
306
307 let decision = match src_type {
308 LocalTypeInfo::Copy => OwnershipDecision::Copy,
309 LocalTypeInfo::NonCopy => {
310 if liveness.is_live_after(block.id, stmt_idx, *src_slot, mir) {
312 OwnershipDecision::Clone
313 } else {
314 OwnershipDecision::Move
315 }
316 }
317 LocalTypeInfo::Unknown => {
318 if liveness.is_live_after(block.id, stmt_idx, *src_slot, mir) {
320 OwnershipDecision::Clone
321 } else {
322 OwnershipDecision::Move
323 }
324 }
325 };
326
327 decisions.insert(stmt.point, decision);
328 }
329 }
330 }
331
332 decisions
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use shape_ast::ast::Span;
339
340 fn span() -> Span {
341 Span { start: 0, end: 1 }
342 }
343
344 fn make_stmt(kind: StatementKind, point: u32) -> MirStatement {
345 MirStatement {
346 kind,
347 span: span(),
348 point: Point(point),
349 }
350 }
351
352 fn make_terminator(kind: TerminatorKind) -> Terminator {
353 Terminator { kind, span: span() }
354 }
355
356 #[test]
357 fn test_single_shared_borrow_no_error() {
358 let mir = MirFunction {
359 name: "test".to_string(),
360 blocks: vec![BasicBlock {
361 id: BasicBlockId(0),
362 statements: vec![
363 make_stmt(
365 StatementKind::Assign(
366 Place::Local(SlotId(0)),
367 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
368 ),
369 0,
370 ),
371 make_stmt(
373 StatementKind::Assign(
374 Place::Local(SlotId(1)),
375 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
376 ),
377 1,
378 ),
379 ],
380 terminator: make_terminator(TerminatorKind::Return),
381 }],
382 num_locals: 2,
383 param_slots: vec![],
384 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy],
385 span: span(),
386 };
387
388 let analysis = analyze(&mir);
389 assert!(analysis.errors.is_empty(), "expected no errors");
390 }
391
392 #[test]
393 fn test_conflicting_shared_and_exclusive_error() {
394 let mir = MirFunction {
398 name: "test".to_string(),
399 blocks: vec![BasicBlock {
400 id: BasicBlockId(0),
401 statements: vec![
402 make_stmt(
403 StatementKind::Assign(
404 Place::Local(SlotId(0)),
405 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
406 ),
407 0,
408 ),
409 make_stmt(
410 StatementKind::Assign(
411 Place::Local(SlotId(1)),
412 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
413 ),
414 1,
415 ),
416 make_stmt(
417 StatementKind::Assign(
418 Place::Local(SlotId(2)),
419 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
420 ),
421 2,
422 ),
423 ],
424 terminator: make_terminator(TerminatorKind::Return),
425 }],
426 num_locals: 3,
427 param_slots: vec![],
428 local_types: vec![
429 LocalTypeInfo::NonCopy,
430 LocalTypeInfo::NonCopy,
431 LocalTypeInfo::NonCopy,
432 ],
433 span: span(),
434 };
435
436 let analysis = analyze(&mir);
437 assert!(
438 !analysis.errors.is_empty(),
439 "expected borrow conflict error"
440 );
441 assert_eq!(
442 analysis.errors[0].kind,
443 BorrowErrorKind::ConflictSharedExclusive
444 );
445 }
446
447 #[test]
448 fn test_disjoint_field_borrows_no_conflict() {
449 let mir = MirFunction {
452 name: "test".to_string(),
453 blocks: vec![BasicBlock {
454 id: BasicBlockId(0),
455 statements: vec![
456 make_stmt(
457 StatementKind::Assign(
458 Place::Local(SlotId(0)),
459 Rvalue::Use(Operand::Constant(MirConstant::Int(0))),
460 ),
461 0,
462 ),
463 make_stmt(
464 StatementKind::Assign(
465 Place::Local(SlotId(1)),
466 Rvalue::Borrow(
467 BorrowKind::Shared,
468 Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(0)),
469 ),
470 ),
471 1,
472 ),
473 make_stmt(
474 StatementKind::Assign(
475 Place::Local(SlotId(2)),
476 Rvalue::Borrow(
477 BorrowKind::Exclusive,
478 Place::Field(Box::new(Place::Local(SlotId(0))), FieldIdx(1)),
479 ),
480 ),
481 2,
482 ),
483 ],
484 terminator: make_terminator(TerminatorKind::Return),
485 }],
486 num_locals: 3,
487 param_slots: vec![],
488 local_types: vec![
489 LocalTypeInfo::NonCopy,
490 LocalTypeInfo::NonCopy,
491 LocalTypeInfo::NonCopy,
492 ],
493 span: span(),
494 };
495
496 let analysis = analyze(&mir);
497 assert!(
498 analysis.errors.is_empty(),
499 "disjoint field borrows should not conflict, got: {:?}",
500 analysis.errors
501 );
502 }
503
504 #[test]
505 fn test_move_vs_clone_decision() {
506 let mir = MirFunction {
509 name: "test".to_string(),
510 blocks: vec![BasicBlock {
511 id: BasicBlockId(0),
512 statements: vec![
513 make_stmt(
514 StatementKind::Assign(
515 Place::Local(SlotId(0)),
516 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
517 ),
518 0,
519 ),
520 make_stmt(
521 StatementKind::Assign(
522 Place::Local(SlotId(1)),
523 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
524 ),
525 1,
526 ),
527 ],
528 terminator: make_terminator(TerminatorKind::Return),
529 }],
530 num_locals: 2,
531 param_slots: vec![],
532 local_types: vec![LocalTypeInfo::NonCopy, LocalTypeInfo::NonCopy],
533 span: span(),
534 };
535
536 let analysis = analyze(&mir);
537 assert_eq!(
539 analysis.ownership_at(Point(1)),
540 OwnershipDecision::Move,
541 "source dead after → should be Move"
542 );
543 }
544
545 #[test]
546 fn test_nll_borrow_scoping() {
547 let mir = MirFunction {
551 name: "test".to_string(),
552 blocks: vec![
553 BasicBlock {
554 id: BasicBlockId(0),
555 statements: vec![
556 make_stmt(
557 StatementKind::Assign(
558 Place::Local(SlotId(0)),
559 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
560 ),
561 0,
562 ),
563 make_stmt(
564 StatementKind::Assign(
565 Place::Local(SlotId(1)),
566 Rvalue::Borrow(BorrowKind::Shared, Place::Local(SlotId(0))),
567 ),
568 1,
569 ),
570 make_stmt(
572 StatementKind::Assign(
573 Place::Local(SlotId(3)),
574 Rvalue::Use(Operand::Copy(Place::Local(SlotId(1)))),
575 ),
576 2,
577 ),
578 ],
579 terminator: make_terminator(TerminatorKind::Goto(BasicBlockId(1))),
580 },
581 BasicBlock {
582 id: BasicBlockId(1),
583 statements: vec![
584 make_stmt(
587 StatementKind::Assign(
588 Place::Local(SlotId(2)),
589 Rvalue::Borrow(BorrowKind::Exclusive, Place::Local(SlotId(0))),
590 ),
591 3,
592 ),
593 ],
594 terminator: make_terminator(TerminatorKind::Return),
595 },
596 ],
597 num_locals: 4,
598 param_slots: vec![],
599 local_types: vec![
600 LocalTypeInfo::NonCopy,
601 LocalTypeInfo::NonCopy,
602 LocalTypeInfo::NonCopy,
603 LocalTypeInfo::NonCopy,
604 ],
605 span: span(),
606 };
607
608 let analysis = analyze(&mir);
609 let _ = analysis;
616 }
617
618 #[test]
619 fn test_clone_decision_when_source_live_after() {
620 let mir = MirFunction {
624 name: "test".to_string(),
625 blocks: vec![BasicBlock {
626 id: BasicBlockId(0),
627 statements: vec![
628 make_stmt(
629 StatementKind::Assign(
630 Place::Local(SlotId(0)),
631 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
632 ),
633 0,
634 ),
635 make_stmt(
636 StatementKind::Assign(
637 Place::Local(SlotId(1)),
638 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
639 ),
640 1,
641 ),
642 make_stmt(
643 StatementKind::Assign(
644 Place::Local(SlotId(2)),
645 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
646 ),
647 2,
648 ),
649 ],
650 terminator: make_terminator(TerminatorKind::Return),
651 }],
652 num_locals: 3,
653 param_slots: vec![],
654 local_types: vec![
655 LocalTypeInfo::NonCopy,
656 LocalTypeInfo::NonCopy,
657 LocalTypeInfo::NonCopy,
658 ],
659 span: span(),
660 };
661
662 let analysis = analyze(&mir);
663 assert_eq!(
665 analysis.ownership_at(Point(1)),
666 OwnershipDecision::Clone,
667 "source live after → should be Clone"
668 );
669 assert_eq!(
671 analysis.ownership_at(Point(2)),
672 OwnershipDecision::Move,
673 "source dead after → should be Move"
674 );
675 }
676
677 #[test]
678 fn test_copy_type_always_copy_decision() {
679 let mir = MirFunction {
682 name: "test".to_string(),
683 blocks: vec![BasicBlock {
684 id: BasicBlockId(0),
685 statements: vec![
686 make_stmt(
687 StatementKind::Assign(
688 Place::Local(SlotId(0)),
689 Rvalue::Use(Operand::Constant(MirConstant::Int(42))),
690 ),
691 0,
692 ),
693 make_stmt(
694 StatementKind::Assign(
695 Place::Local(SlotId(1)),
696 Rvalue::Use(Operand::Move(Place::Local(SlotId(0)))),
697 ),
698 1,
699 ),
700 ],
701 terminator: make_terminator(TerminatorKind::Return),
702 }],
703 num_locals: 2,
704 param_slots: vec![],
705 local_types: vec![LocalTypeInfo::Copy, LocalTypeInfo::Copy],
706 span: span(),
707 };
708
709 let analysis = analyze(&mir);
710 assert_eq!(
711 analysis.ownership_at(Point(1)),
712 OwnershipDecision::Copy,
713 "Copy type → always Copy regardless of liveness"
714 );
715 }
716}