Add a rewrite pattern that transforms an instance of
`HLFHELinalg.zero` into an instance of `linalg.generate` with an
appropriate region yielding a zero value.
Example:
%out = "HLFHELinalg.zero"() : () -> tensor<MxNx!HLFHE.eint<p>>
becomes:
%0 = tensor.generate {
^bb0(%arg2: index, %arg3: index):
%zero = "HLFHE.zero"() : () -> !HLFHE.eint<p>
tensor.yield %zero : !HLFHE.eint<p>
} : tensor<MxNx!HLFHE.eint<p>>
This changes the semantics of `HLFHE.dot_eint_int` from memref-based
reference semantics to tensor-based value semantics. The former:
"HLFHE.dot_eint_int"(%arg0, %arg1, %arg2) :
(memref<Nx!HLFHE.eint<0>>, memref<Nxi32>, memref<!HLFHE.eint<0>>) -> ()
becomes:
"HLFHE.dot_eint_int"(%arg0, %arg1) :
(tensor<Nx!HLFHE.eint<0>>, tensor<Nxi32>) -> !HLFHE.eint<0>
As a side effect, data-flow analyses become much easier. With the
previous memref type of the plaintext argument it is difficult to
check whether the plaintext values are statically defined constants or
originate from a memory region changed at execution time (e.g., for
analyses evaluating the impact on noise). Changing the plaintext type
from `memref` to `vector` makes such analyses significantly easier.