<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/71469>71469</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            [mlir][linalg] Peeling can't determine when scalable-tiled loops shouldn't be peeled
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            mlir:linalg,
            mlir
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
            matthias-springer
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          dcaballe
      </td>
    </tr>
</table>

<pre>
    I think there is a gap in the analysis that determines if a loop needs peeling or not.

Let's say we have the following loop, tiled for scalable vectorization (see %0 and %1):

```
#map = affine_map<(d0)[s0, s1] -> (-d0 + s1, s0)>
module {
  func.func @dot_dispatch_0_matmul_DxDxD_f32(%arg0: tensor<?x?xf32>, %arg1: tensor<?x?xf32>, %arg2: tensor<?x?xf32>) -> tensor<?x?xf32> {
    %c32 = arith.constant 32 : index
    %c1 = arith.constant 1 : index
    %cst = arith.constant 0.000000e+00 : f32
    %c0 = arith.constant 0 : index
 %dim = tensor.dim %arg0, %c0 : tensor<?x?xf32>
    %dim_0 = tensor.dim %arg1, %c1 : tensor<?x?xf32>
    %dim_1 = tensor.dim %arg0, %c1 : tensor<?x?xf32>
    %0 = vector.vscale
    %1 = arith.muli %0, %c32 : index
    %2 = scf.for %arg3 = %c0 to %dim step %c1 iter_args(%arg4 = %arg0) -> (tensor<?x?xf32>) {
      %3 = scf.for %arg5 = %c0 to %dim_0 step %1 iter_args(%arg6 = %arg4) -> (tensor<?x?xf32>) {
        %4 = affine.min #map(%arg5)[%1, %dim_0]
        %extracted_slice = tensor.extract_slice %arg1[%arg3, 0] [1, %dim_1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %extracted_slice_2 = tensor.extract_slice %arg2[0, %arg5] [%dim_1, %4] [1, 1] : tensor<?x?xf32> to tensor<?x?xf32>
 %extracted_slice_3 = tensor.extract_slice %arg6[%arg3, %arg5] [1, %4] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %5 = linalg.fill ins(%cst : f32) outs(%extracted_slice_3 : tensor<1x?xf32>) -> tensor<1x?xf32>
 %6 = scf.for %arg7 = %c0 to %dim_1 step %c1 iter_args(%arg8 = %5) -> (tensor<1x?xf32>) {
          %extracted_slice_4 = tensor.extract_slice %extracted_slice[0, %arg7] [1, 1] [1, 1] : tensor<1x?xf32> to tensor<1x1xf32>
          %extracted_slice_5 = tensor.extract_slice %extracted_slice_2[%arg7, 0] [1, %4] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
          %extracted_slice_6 = tensor.extract_slice %arg8[0, 0] [1, %4] [1, 1] : tensor<1x?xf32> to tensor<1x?xf32>
 %7 = linalg.matmul ins(%extracted_slice_4, %extracted_slice_5 : tensor<1x1xf32>, tensor<1x?xf32>) outs(%extracted_slice_6 : tensor<1x?xf32>) -> tensor<1x?xf32>
          %inserted_slice_7 = tensor.insert_slice %7 into %arg8[0, 0] [1, %4] [1, 1] : tensor<1x?xf32> into tensor<1x?xf32>
 scf.yield %inserted_slice_7 : tensor<1x?xf32>
        }
 %inserted_slice = tensor.insert_slice %6 into %arg6[%arg3, %arg5] [1, %4] [1, 1] : tensor<1x?xf32> into tensor<?x?xf32>
        scf.yield %inserted_slice : tensor<?x?xf32>
      }
      scf.yield %3 : tensor<?x?xf32>
    }
    return %2 : tensor<?x?xf32>
 }
}
```
We then apply peeling with `--scf-for-loop-peeling` and we get the following output, which looks good to me:

```
#map = affine_map<()[s0, s1] -> (s0 - s0 mod s1)>
#map1 = affine_map<(d0)[s0] -> (-d0 + s0)>
module {
  func.func @dot_dispatch_0_matmul_DxDxD_f32(%arg0: tensor<?x?xf32>, %arg1: tensor<?x?xf32>, %arg2: tensor<?x?xf32>) -> tensor<?x?xf32> {
    %c32 = arith.constant 32 : index
    %c1 = arith.constant 1 : index
    %cst = arith.constant 0.000000e+00 : f32
    %c0 = arith.constant 0 : index
 %dim = tensor.dim %arg0, %c0 : tensor<?x?xf32>
    %dim_0 = tensor.dim %arg1, %c1 : tensor<?x?xf32>
    %dim_1 = tensor.dim %arg0, %c1 : tensor<?x?xf32>
    %0 = vector.vscale
    %1 = arith.muli %0, %c32 : index
    %2 = scf.for %arg3 = %c0 to %dim step %c1 iter_args(%arg4 = %arg0) -> (tensor<?x?xf32>) {
      %3 = affine.apply #map()[%dim_0, %1]
      %4 = scf.for %arg5 = %c0 to %3 step %1 iter_args(%arg6 = %arg4) -> (tensor<?x?xf32>) {
        %extracted_slice = tensor.extract_slice %arg1[%arg3, 0] [1, %dim_1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
 %extracted_slice_2 = tensor.extract_slice %arg2[0, %arg5] [%dim_1, %1] [1, 1] : tensor<?x?xf32> to tensor<?x?xf32>
        %extracted_slice_3 = tensor.extract_slice %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %6 = linalg.fill ins(%cst : f32) outs(%extracted_slice_3 : tensor<1x?xf32>) -> tensor<1x?xf32>
        %7 = scf.for %arg7 = %c0 to %dim_1 step %c1 iter_args(%arg8 = %6) -> (tensor<1x?xf32>) {
          %extracted_slice_4 = tensor.extract_slice %extracted_slice[0, %arg7] [1, 1] [1, 1] : tensor<1x?xf32> to tensor<1x1xf32>
          %extracted_slice_5 = tensor.extract_slice %extracted_slice_2[%arg7, 0] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
          %extracted_slice_6 = tensor.extract_slice %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> to tensor<1x?xf32>
 %8 = linalg.matmul ins(%extracted_slice_4, %extracted_slice_5 : tensor<1x1xf32>, tensor<1x?xf32>) outs(%extracted_slice_6 : tensor<1x?xf32>) -> tensor<1x?xf32>
          %inserted_slice_7 = tensor.insert_slice %8 into %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<1x?xf32>
 scf.yield %inserted_slice_7 : tensor<1x?xf32>
        }
 %inserted_slice = tensor.insert_slice %7 into %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<?x?xf32>
        scf.yield %inserted_slice : tensor<?x?xf32>
      }
      %5 = scf.for %arg5 = %3 to %dim_0 step %1 iter_args(%arg6 = %4) -> (tensor<?x?xf32>) {
        %6 = affine.apply #map1(%arg5)[%dim_0]
        %extracted_slice = tensor.extract_slice %arg1[%arg3, 0] [1, %dim_1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %extracted_slice_2 = tensor.extract_slice %arg2[0, %arg5] [%dim_1, %6] [1, 1] : tensor<?x?xf32> to tensor<?x?xf32>
 %extracted_slice_3 = tensor.extract_slice %arg6[%arg3, %arg5] [1, %6] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %7 = linalg.fill ins(%cst : f32) outs(%extracted_slice_3 : tensor<1x?xf32>) -> tensor<1x?xf32>
 %8 = scf.for %arg7 = %c0 to %dim_1 step %c1 iter_args(%arg8 = %7) -> (tensor<1x?xf32>) {
          %extracted_slice_4 = tensor.extract_slice %extracted_slice[0, %arg7] [1, 1] [1, 1] : tensor<1x?xf32> to tensor<1x1xf32>
          %extracted_slice_5 = tensor.extract_slice %extracted_slice_2[%arg7, 0] [1, %6] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
          %extracted_slice_6 = tensor.extract_slice %arg8[0, 0] [1, %6] [1, 1] : tensor<1x?xf32> to tensor<1x?xf32>
 %9 = linalg.matmul ins(%extracted_slice_4, %extracted_slice_5 : tensor<1x1xf32>, tensor<1x?xf32>) outs(%extracted_slice_6 : tensor<1x?xf32>) -> tensor<1x?xf32>
          %inserted_slice_7 = tensor.insert_slice %9 into %arg8[0, 0] [1, %6] [1, 1] : tensor<1x?xf32> into tensor<1x?xf32>
 scf.yield %inserted_slice_7 : tensor<1x?xf32>
        }
 %inserted_slice = tensor.insert_slice %8 into %arg6[%arg3, %arg5] [1, %6] [1, 1] : tensor<1x?xf32> into tensor<?x?xf32>
        scf.yield %inserted_slice : tensor<?x?xf32>
      }
      scf.yield %5 : tensor<?x?xf32>
    }
    return %2 : tensor<?x?xf32>
 }
}
```

However, if we take the output after peeling above and remove the partial iteration:

```
#map = affine_map<()[s0, s1] -> (s0 - s0 mod s1)>
#map1 = affine_map<(d0)[s0] -> (-d0 + s0)>
module {
  func.func @dot_dispatch_0_matmul_DxDxD_f32(%arg0: tensor<?x?xf32>, %arg1: tensor<?x?xf32>, %arg2: tensor<?x?xf32>) -> tensor<?x?xf32> {
 %c32 = arith.constant 32 : index
    %c1 = arith.constant 1 : index
 %cst = arith.constant 0.000000e+00 : f32
    %c0 = arith.constant 0 : index
    %dim = tensor.dim %arg0, %c0 : tensor<?x?xf32>
    %dim_0 = tensor.dim %arg1, %c1 : tensor<?x?xf32>
    %dim_1 = tensor.dim %arg0, %c1 : tensor<?x?xf32>
    %0 = vector.vscale
    %1 = arith.muli %0, %c32 : index
    %2 = scf.for %arg3 = %c0 to %dim step %c1 iter_args(%arg4 = %arg0) -> (tensor<?x?xf32>) {
      %3 = affine.apply #map()[%dim_0, %1]
      %4 = scf.for %arg5 = %c0 to %3 step %1 iter_args(%arg6 = %arg4) -> (tensor<?x?xf32>) {
        %extracted_slice = tensor.extract_slice %arg1[%arg3, 0] [1, %dim_1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
 %extracted_slice_2 = tensor.extract_slice %arg2[0, %arg5] [%dim_1, %1] [1, 1] : tensor<?x?xf32> to tensor<?x?xf32>
        %extracted_slice_3 = tensor.extract_slice %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %6 = linalg.fill ins(%cst : f32) outs(%extracted_slice_3 : tensor<1x?xf32>) -> tensor<1x?xf32>
        %7 = scf.for %arg7 = %c0 to %dim_1 step %c1 iter_args(%arg8 = %6) -> (tensor<1x?xf32>) {
          %extracted_slice_4 = tensor.extract_slice %extracted_slice[0, %arg7] [1, 1] [1, 1] : tensor<1x?xf32> to tensor<1x1xf32>
          %extracted_slice_5 = tensor.extract_slice %extracted_slice_2[%arg7, 0] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
          %extracted_slice_6 = tensor.extract_slice %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> to tensor<1x?xf32>
 %8 = linalg.matmul ins(%extracted_slice_4, %extracted_slice_5 : tensor<1x1xf32>, tensor<1x?xf32>) outs(%extracted_slice_6 : tensor<1x?xf32>) -> tensor<1x?xf32>
          %inserted_slice_7 = tensor.insert_slice %8 into %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<1x?xf32>
 scf.yield %inserted_slice_7 : tensor<1x?xf32>
        }
 %inserted_slice = tensor.insert_slice %7 into %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<?x?xf32>
        scf.yield %inserted_slice : tensor<?x?xf32>
      }
      scf.yield %4 : tensor<?x?xf32>
    }
    return %2 : tensor<?x?xf32>
 }
}
```

And apply peeling again, the loop is peeled again, which is wrong:

```
#map = affine_map<()[s0, s1] -> (s0 - s0 mod s1)>
#map1 = affine_map<()[s0, s1] -> (s1 - s1 mod s0 - (s1 - s1 mod s0) mod s0)>
module {
  func.func @dot_dispatch_0_matmul_DxDxD_f32(%arg0: tensor<?x?xf32>, %arg1: tensor<?x?xf32>, %arg2: tensor<?x?xf32>) -> tensor<?x?xf32> {
    %c32 = arith.constant 32 : index
    %c1 = arith.constant 1 : index
    %cst = arith.constant 0.000000e+00 : f32
 %c0 = arith.constant 0 : index
    %dim = tensor.dim %arg0, %c0 : tensor<?x?xf32>
    %dim_0 = tensor.dim %arg1, %c1 : tensor<?x?xf32>
 %dim_1 = tensor.dim %arg0, %c1 : tensor<?x?xf32>
    %0 = vector.vscale
    %1 = arith.muli %0, %c32 : index
    %2 = scf.for %arg3 = %c0 to %dim step %c1 iter_args(%arg4 = %arg0) -> (tensor<?x?xf32>) {
      %3 = affine.apply #map()[%dim_0, %1]
      %4 = affine.apply #map1()[%1, %dim_0]
      %5 = scf.for %arg5 = %c0 to %4 step %1 iter_args(%arg6 = %arg4) -> (tensor<?x?xf32>) {
        %extracted_slice = tensor.extract_slice %arg1[%arg3, 0] [1, %dim_1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %extracted_slice_2 = tensor.extract_slice %arg2[0, %arg5] [%dim_1, %1] [1, 1] : tensor<?x?xf32> to tensor<?x?xf32>
 %extracted_slice_3 = tensor.extract_slice %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %7 = linalg.fill ins(%cst : f32) outs(%extracted_slice_3 : tensor<1x?xf32>) -> tensor<1x?xf32>
 %8 = scf.for %arg7 = %c0 to %dim_1 step %c1 iter_args(%arg8 = %7) -> (tensor<1x?xf32>) {
          %extracted_slice_4 = tensor.extract_slice %extracted_slice[0, %arg7] [1, 1] [1, 1] : tensor<1x?xf32> to tensor<1x1xf32>
          %extracted_slice_5 = tensor.extract_slice %extracted_slice_2[%arg7, 0] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
          %extracted_slice_6 = tensor.extract_slice %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> to tensor<1x?xf32>
 %9 = linalg.matmul ins(%extracted_slice_4, %extracted_slice_5 : tensor<1x1xf32>, tensor<1x?xf32>) outs(%extracted_slice_6 : tensor<1x?xf32>) -> tensor<1x?xf32>
          %inserted_slice_7 = tensor.insert_slice %9 into %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<1x?xf32>
 scf.yield %inserted_slice_7 : tensor<1x?xf32>
        }
 %inserted_slice = tensor.insert_slice %8 into %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<?x?xf32>
        scf.yield %inserted_slice : tensor<?x?xf32>
      }
      %6 = scf.for %arg5 = %4 to %3 step %1 iter_args(%arg6 = %5) -> (tensor<?x?xf32>) {
 %extracted_slice = tensor.extract_slice %arg1[%arg3, 0] [1, %dim_1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
        %extracted_slice_2 = tensor.extract_slice %arg2[0, %arg5] [%dim_1, %1] [1, 1] : tensor<?x?xf32> to tensor<?x?xf32>
        %extracted_slice_3 = tensor.extract_slice %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
 %7 = linalg.fill ins(%cst : f32) outs(%extracted_slice_3 : tensor<1x?xf32>) -> tensor<1x?xf32>
        %8 = scf.for %arg7 = %c0 to %dim_1 step %c1 iter_args(%arg8 = %7) -> (tensor<1x?xf32>) {
 %extracted_slice_4 = tensor.extract_slice %extracted_slice[0, %arg7] [1, 1] [1, 1] : tensor<1x?xf32> to tensor<1x1xf32>
          %extracted_slice_5 = tensor.extract_slice %extracted_slice_2[%arg7, 0] [1, %1] [1, 1] : tensor<?x?xf32> to tensor<1x?xf32>
          %extracted_slice_6 = tensor.extract_slice %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> to tensor<1x?xf32>
          %9 = linalg.matmul ins(%extracted_slice_4, %extracted_slice_5 : tensor<1x1xf32>, tensor<1x?xf32>) outs(%extracted_slice_6 : tensor<1x?xf32>) -> tensor<1x?xf32>
 %inserted_slice_7 = tensor.insert_slice %9 into %arg8[0, 0] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<1x?xf32>
          scf.yield %inserted_slice_7 : tensor<1x?xf32>
        }
        %inserted_slice = tensor.insert_slice %8 into %arg6[%arg3, %arg5] [1, %1] [1, 1] : tensor<1x?xf32> into tensor<?x?xf32>
        scf.yield %inserted_slice : tensor<?x?xf32>
      }
      scf.yield %6 : tensor<?x?xf32>
 }
    return %2 : tensor<?x?xf32>
  }
}
```

Peeling should be able to determine that the loop will compute a number of iterations multiple of `%1`, which also happens to be the step, and don't peel the loop again:
```
      #map = affine_map<()[s0, s1] -> (s0 - s0 mod s1)>

      %3 = affine.apply #map()[%dim_0, %1]
      %4 = scf.for %arg5 = %c0 to %3 step %1 iter_args(%arg6 = %arg4) -> (tensor<?x?xf32>) {
```

This issue shows up in IREE, where we peel the same loop multiple times.
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJzsXFtvqzoW_jXuy1IiMLfkIQ_tTqs50jyMRiPNY-SACZ5tMMKmaefXHxkTIC3kttPd7JbqnOwE28uf18Wsb3EhUrJNRukCeQ8I45QolTAiJzIvWLahBcIYecs7UqpEFIsoJGvCOb1bi-h18ReohGU_QSW0oMAkENiQHFimjwDJCH-VTIJKiIKIKlqkLKMSWAwEuBA5ZJRGEnJKOcs2IArIhJoia4mse_P5T6oQDiRI8gpbCgl5ppXsWHAutnqQloPwD1CM0whiUYAMCSdrTuGZhkoU7P9EMZEBwjNJKSDsWUCySH-xEZ4j5747IfKt-j_zEzspyQE5SyBxzDK6SkmOnB8IzyJLj_YepKWnlzbyljBBzqOeaBJZgPCDPqrbqp7OoxGZiqjkFFDwYH4DxGUWTvUHINeKhFpFTOZEhcnKWqVEpSVfLV-WL8tV7GCEZwh7pNhYyLkHRTMpCo3HeXrR_-sezqOe1PSyT-qFD_aam2UNtHcXAlpe6GCjr4KpZBqKTCqSKaiO3gPLIvqy39_u624P9Zaqr7s1tao_ivCDZVVjNby9kVbvwHfzIOxFLK36miVPq5-10o3OQjNsSGXdaSOWrqx-afZOmn2WNPsIttOlGVwmTKbPOm7oXnvXMmnJWTVmN82gQY35ZRhPdTQacE51zGhOiZ2OpaJ5DZkpWqxIsZGNg7u7IWZt8ya4Djjqni9WYJweMF4fmJXVwOlD43fQuJehqfC4nb1kmjK9LekdppnHM5uK2Zx-NNiQt3wnir6ogoSKRivJWUi7XlE37Rpqb6vEamNoyVokIO-hO43dPWZ-DHuSVl3TZPe6WD_QFT4GFSPvwWq3J6-G1aA0Le7FaHtDogeocwyov6_Tfbi_jvOgVo0Xc5YRvpnGjHNgWe2vZpM0WyCegyhV3dC3wPuB-d7t-z1oEPb8ngALegPMPhzvs90grze83mJ7F1y9ruYetOCb3vteF7w326AN7WEL2r326wXrnQN2hRvfC_ri-SO8rhe1fyxIZju9ngnxgFL7HDHoxoNJm9qIeOcXNYQ-E9z3mk-nmEO-OBxg_i8GWFfxLJO0aEUHXb2btlbtAbDMRN61DFDJOwRVbwGvjPJoAOuQGvb3tWDZWnRfyKHl-t3lXmNTPrD0oXxK_x3QwWlJWVcD7yU6J2Z2XREFVWWR7fKyY6Oboe2XfUL034p_ZUDynL82zG3LVALItyYTGcaTWBQTTcsmdTPyDePaUthQ9Ya_iVLlpdJW2CYsTDSf-ylhI0SkYz6lF_OzYXYmLZiAtCAVUcXQWm5mxNnH-F4f0xs53sjxRo73iRyv5lRmY-qwqnnLHHbrst_QqYaXHeOJzm_hiDdO7D6O0V0O89BJ-cOI3QfRZf8GiF2LJrg6v_NHfncFfvcRzndlfne5Uvv2nNnI71q1z07id-cY4Kb5XXA2v7t86b-X3zWFvIHUwzm_Qv0rqYc_lErZPRXqsTI9kMf4f0hl-nKcJ6QMn16Znl09cwnGzOUKmctHeN2VM5cjEM_MXOZj5tKqfX5S5nKOAW46c5mdnblcvvRPrEx7n1-ZNp__EFv6TAutOxbDloIiP80tQ6bQDCRWtGgK12QtnmlVnC5oKuqbi3JSKEZ4dSao7h0aa9A3VYP-wAL0b6k-N2XesQA9FqB3MsYC9FiAHgvQYwH6j6FxYwH6O9O4sQD9RQrQexLdW6Fx91n05hYjsiEsq4IkoebhEWaeHKFR22ZuIWIStoXINjdD2w7Is7U828jTst8d01HcfPsG5O5WbzD6EvxuJHefSe4GL6ud8MTHseuEjVbckSH-hit8VyaKfxpDHK_wjdRwpIbjFb5fuML3Zajh-Vf4_hRqOPB0X5NzuOcVpfuf6zuUb4yJxreuSN9IotHq7PPzjTHN-LZpRhfgl8w3bj_NgBPOtZfkG0N51vdMO_Yk-ufUlOHscvSp9eh_1VVomYiSR7CmUL1hSIn2vUbmNUdNdXqrT1ahSPNSUSCQlemaFiDi9q4iCWnJFcs51Yf1hNpGvtUWsgmXAhKS5zSTeq61uT9Jn1p0J5JFEIkM4UBVlfB2clMQd_oL3ztnu275-wveEdHrCf9JmAQmZUm1M2wllNULr_769-OjMRwtKGxpaxBJ0toqjbkVS6mc3kULJ5o7c3JHF7Y_nzv2zHH9u2Thz73AnVNvHYdx6NheFDtu6NB4PQ9sz5v5d2yBLezYthVYloexPyUzlzrUiUm4DubYd5Fr0ZQwPuX8OZ2KYnNXIV4EtuvP7zhZUy53L_zirEDOvTmXIIwR_rE7at78VSy0kMm63EjkWpxJJVuxiilevTqsGuAtkfdQS_KWsAuakBgfbUNlm9CseU_XxLy6S6tI1gFm-q9pfYHnriz4IlEql9qn8RPCTxumknI9DUWK8JOGU_8zyQvxPxoqhJ-qJUuEn6pV_x0AAP__1XpoDw">