|
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