- auto& accumulator = pos.state()->accumulator;
- IndexType i = 0;
- Features::IndexList active_indices[2];
- RawFeatures::AppendActiveIndices(pos, kRefreshTriggers[i],
- active_indices);
- for (Color perspective : { WHITE, BLACK }) {
- #ifdef TILING
- for (unsigned j = 0; j < kHalfDimensions / kTileHeight; ++j) {
- auto biasesTile = reinterpret_cast<const vec_t*>(
- &biases_[j * kTileHeight]);
- auto accTile = reinterpret_cast<vec_t*>(
- &accumulator.accumulation[perspective][i][j * kTileHeight]);
- vec_t acc[kNumRegs];
-
- for (unsigned k = 0; k < kNumRegs; ++k)
- acc[k] = biasesTile[k];
-
- for (const auto index : active_indices[perspective]) {
- const IndexType offset = kHalfDimensions * index + j * kTileHeight;
- auto column = reinterpret_cast<const vec_t*>(&weights_[offset]);
-
- for (unsigned k = 0; k < kNumRegs; ++k)
+ // NOTE: The parameter states_to_update is an array of position states, ending with nullptr.
+ // All states must be sequential, that is states_to_update[i] must either be reachable
+ // by repeatedly applying ->previous from states_to_update[i+1] or states_to_update[i] == nullptr.
+ // computed_st must be reachable by repeatadly applying ->previous on states_to_update[0], if not nullptr.
+ template<Color Perspective, size_t N>
+ void update_accumulator_incremental(const Position& pos, StateInfo* computed_st, StateInfo* states_to_update[N]) const {
+ static_assert(N > 0);
+ assert(states_to_update[N-1] == nullptr);
+
+ #ifdef VECTOR
+ // Gcc-10.2 unnecessarily spills AVX2 registers if this array
+ // is defined in the VECTOR code below, once in each branch
+ vec_t acc[NumRegs];
+ psqt_vec_t psqt[NumPsqtRegs];
+ #endif
+
+ if (states_to_update[0] == nullptr)
+ return;
+
+ // Update incrementally going back through states_to_update.
+
+ // Gather all features to be updated.
+ const Square ksq = pos.square<KING>(Perspective);
+
+ // The size must be enough to contain the largest possible update.
+ // That might depend on the feature set and generally relies on the
+ // feature set's update cost calculation to be correct and never
+ // allow updates with more added/removed features than MaxActiveDimensions.
+ FeatureSet::IndexList removed[N-1], added[N-1];
+
+ {
+ int i = N-2; // last potential state to update. Skip last element because it must be nullptr.
+ while (states_to_update[i] == nullptr)
+ --i;
+
+ StateInfo *st2 = states_to_update[i];
+
+ for (; i >= 0; --i)
+ {
+ states_to_update[i]->accumulator.computed[Perspective] = true;
+
+ StateInfo* end_state = i == 0 ? computed_st : states_to_update[i - 1];
+
+ for (; st2 != end_state; st2 = st2->previous)
+ FeatureSet::append_changed_indices<Perspective>(
+ ksq, st2->dirtyPiece, removed[i], added[i]);
+ }
+ }
+
+ StateInfo* st = computed_st;
+
+ // Now update the accumulators listed in states_to_update[], where the last element is a sentinel.
+#ifdef VECTOR
+ for (IndexType j = 0; j < HalfDimensions / TileHeight; ++j)
+ {
+ // Load accumulator
+ auto accTile = reinterpret_cast<vec_t*>(
+ &st->accumulator.accumulation[Perspective][j * TileHeight]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = vec_load(&accTile[k]);
+
+ for (IndexType i = 0; states_to_update[i]; ++i)
+ {
+ // Difference calculation for the deactivated features
+ for (const auto index : removed[i])
+ {
+ const IndexType offset = HalfDimensions * index + j * TileHeight;
+ auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
+ for (IndexType k = 0; k < NumRegs; ++k)
+ acc[k] = vec_sub_16(acc[k], column[k]);
+ }
+
+ // Difference calculation for the activated features
+ for (const auto index : added[i])
+ {
+ const IndexType offset = HalfDimensions * index + j * TileHeight;
+ auto column = reinterpret_cast<const vec_t*>(&weights[offset]);
+ for (IndexType k = 0; k < NumRegs; ++k)