diff --git a/open_spiel/CMakeLists.txt b/open_spiel/CMakeLists.txt index 1efd34f0cf..1fa956151c 100644 --- a/open_spiel/CMakeLists.txt +++ b/open_spiel/CMakeLists.txt @@ -50,14 +50,14 @@ if(${BUILD_TYPE} STREQUAL "Testing") # A build used for running tests: keep all runtime checks (assert, # SPIEL_CHECK_*, SPIEL_DCHECK_*), but turn on some speed optimizations, # otherwise tests run for too long. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -march=x86-64-v3") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") endif() if(${BUILD_TYPE} STREQUAL "Release") # Optimized release build: turn off debug runtime checks (assert, # SPIEL_DCHECK_*) and turn on highest speed optimizations. # The difference in perfomance can be up to 10x higher. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -march=x86-64-v3") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 ") endif() if(APPLE) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index b832532381..3fcf2f138e 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -53,7 +53,7 @@ class Node { Node(uint32_t cards, std::array suit_masks, char trump,bool player) { cards_ = cards; suit_masks_ = suit_masks; - total_tricks_ = __builtin_popcount(cards); + total_tricks_ = popcnt_u32(cards); trump_ = trump; moves_ = 0; player_ = player; @@ -83,15 +83,15 @@ class Node { void RemoveCard(ActionStruct action) { //Removes card from cards_// uint32_t mask_b = ~0; - mask_b =_bzhi_u32(mask_b, action.index); + mask_b =bzhi_u32(mask_b, action.index); uint32_t mask_a = ~mask_b; - mask_a = _blsr_u32(mask_a); + mask_a = blsr_u32(mask_a); uint32_t copy_a = cards_ & mask_a; uint32_t copy_b = cards_ & mask_b; copy_a = copy_a >> 1; cards_ = copy_a | copy_b; //decrements appropriate suits// - suit_masks_[action.suit] = _blsr_u32(suit_masks_[action.suit])>>1; + suit_masks_[action.suit] = blsr_u32(suit_masks_[action.suit])>>1; char suit = action.suit; suit++; while (suit < kNumSuits) { @@ -102,7 +102,7 @@ class Node { void InsertCard(ActionStruct action) { //inserts card into cards_// uint32_t mask_b = ~0; - mask_b = _bzhi_u32(mask_b, action.index); + mask_b = bzhi_u32(mask_b, action.index); uint32_t mask_a = ~mask_b; uint32_t copy_b = cards_ & mask_b; uint32_t copy_a = cards_ & mask_a; @@ -128,17 +128,17 @@ class Node { //this implies player 1 achieves the minimax value of the original game ie the value is remaining tricks - value of the original game for this transformed game// //also does not take advantage of single suit isomorphism. Namely all single suit games with the same card distribution are isomorphic. Currently this considers all trump, all no trump games as distinct// uint64_t suit_sig = 0; - char trump_length = __builtin_popcount(suit_masks_[trump_]); + char trump_length = popcnt_u32(suit_masks_[trump_]); if (trump_length > kNumRanks) { throw; } std::vector non_trump_lengths; for (char i = 0; i < kNumSuits; ++i) { if (i != trump_) { - char length = __builtin_popcount(suit_masks_[i]); + char length = popcnt_u32(suit_masks_[i]); uint32_t sig = suit_masks_[i]&cards_; if (suit_masks_[i] != 0) { - sig = (sig >> (_tzcnt_u32(suit_masks_[i]))); + sig = (sig >> (tzcnt_u32(suit_masks_[i]))); } if (length > kNumRanks) { throw 1; @@ -157,19 +157,19 @@ class Node { std::array suit_cards; suit_cards[0] = cards_ & suit_masks_[trump_]; if (suit_masks_[trump_] != 0) { - suit_cards[0] = suit_cards[0] >> _tzcnt_u32(suit_masks_[trump_]); + suit_cards[0] = suit_cards[0] >> tzcnt_u32(suit_masks_[trump_]); } - uint32_t sum = __builtin_popcount(suit_masks_[trump_]); + uint32_t sum = popcnt_u32(suit_masks_[trump_]); uint32_t cards = 0|suit_cards[0]; for (size_t i = 0; i < non_trump_lengths.size(); ++i) { suit_cards[i] = cards_ & suit_masks_[non_trump_lengths[i].index]; uint32_t val = 0; if (suit_masks_[non_trump_lengths[i].index] != 0) { - val = _tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); + val = tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); } suit_cards[i]= suit_cards[i] >>val; suit_cards[i] = suit_cards[i] << sum; - sum += __builtin_popcount(suit_masks_[non_trump_lengths[i].index]); + sum += popcnt_u32(suit_masks_[non_trump_lengths[i].index]); cards = cards | suit_cards[i]; } //cards = cards | (player_ << 31); @@ -186,7 +186,7 @@ class Node { #endif } uint64_t AltKey() { - uint32_t mask = _bzhi_u32(~0, 2 * RemainingTricks()); + uint32_t mask = bzhi_u32(~0, 2 * RemainingTricks()); return key_ ^ (uint64_t)mask; } //Move Ordering Heuristics// @@ -200,7 +200,7 @@ class Node { uint32_t suit_cards = copy_cards & suit_masks_[suit]; uint32_t mask = suit_cards & ~(suit_cards >> 1); //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// - int suit_rank = __builtin_popcount(_bzhi_u32(mask, action.index)); + int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); ApplyAction(action); std::vector moves = LegalActions(); UndoAction(action); @@ -230,7 +230,7 @@ class Node { uint32_t suit_cards = copy_cards & suit_masks_[suit]; uint32_t mask = suit_cards & ~(suit_cards >> 1); //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// - int suit_rank = __builtin_popcount(_bzhi_u32(mask, action.index)); + int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); if (!Trick(lead, action)) { return -kNumRanks - suit_rank; } @@ -274,14 +274,14 @@ class Node { } if ((lead || (follow && (correct_suit || void_in_suit)))) { while (suit_mask != 0) { - uint32_t best = _tzcnt_u32(suit_mask); + uint32_t best = tzcnt_u32(suit_mask); if (moves_ % 2 == 0) { temp.push_back({ ActionStruct(best, i, player_),LeadOrdering(ActionStruct(best, i, player_)) }); } else { temp.push_back({ ActionStruct(best, i, player_),FollowOrdering(ActionStruct(best, i, player_)) }); } - suit_mask = _blsr_u32(suit_mask); + suit_mask = blsr_u32(suit_mask); } } } @@ -428,9 +428,9 @@ char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, if (node->Moves() % 2 == 0&& depth==0) { node->UpdateNodeKey(); key = (player) ? node->AltKey() : node->GetNodeKey(); - uint32_t cards = key & _bzhi_u64(~0, 32); + uint32_t cards = key & bzhi_u64(~0, 32); uint32_t colex = HalfColexer(cards, &bin_coeffs); - uint32_t suits = (key & (~0 ^ _bzhi_u64(~0, 32))) >> 32; + uint32_t suits = (key & (~0 ^ bzhi_u64(~0, 32))) >> 32; uint32_t suit_rank = SuitRanks->at(suits); char value = (player) ? node->RemainingTricks() - TTable->Get(colex,suit_rank) :TTable->Get(colex,suit_rank); return value+node->Score(); @@ -519,16 +519,16 @@ std::vector GWhistGenerator(int num,unsigned int seed){ int cum_sum =0; for (int j = 0; j < kNumSuits; ++j) { if (j == 0) { - suits[j] = _bzhi_u32(~0, suit_lengths[j]); + suits[j] = bzhi_u32(~0, suit_lengths[j]); } else { - suits[j] = (_bzhi_u32(~0, suit_lengths[j]+cum_sum)) ^ _bzhi_u32(~0,cum_sum); + suits[j] = (bzhi_u32(~0, suit_lengths[j]+cum_sum)) ^ bzhi_u32(~0,cum_sum); } cum_sum+= suit_lengths[j]; } out.push_back(Node(cards, suits, 0,false)); #ifdef DEBUG - std::cout << __builtin_popcount(cards) << " " << __builtin_popcount(suits[0]) + __builtin_popcount(suits[1]) + __builtin_popcount(suits[2]) + __builtin_popcount(suits[3]) << std::endl; + std::cout << popcnt_u32(cards) << " " << popcnt_u32(suits[0]) + popcnt_u32(suits[1]) + popcnt_u32(suits[2]) + popcnt_u32(suits[3]) << std::endl; std::cout << cards << " " << suits[0] << " " << suits[1] << " " << suits[2] << " " << suits[3] << std::endl; #endif @@ -561,12 +561,12 @@ void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, std: } for (int i = 0; i < suit_splits.size(); ++i) { std::array suit_arr; - suit_arr[0] = _bzhi_u32(~0, suit_splits[i] & 0b1111); - int sum = suit_splits[i] & 0b1111; + suit_arr[0] = bzhi_u32(~0, suit_splits[i] & 0b1111); + uint32_t sum = suit_splits[i] & 0b1111; for (int j = 1; j < kNumSuits; ++j) { - uint32_t mask = _bzhi_u32(~0, sum); + uint32_t mask = bzhi_u32(~0, sum); sum += (suit_splits[i] & (0b1111 << (4 * j))) >> 4 * j; - suit_arr[j] = _bzhi_u32(~0, sum); + suit_arr[j] = bzhi_u32(~0, sum); suit_arr[j] = suit_arr[j] ^ mask; } Node node(cards, suit_arr, 0, false); diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 5028361adc..13e15252b9 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -1,10 +1,5 @@ #include -//to do -//InfostateTensor implementation -// PR!!!!! - - #include "open_spiel/abseil-cpp/absl/strings/str_cat.h" #include "open_spiel/game_parameters.h" #include "open_spiel/observer.h" @@ -18,6 +13,45 @@ namespace german_whist_foregame { std::string kTTablePath=""; + +uint32_t tzcnt_u32(uint32_t a){ + return __builtin_ctz(a); +} +uint64_t tzcnt_u64(uint64_t a){ + return __builtin_ctzll(a); +} +uint32_t bzhi_u32(uint32_t a,uint32_t b){ + return (b==0)?0:((a<<(32-b))>>(32-b)); +} +uint64_t bzhi_u64(uint64_t a,uint64_t b){ + return (b==0)?0:((a<<(64-b))>>(64-b)); +} +uint32_t blsr_u32(uint32_t a){ + return(a-1)&a; +} +uint64_t blsr_u64(uint64_t a){ + return (a-1)&a; +} +uint32_t popcnt_u32(uint32_t a){ + return __builtin_popcount(a); +} +uint64_t popcnt_u64(uint64_t a){ + return __builtin_popcountll(a); +} +uint64_t pext_u64(uint64_t x,uint64_t m){ + uint64_t r = 0; + uint64_t s = 0; + uint64_t b = 0; + do{ + b =m&1; + r = r|((x&b)<>1; + m = m>>1; + }while(m!=0); + return r; +} + bool Triple::operator<(const Triple& triple)const{ return (length < triple.length)|| (length == triple.length && sig < triple.sig); } @@ -25,12 +59,12 @@ bool Triple::operator<(const Triple& triple)const{ inline int CardRank(int card, int suit) { uint64_t card_mask = ((uint64_t)1 << card); card_mask = (card_mask >> (suit * kNumRanks)); - return _tzcnt_u64(card_mask); + return tzcnt_u64(card_mask); } inline int CardSuit(int card) { uint64_t card_mask = ((uint64_t)1 << card); for (int i = 0; i < kNumSuits; ++i) { - if (_mm_popcnt_u64(card_mask & kSuitMasks[i]) == 1) { + if (popcnt_u64(card_mask & kSuitMasks[i]) == 1) { return i; } } @@ -86,10 +120,10 @@ uint32_t HalfColexer(uint32_t cards,const std::vector>* bi uint32_t out = 0; uint32_t count = 0; while (cards != 0) { - uint32_t ind = _tzcnt_u32(cards); + uint32_t ind = tzcnt_u32(cards); uint32_t val = bin_coeffs->at(ind)[count+1]; out += val; - cards = _blsr_u32(cards); + cards = blsr_u32(cards); count++; } return out; @@ -217,7 +251,7 @@ GWhistFState::GWhistFState(std::shared_ptr game):State(game) player_ = kChancePlayerId; move_number_ = 0; trump_ = -1; - deck_ = _bzhi_u64(~0,kNumRanks*kNumSuits); + deck_ = bzhi_u64(~0,kNumRanks*kNumSuits); discard_ = 0; hands_ = { 0,0 }; history_.reserve(78); @@ -233,7 +267,7 @@ bool GWhistFState::Trick(int lead, int follow) const { return (lead_suit == follow_suit && lead_rank < follow_rank) || (lead_suit != follow_suit && follow_suit != trump_); } bool GWhistFState::IsTerminal() const { - return(_mm_popcnt_u64(deck_) == 0); + return(popcnt_u64(deck_) == 0); } uint64_t GWhistFState::EndgameKey(int player_to_move) const{ //generates a 64 bit unsigned int where the first 32 are the suit ownerships from the perspective of the opponent using canonical rankings// @@ -245,18 +279,18 @@ uint64_t GWhistFState::EndgameKey(int player_to_move) const{ //sort trump suits by length,then sig// for(int i =0;i hand0; std::array hand1; - hand0[0]=_pext_u64(hands_[0],kSuitMasks[trump_]); - hand1[0]=_pext_u64(hands_[1],kSuitMasks[trump_]); + hand0[0]=pext_u64(hands_[0],kSuitMasks[trump_]); + hand1[0]=pext_u64(hands_[1],kSuitMasks[trump_]); for(int i =0;ihands_shuffled = {0,0}; for(int i =0;i GWhistFState::Returns() const{ int player_to_move=(lead_win)?history_[move_number_-3].player:history_[move_number_-2].player; int opp = (player_to_move==0)?1:0; uint64_t key = EndgameKey(player_to_move); - uint32_t cards = (key&_bzhi_u64(~0,32)); + uint32_t cards = (key&bzhi_u64(~0,32)); uint32_t colex = HalfColexer(cards,bin_coeffs_); - uint32_t suits = (key&(~0^_bzhi_u64(~0,32)))>>32; + uint32_t suits = (key&(~0^bzhi_u64(~0,32)))>>32; uint32_t suit_rank = suit_ranks_->at(suits); char value =ttable_->Get(colex,suit_rank); out[player_to_move] = 2*value-kNumRanks; @@ -336,21 +370,21 @@ std::string GWhistFState::StateToString() const { std::vector player1_cards; std::vector discard; while (copy_deck != 0) { - deck_cards.push_back(_tzcnt_u64(copy_deck)); - copy_deck = _blsr_u64(copy_deck); + deck_cards.push_back(tzcnt_u64(copy_deck)); + copy_deck = blsr_u64(copy_deck); } while (copy_discard != 0) { - discard.push_back(_tzcnt_u64(copy_discard)); - copy_discard = _blsr_u64(copy_discard); + discard.push_back(tzcnt_u64(copy_discard)); + copy_discard = blsr_u64(copy_discard); } while (copy_hands[0] != 0) { - player0_cards.push_back(_tzcnt_u64(copy_hands[0])); - copy_hands[0] = _blsr_u64(copy_hands[0]); + player0_cards.push_back(tzcnt_u64(copy_hands[0])); + copy_hands[0] = blsr_u64(copy_hands[0]); } while (copy_hands[1] != 0) { - player1_cards.push_back(_tzcnt_u64(copy_hands[1])); - copy_hands[1] = _blsr_u64(copy_hands[1]); + player1_cards.push_back(tzcnt_u64(copy_hands[1])); + copy_hands[1] = blsr_u64(copy_hands[1]); } out += "Deck \n"; for (int i = 0; i < deck_cards.size(); ++i) { @@ -384,8 +418,8 @@ std::string GWhistFState::InformationStateString(Player player) const{ std::vector v_hand = {}; uint64_t p_hand = hands_[player]; while(p_hand!=0){ - v_hand.push_back(_tzcnt_u64(p_hand)); - p_hand = _blsr_u64(p_hand); + v_hand.push_back(tzcnt_u64(p_hand)); + p_hand = blsr_u64(p_hand); } std::sort(v_hand.begin(),v_hand.end()); for(int i =0;i GWhistFState::ResampleFromInfostate(int player_id,std::fu // if a face up card from the deck is not in players hand or discard it must be in opps unless it is the most recent face up// necessary_cards = (necessary_cards & (~(hands_[player_id] | discard_|recent_faceup_card))); //sufficient cards are all cards not in players hand,the discard, or the recent face up// - uint64_t sufficient_cards = (_bzhi_u64(~0, kNumRanks * kNumSuits) ^(hands_[player_id] | discard_|recent_faceup_card)); + uint64_t sufficient_cards = (bzhi_u64(~0, kNumRanks * kNumSuits) ^(hands_[player_id] | discard_|recent_faceup_card)); //sufficient_cards are not necessary // sufficient_cards = (sufficient_cards & (~(necessary_cards))); //we must now take into account the observation of voids// @@ -469,14 +503,14 @@ std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::fu } } //we now perform a sequence of shuffles to generate a possible opponent hand, and make no attempt to reconcile the history with this new deal// - int nec = _mm_popcnt_u64(necessary_cards); + int nec = popcnt_u64(necessary_cards); for (int i = 0; i < kNumSuits; ++i) { - if (voids[i] != -1&&_mm_popcnt_u64(sufficient_cards&kSuitMasks[i])>voids[i]) { + if (voids[i] != -1&&popcnt_u64(sufficient_cards&kSuitMasks[i])>voids[i]) { uint64_t suit_subset = (sufficient_cards & kSuitMasks[i]); std::vector temp; while (suit_subset != 0) { - temp.push_back(_tzcnt_u64(suit_subset)); - suit_subset = _blsr_u64(suit_subset); + temp.push_back(tzcnt_u64(suit_subset)); + suit_subset = blsr_u64(suit_subset); } std::shuffle(temp.begin(), temp.end(), gen); sufficient_cards = (sufficient_cards &~(kSuitMasks[i])); @@ -488,18 +522,18 @@ std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::fu //finally generating a possible hand for opponent// std::vector hand_vec; while (sufficient_cards != 0) { - hand_vec.push_back(_tzcnt_u64(sufficient_cards)); - sufficient_cards = _blsr_u64(sufficient_cards); + hand_vec.push_back(tzcnt_u64(sufficient_cards)); + sufficient_cards = blsr_u64(sufficient_cards); } std::shuffle(hand_vec.begin(), hand_vec.end(), gen); uint64_t suff_hand = 0; uint64_t opp_hand=0; - for (int i = 0; i < _mm_popcnt_u64(hands_[opp])-nec; ++i) { + for (int i = 0; i < popcnt_u64(hands_[opp])-nec; ++i) { suff_hand = suff_hand | (uint64_t(1) << hand_vec[i]); } opp_hand = suff_hand | necessary_cards; resampled_state->hands_[opp] = opp_hand; - resampled_state->deck_ = _bzhi_u64(~0, kNumRanks * kNumSuits) ^ (discard_ | opp_hand | hands_[player_id]|recent_faceup_card); + resampled_state->deck_ = bzhi_u64(~0, kNumRanks * kNumSuits) ^ (discard_ | opp_hand | hands_[player_id]|recent_faceup_card); return resampled_state; } std::string GWhistFState::ObservationString(Player player) const { @@ -510,8 +544,8 @@ std::string GWhistFState::ObservationString(Player player) const { uint64_t p_hand = hands_[player]; std::vector v_hand = {}; while(p_hand!=0){ - v_hand.push_back(_tzcnt_u64(p_hand)); - p_hand = _blsr_u64(p_hand); + v_hand.push_back(tzcnt_u64(p_hand)); + p_hand = blsr_u64(p_hand); } std::sort(v_hand.begin(),v_hand.end()); for(int i =0;i GWhistFState::LegalActions() const{ std::vector actions; if (IsTerminal()) return {}; if (IsChanceNode()) { - actions.reserve(_mm_popcnt_u64(deck_)); + actions.reserve(popcnt_u64(deck_)); uint64_t copy_deck = deck_; while (copy_deck != 0) { - actions.push_back(_tzcnt_u64(copy_deck)); - copy_deck = _blsr_u64(copy_deck); + actions.push_back(tzcnt_u64(copy_deck)); + copy_deck = blsr_u64(copy_deck); } } else { @@ -543,8 +577,8 @@ std::vector GWhistFState::LegalActions() const{ if (history_.back().player == kChancePlayerId) { uint64_t copy_hand = hands_[player_]; while (copy_hand != 0) { - actions.push_back(_tzcnt_u64(copy_hand)); - copy_hand = _blsr_u64(copy_hand); + actions.push_back(tzcnt_u64(copy_hand)); + copy_hand = blsr_u64(copy_hand); } } @@ -555,8 +589,8 @@ std::vector GWhistFState::LegalActions() const{ copy_hand = hands_[player_]; } while (copy_hand != 0) { - actions.push_back(_tzcnt_u64(copy_hand)); - copy_hand = _blsr_u64(copy_hand); + actions.push_back(tzcnt_u64(copy_hand)); + copy_hand = blsr_u64(copy_hand); } } } diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index 61a973b760..6540033c6e 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -18,10 +17,6 @@ //The imperfect information part of 2 player whist variant //https://en.wikipedia.org/wiki/German_Whist -// -// - -// namespace open_spiel { namespace german_whist_foregame { @@ -35,8 +30,23 @@ inline constexpr int kNumRanks = 13; inline constexpr int kNumSuits = 4; inline constexpr char kRankChar[] = "AKQJT98765432"; inline constexpr char kSuitChar[] = "CDHS"; -inline const std::array kSuitMasks = { _bzhi_u64(~0,kNumRanks),_bzhi_u64(~0,2 * kNumRanks) ^ _bzhi_u64(~0,kNumRanks),_bzhi_u64(~0,3 * kNumRanks) ^ _bzhi_u64(~0,2 * kNumRanks),_bzhi_u64(~0,4 * kNumRanks) ^ _bzhi_u64(~0,3 * kNumRanks) }; + extern std::string kTTablePath ; + +//Reimplementing bmi2 intrinsics with bit operations that will work on all platforms// +uint32_t tzcnt_u32(uint32_t a); +uint64_t tzcnt_u64(uint64_t a); +uint32_t bzhi_u32(uint32_t a,uint32_t b); +uint64_t bzhi_u64(uint64_t a,uint64_t b); +uint32_t blsr_u32(uint32_t a); +uint64_t blsr_u64(uint64_t a); +uint32_t popcnt_u32(uint32_t a); +uint64_t popcnt_u64(uint64_t a); +uint64_t pext_u64(uint64_t a,uint64_t b); + +inline const std::array kSuitMasks = { bzhi_u64(~0,kNumRanks),bzhi_u64(~0,2 * kNumRanks) ^ bzhi_u64(~0,kNumRanks),bzhi_u64(~0,3 * kNumRanks) ^ bzhi_u64(~0,2 * kNumRanks),bzhi_u64(~0,4 * kNumRanks) ^ bzhi_u64(~0,3 * kNumRanks) }; + + struct Triple{ char index; char length;