@@ -486,6 +486,7 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
486
486
// value and the variant index match, since that's all `Niche` can encode.
487
487
488
488
let relative_max = niche_variants. end ( ) . as_u32 ( ) - niche_variants. start ( ) . as_u32 ( ) ;
489
+ let niche_start_const = bx. cx ( ) . const_uint_big ( tag_llty, niche_start) ;
489
490
490
491
// We have a subrange `niche_start..=niche_end` inside `range`.
491
492
// If the value of the tag is inside this subrange, it's a
@@ -511,35 +512,88 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
511
512
// } else {
512
513
// untagged_variant
513
514
// }
514
- let niche_start = bx. cx ( ) . const_uint_big ( tag_llty, niche_start) ;
515
- let is_niche = bx. icmp ( IntPredicate :: IntEQ , tag, niche_start) ;
515
+ let is_niche = bx. icmp ( IntPredicate :: IntEQ , tag, niche_start_const) ;
516
516
let tagged_discr =
517
517
bx. cx ( ) . const_uint ( cast_to, niche_variants. start ( ) . as_u32 ( ) as u64 ) ;
518
518
( is_niche, tagged_discr, 0 )
519
519
} else {
520
- // The special cases don't apply, so we'll have to go with
521
- // the general algorithm.
522
- let relative_discr = bx. sub ( tag, bx. cx ( ) . const_uint_big ( tag_llty, niche_start) ) ;
520
+ // With multiple niched variants we'll have to actually compute
521
+ // the variant index from the stored tag.
522
+ //
523
+ // However, there's still one small optimization we can often do for
524
+ // determining *whether* a tag value is a natural value or a niched
525
+ // variant. The general algorithm involves a subtraction that often
526
+ // wraps in practice, making it tricky to analyse. However, in cases
527
+ // where there are few enough possible values of the tag that it doesn't
528
+ // need to wrap around, we can instead just look for the contiguous
529
+ // tag values on the end of the range with a single comparison.
530
+ //
531
+ // For example, take the type `enum Demo { A, B, Untagged(bool) }`.
532
+ // The `bool` is {0, 1}, and the two other variants are given the
533
+ // tags {2, 3} respectively. That means the `tag_range` is
534
+ // `[0, 3]`, which doesn't wrap as unsigned (nor as signed), so
535
+ // we can test for the niched variants with just `>= 2`.
536
+ //
537
+ // That means we're looking either for the niche values *above*
538
+ // the natural values of the untagged variant:
539
+ //
540
+ // niche_start niche_end
541
+ // | |
542
+ // v v
543
+ // MIN -------------+---------------------------+---------- MAX
544
+ // ^ | is niche |
545
+ // | +---------------------------+
546
+ // | |
547
+ // tag_range.start tag_range.end
548
+ //
549
+ // Or *below* the natural values:
550
+ //
551
+ // niche_start niche_end
552
+ // | |
553
+ // v v
554
+ // MIN ----+-----------------------+---------------------- MAX
555
+ // | is niche | ^
556
+ // +-----------------------+ |
557
+ // | |
558
+ // tag_range.start tag_range.end
559
+ //
560
+ // With those two options and having the flexibility to choose
561
+ // between a signed or unsigned comparison on the tag, that
562
+ // covers most realistic scenarios. The tests have a (contrived)
563
+ // example of a 1-byte enum with over 128 niched variants which
564
+ // wraps both as signed as unsigned, though, and for something
565
+ // like that we're stuck with the general algorithm.
566
+
567
+ let tag_range = tag_scalar. valid_range ( & dl) ;
568
+ let tag_size = tag_scalar. size ( & dl) ;
569
+ let niche_end = u128:: from ( relative_max) . wrapping_add ( niche_start) ;
570
+ let niche_end = tag_size. truncate ( niche_end) ;
571
+
572
+ let relative_discr = bx. sub ( tag, niche_start_const) ;
523
573
let cast_tag = bx. intcast ( relative_discr, cast_to, false ) ;
524
- let is_niche = bx. icmp (
525
- IntPredicate :: IntULE ,
526
- relative_discr,
527
- bx. cx ( ) . const_uint ( tag_llty, relative_max as u64 ) ,
528
- ) ;
529
-
530
- // Thanks to parameter attributes and load metadata, LLVM already knows
531
- // the general valid range of the tag. It's possible, though, for there
532
- // to be an impossible value *in the middle*, which those ranges don't
533
- // communicate, so it's worth an `assume` to let the optimizer know.
534
- if niche_variants. contains ( & untagged_variant)
535
- && bx. cx ( ) . sess ( ) . opts . optimize != OptLevel :: No
536
- {
537
- let impossible =
538
- u64:: from ( untagged_variant. as_u32 ( ) - niche_variants. start ( ) . as_u32 ( ) ) ;
539
- let impossible = bx. cx ( ) . const_uint ( tag_llty, impossible) ;
540
- let ne = bx. icmp ( IntPredicate :: IntNE , relative_discr, impossible) ;
541
- bx. assume ( ne) ;
542
- }
574
+ let is_niche = if tag_range. no_unsigned_wraparound ( tag_size) == Ok ( true ) {
575
+ if niche_start == tag_range. start {
576
+ let niche_end_const = bx. cx ( ) . const_uint_big ( tag_llty, niche_end) ;
577
+ bx. icmp ( IntPredicate :: IntULE , tag, niche_end_const)
578
+ } else {
579
+ assert_eq ! ( niche_end, tag_range. end) ;
580
+ bx. icmp ( IntPredicate :: IntUGE , tag, niche_start_const)
581
+ }
582
+ } else if tag_range. no_signed_wraparound ( tag_size) == Ok ( true ) {
583
+ if niche_start == tag_range. start {
584
+ let niche_end_const = bx. cx ( ) . const_uint_big ( tag_llty, niche_end) ;
585
+ bx. icmp ( IntPredicate :: IntSLE , tag, niche_end_const)
586
+ } else {
587
+ assert_eq ! ( niche_end, tag_range. end) ;
588
+ bx. icmp ( IntPredicate :: IntSGE , tag, niche_start_const)
589
+ }
590
+ } else {
591
+ bx. icmp (
592
+ IntPredicate :: IntULE ,
593
+ relative_discr,
594
+ bx. cx ( ) . const_uint ( tag_llty, relative_max as u64 ) ,
595
+ )
596
+ } ;
543
597
544
598
( is_niche, cast_tag, niche_variants. start ( ) . as_u32 ( ) as u128 )
545
599
} ;
@@ -550,11 +604,24 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> {
550
604
bx. add ( tagged_discr, bx. cx ( ) . const_uint_big ( cast_to, delta) )
551
605
} ;
552
606
553
- let discr = bx. select (
554
- is_niche,
555
- tagged_discr,
556
- bx. cx ( ) . const_uint ( cast_to, untagged_variant. as_u32 ( ) as u64 ) ,
557
- ) ;
607
+ let untagged_variant_const =
608
+ bx. cx ( ) . const_uint ( cast_to, u64:: from ( untagged_variant. as_u32 ( ) ) ) ;
609
+
610
+ // Thanks to parameter attributes and load metadata, LLVM already knows
611
+ // the general valid range of the tag. It's possible, though, for there
612
+ // to be an impossible value *in the middle*, which those ranges don't
613
+ // communicate, so it's worth an `assume` to let the optimizer know.
614
+ // Most importantly, this means when optimizing a variant test like
615
+ // `SELECT(is_niche, complex, CONST) == CONST` it's ok to simplify that
616
+ // to `!is_niche` because the `complex` part can't possibly match.
617
+ if niche_variants. contains ( & untagged_variant)
618
+ && bx. cx ( ) . sess ( ) . opts . optimize != OptLevel :: No
619
+ {
620
+ let ne = bx. icmp ( IntPredicate :: IntNE , tagged_discr, untagged_variant_const) ;
621
+ bx. assume ( ne) ;
622
+ }
623
+
624
+ let discr = bx. select ( is_niche, tagged_discr, untagged_variant_const) ;
558
625
559
626
// In principle we could insert assumes on the possible range of `discr`, but
560
627
// currently in LLVM this isn't worth it because the original `tag` will
0 commit comments