|
30 | 30 | // |
31 | 31 | // Any occurrence of the rule in the remaining loops is replaced with the |
32 | 32 | // alternate definition obtained by splitting the loop that witnessed the |
33 | | -// redundancy. After substitution, every loop is normalized to a cyclically |
34 | | -// reduced left-canonical form. The loop witnessing the redundancy normalizes |
35 | | -// to the empty loop and is deleted. |
| 33 | +// redundancy. |
36 | 34 | // |
37 | 35 | // Iterating this process eventually produces a minimal set of rewrite rules. |
38 | 36 | // |
39 | 37 | // For a description of the general algorithm, see "A Homotopical Completion |
40 | 38 | // Procedure with Applications to Coherence of Monoids", |
41 | 39 | // https://hal.inria.fr/hal-00818253. |
42 | 40 | // |
43 | | -// The idea of computing a left-canonical form for rewrite loops is from |
44 | | -// "Homotopy reduction systems for monoid presentations", |
45 | | -// https://www.sciencedirect.com/science/article/pii/S0022404997000959 |
46 | | -// |
47 | 41 | // Note that in the world of Swift, rewrite rules for introducing associated |
48 | 42 | // type symbols are marked 'permanent'; they are always re-added when a new |
49 | 43 | // rewrite system is built from a minimal generic signature, so instead of |
@@ -321,232 +315,6 @@ bool RewritePath::replaceRuleWithPath(unsigned ruleID, |
321 | 315 | return true; |
322 | 316 | } |
323 | 317 |
|
324 | | -/// Returns true if this rewrite step is an inverse of \p other |
325 | | -/// (and vice versa). |
326 | | -bool RewriteStep::isInverseOf(const RewriteStep &other) const { |
327 | | - if (Kind != other.Kind) |
328 | | - return false; |
329 | | - |
330 | | - if (StartOffset != other.StartOffset) |
331 | | - return false; |
332 | | - |
333 | | - if (Inverse != !other.Inverse) |
334 | | - return false; |
335 | | - |
336 | | - switch (Kind) { |
337 | | - case RewriteStep::ApplyRewriteRule: |
338 | | - return RuleID == other.RuleID; |
339 | | - |
340 | | - case RewriteStep::AdjustConcreteType: |
341 | | - return true; |
342 | | - |
343 | | - case RewriteStep::Shift: |
344 | | - return true; |
345 | | - |
346 | | - case RewriteStep::Decompose: |
347 | | - return RuleID == other.RuleID; |
348 | | - |
349 | | - case RewriteStep::ConcreteConformance: |
350 | | - case RewriteStep::SuperclassConformance: |
351 | | - return true; |
352 | | - } |
353 | | - |
354 | | - assert(EndOffset == other.EndOffset && "Bad whiskering?"); |
355 | | - return true; |
356 | | -} |
357 | | - |
358 | | -bool RewriteStep::maybeSwapRewriteSteps(RewriteStep &other, |
359 | | - const RewriteSystem &system) { |
360 | | - if (Kind != RewriteStep::ApplyRewriteRule || |
361 | | - other.Kind != RewriteStep::ApplyRewriteRule) |
362 | | - return false; |
363 | | - |
364 | | - // Two rewrite steps are _orthogonal_ if they rewrite disjoint subterms |
365 | | - // in context. Orthogonal rewrite steps commute, so we can canonicalize |
366 | | - // a path by placing the left-most step first. |
367 | | - // |
368 | | - // Eg, A.U.B.(X => Y).C ⊗ A.(U => V).B.Y == A.(U => V).B.X ⊗ A.V.B.(X => Y). |
369 | | - // |
370 | | - // Or, in diagram form. We want to turn this: |
371 | | - // |
372 | | - // ----- time -----> |
373 | | - // +---------+---------+ |
374 | | - // | A | A | |
375 | | - // +---------+---------+ |
376 | | - // | U | U ==> V | |
377 | | - // +---------+---------+ |
378 | | - // | B | B | |
379 | | - // +---------+---------+ |
380 | | - // | X ==> Y | Y | |
381 | | - // +---------+---------+ |
382 | | - // | C | C | |
383 | | - // +---------+---------+ |
384 | | - // |
385 | | - // Into this: |
386 | | - // |
387 | | - // +---------+---------+ |
388 | | - // | A | A | |
389 | | - // +---------+---------+ |
390 | | - // | U ==> V | V | |
391 | | - // +---------+---------+ |
392 | | - // | B | B | |
393 | | - // +---------+---------+ |
394 | | - // | X | X ==> Y | |
395 | | - // +---------+---------+ |
396 | | - // | C | C | |
397 | | - // +---------+---------+ |
398 | | - // |
399 | | - // Note that |
400 | | - // |
401 | | - // StartOffset == |A|+|U|+|B| |
402 | | - // EndOffset = |C| |
403 | | - // |
404 | | - // other.StartOffset = |A| |
405 | | - // other.EndOffset = |B|+|Y|+|C| |
406 | | - // |
407 | | - // After interchange, we adjust: |
408 | | - // |
409 | | - // StartOffset = |A| |
410 | | - // EndOffset = |B|+|X|+|C| |
411 | | - // |
412 | | - // other.StartOffset = |A|+|V|+|B| |
413 | | - // other.EndOffset = |C| |
414 | | - |
415 | | - const auto &rule = system.getRule(RuleID); |
416 | | - auto lhs = (Inverse ? rule.getRHS() : rule.getLHS()); |
417 | | - auto rhs = (Inverse ? rule.getLHS() : rule.getRHS()); |
418 | | - |
419 | | - const auto &otherRule = system.getRule(other.RuleID); |
420 | | - auto otherLHS = (other.Inverse ? otherRule.getRHS() : otherRule.getLHS()); |
421 | | - auto otherRHS = (other.Inverse ? otherRule.getLHS() : otherRule.getRHS()); |
422 | | - |
423 | | - if (StartOffset < other.StartOffset + otherLHS.size()) |
424 | | - return false; |
425 | | - |
426 | | - std::swap(*this, other); |
427 | | - EndOffset += (lhs.size() - rhs.size()); |
428 | | - other.StartOffset += (otherRHS.size() - otherLHS.size()); |
429 | | - |
430 | | - return true; |
431 | | -} |
432 | | - |
433 | | -/// Cancels out adjacent rewrite steps that are inverses of each other. |
434 | | -/// This does not change either endpoint of the path, and the path does |
435 | | -/// not necessarily need to be a loop. |
436 | | -bool RewritePath::computeFreelyReducedPath() { |
437 | | - SmallVector<RewriteStep, 4> newSteps; |
438 | | - bool changed = false; |
439 | | - |
440 | | - for (const auto &step : Steps) { |
441 | | - if (!newSteps.empty() && |
442 | | - newSteps.back().isInverseOf(step)) { |
443 | | - changed = true; |
444 | | - newSteps.pop_back(); |
445 | | - continue; |
446 | | - } |
447 | | - |
448 | | - newSteps.push_back(step); |
449 | | - } |
450 | | - |
451 | | - std::swap(newSteps, Steps); |
452 | | - return changed; |
453 | | -} |
454 | | - |
455 | | -/// Given a path that is a loop around the given basepoint, cancels out |
456 | | -/// pairs of terms from the ends that are inverses of each other, applying |
457 | | -/// the corresponding translation to the basepoint. |
458 | | -/// |
459 | | -/// For example, consider this loop with basepoint 'X': |
460 | | -/// |
461 | | -/// (X => Y.A) * (Y.A => Y.B) * Y.(B => A) * (Y.A => X) |
462 | | -/// |
463 | | -/// The first step is the inverse of the last step, so the cyclic |
464 | | -/// reduction is the loop (Y.A => Y.B) * Y.(B => A), with a new |
465 | | -/// basepoint 'Y.A'. |
466 | | -bool RewritePath::computeCyclicallyReducedLoop(MutableTerm &basepoint, |
467 | | - const RewriteSystem &system) { |
468 | | - RewritePathEvaluator evaluator(basepoint); |
469 | | - unsigned count = 0; |
470 | | - |
471 | | - while (2 * count + 1 < size()) { |
472 | | - auto left = Steps[count]; |
473 | | - auto right = Steps[Steps.size() - count - 1]; |
474 | | - if (!left.isInverseOf(right)) |
475 | | - break; |
476 | | - |
477 | | - // Update the basepoint by applying the first step in the path. |
478 | | - evaluator.apply(left, system); |
479 | | - |
480 | | - ++count; |
481 | | - } |
482 | | - |
483 | | - std::rotate(Steps.begin(), Steps.begin() + count, Steps.end() - count); |
484 | | - Steps.erase(Steps.end() - 2 * count, Steps.end()); |
485 | | - |
486 | | - basepoint = evaluator.getCurrentTerm(); |
487 | | - return count > 0; |
488 | | -} |
489 | | - |
490 | | -/// Apply the interchange rule until fixed point (see maybeSwapRewriteSteps()). |
491 | | -bool RewritePath::computeLeftCanonicalForm(const RewriteSystem &system) { |
492 | | - bool changed = false; |
493 | | - |
494 | | - for (unsigned i = 1, e = Steps.size(); i < e; ++i) { |
495 | | - auto &prevStep = Steps[i - 1]; |
496 | | - auto &step = Steps[i]; |
497 | | - |
498 | | - if (prevStep.maybeSwapRewriteSteps(step, system)) |
499 | | - changed = true; |
500 | | - } |
501 | | - |
502 | | - return changed; |
503 | | -} |
504 | | - |
505 | | -/// Compute cyclically-reduced left-canonical normal form of a loop. |
506 | | -void RewriteLoop::normalize(const RewriteSystem &system) { |
507 | | - // FIXME: This can be more efficient. |
508 | | - bool changed; |
509 | | - do { |
510 | | - changed = false; |
511 | | - changed |= Path.computeFreelyReducedPath(); |
512 | | - changed |= Path.computeCyclicallyReducedLoop(Basepoint, system); |
513 | | - changed |= Path.computeLeftCanonicalForm(system); |
514 | | - } while (changed); |
515 | | -} |
516 | | - |
517 | | -/// A loop is "in context" if every rewrite step has a left or right whisker. |
518 | | -bool RewriteLoop::isInContext(const RewriteSystem &system) const { |
519 | | - RewritePathEvaluator evaluator(Basepoint); |
520 | | - |
521 | | - unsigned minStartOffset = (unsigned) -1; |
522 | | - unsigned minEndOffset = (unsigned) -1; |
523 | | - |
524 | | - for (const auto &step : Path) { |
525 | | - if (!evaluator.isInContext()) { |
526 | | - switch (step.Kind) { |
527 | | - case RewriteStep::ApplyRewriteRule: |
528 | | - minStartOffset = std::min(minStartOffset, step.StartOffset); |
529 | | - minEndOffset = std::min(minEndOffset, step.EndOffset); |
530 | | - break; |
531 | | - |
532 | | - case RewriteStep::AdjustConcreteType: |
533 | | - case RewriteStep::Shift: |
534 | | - case RewriteStep::Decompose: |
535 | | - case RewriteStep::ConcreteConformance: |
536 | | - case RewriteStep::SuperclassConformance: |
537 | | - break; |
538 | | - } |
539 | | - |
540 | | - if (minStartOffset == 0 && minEndOffset == 0) |
541 | | - break; |
542 | | - } |
543 | | - |
544 | | - evaluator.apply(step, system); |
545 | | - } |
546 | | - |
547 | | - return (minStartOffset > 0 || minEndOffset > 0); |
548 | | -} |
549 | | - |
550 | 318 | /// Check if a rewrite rule is a candidate for deletion in this pass of the |
551 | 319 | /// minimization algorithm. |
552 | 320 | bool RewriteSystem:: |
@@ -688,39 +456,6 @@ void RewriteSystem::deleteRule(unsigned ruleID, |
688 | 456 | if (!changed) |
689 | 457 | continue; |
690 | 458 |
|
691 | | - unsigned size = loop.Path.size(); |
692 | | - |
693 | | - loop.normalize(*this); |
694 | | - |
695 | | - if (Debug.contains(DebugFlags::HomotopyReduction)) { |
696 | | - if (size != loop.Path.size()) { |
697 | | - llvm::dbgs() << "** Note: loop normalization eliminated " |
698 | | - << (size - loop.Path.size()) << " steps\n"; |
699 | | - } |
700 | | - } |
701 | | - |
702 | | - if (loop.Path.empty()) { |
703 | | - if (Debug.contains(DebugFlags::HomotopyReduction)) { |
704 | | - llvm::dbgs() << "** Deleting trivial loop at basepoint "; |
705 | | - llvm::dbgs() << loop.Basepoint << "\n"; |
706 | | - } |
707 | | - |
708 | | - loop.markDeleted(); |
709 | | - continue; |
710 | | - } |
711 | | - |
712 | | - // FIXME: Is this correct? |
713 | | - if (loop.isInContext(*this)) { |
714 | | - if (Debug.contains(DebugFlags::HomotopyReduction)) { |
715 | | - llvm::dbgs() << "** Deleting loop in context: "; |
716 | | - loop.dump(llvm::dbgs(), *this); |
717 | | - llvm::dbgs() << "\n"; |
718 | | - } |
719 | | - |
720 | | - loop.markDeleted(); |
721 | | - continue; |
722 | | - } |
723 | | - |
724 | 459 | if (Debug.contains(DebugFlags::HomotopyReduction)) { |
725 | 460 | llvm::dbgs() << "** Updated loop: "; |
726 | 461 | loop.dump(llvm::dbgs(), *this); |
@@ -754,15 +489,6 @@ void RewriteSystem::minimizeRewriteSystem() { |
754 | 489 | assert(!Minimized); |
755 | 490 | Minimized = 1; |
756 | 491 |
|
757 | | - /// Begin by normalizing all loops to cyclically-reduced left-canonical |
758 | | - /// form. |
759 | | - for (auto &loop : Loops) { |
760 | | - if (loop.isDeleted()) |
761 | | - continue; |
762 | | - |
763 | | - loop.normalize(*this); |
764 | | - } |
765 | | - |
766 | 492 | // Check invariants before homotopy reduction. |
767 | 493 | verifyRewriteLoops(); |
768 | 494 |
|
|
0 commit comments