Commit Graph

291 Commits

Author SHA1 Message Date
Longsheng Mou
22a11be8ab [mlir][vector] Fix parser of vector.contract (#133434)
This PR adds a check in the parser to prevent a crash when
`vector.contract` lacks the `iterator_types` attribute.
Fixes #132886.
2025-03-29 09:24:42 +08:00
Kazu Hirata
a1bb750745 [mlir] Use a range constructor of DenseSet (NFC) (#133355) 2025-03-27 20:13:30 -07:00
Kunwar Grover
f3fa54a191 [mlir][Vector] Handle 0-rank case in fold instead of RewriterPattern (#130168)
For vector.extract, the folder always canonicalizes to a vector.extract
operation, while the rewrite pattern canonicalizes to a vector.broadcast
except in the case of 0-rank vectors.

Remove this special casing, and instead handle the 0-rank vector case in
the folder.
2025-03-24 13:14:24 +00:00
Kunwar Grover
dc28e0d5d2 [mlir][Vector] Remove more special case uses for extractelement/insertelement (#130166)
A number of places in our codebase special case to use
extractelement/insertelement for 0D vectors, because extract/insert did
not support 0D vectors previously. Since insert/extract support 0D
vectors now, use them instead of special casing.
2025-03-24 13:04:16 +00:00
Kunwar Grover
24a8e18f5a [mlir][vector] Allow multi dim vectors in vector.scatter (#132217)
This patch matches the definition of vector.scatter as a counter part of
vector.gather.

All of the changes done in this patch make vector.scatter match
vector.gather 's multi dimensional definition.

Unrolling for vector.scatter will be implemented in subsequent patches.

Discourse Discussion:
https://discourse.llvm.org/t/rfc-improving-gather-codegen-for-vector-dialect/85011/13
2025-03-24 12:52:46 +00:00
Kazu Hirata
3041fa6c7a [mlir] Use *Set::insert_range (NFC) (#132326)
DenseSet, SmallPtrSet, SmallSet, SetVector, and StringSet recently
gained C++23-style insert_range.  This patch replaces:

  Dest.insert(Src.begin(), Src.end());

with:

  Dest.insert_range(Src);

This patch does not touch custom begin like succ_begin for now.
2025-03-20 22:24:17 -07:00
Kunwar Grover
d10dca6ba7 [mlir][Vector] Move vector.insert canonicalizers for DenseElementsAttr to folders (#128040)
This PR moves vector.insert canonicalizers for DenseElementsAttr (splat
and non splat case) to folders. Folders are local, and it's always
better to implement a folder than a canonicalizer.

This PR is mostly NFC-ish, because the functionality mostly remains
same, but is now run as part of a folder, which is why some tests are
changed, because GreedyPatternRewriter tries to fold by default.
2025-03-06 18:24:38 +00:00
Kunwar Grover
98542a3d6d [mlir][Vector] Move vector.extract canonicalizers for DenseElementsAttr to folders (#127995)
This PR moves vector.extract canonicalizers for DenseElementsAttr (splat
and non splat case) to folders. Folders are local, and it's always
better to implement a folder than a canonicalization pattern.

This PR is mostly NFC-ish, because the functionality mostly remains
same, but is now run as part of a folder, which is why some tests are
changed, because GreedyPatternRewriter tries to fold by default.

There is also a test change which makes the indices of a vector.extract
test dynamic. This is so that it doesn't fold away after this pr.
2025-02-26 10:58:24 +05:30
Kunwar Grover
61fb954109 [mlir][Vector] Improve support for vector.extract(broadcast) (#116234)
This patch improves support for vector.extract(broadcast) dynamic
dimension folders. This is mostly a matter of moving a conservative
condition for dynamic dimensions. The broadcast folder for
vector.extract now covers the cases that the vector.extractelement +
broadcast folder does.

This patch also improves test coverage for vector.extract + broadcast
folders/canonicalizers. The folders/canonicalizers now enumerate every
supported / unsupported case.
2025-02-24 16:17:38 +05:30
Longsheng Mou
3e223e3a20 [mlir][vector] Fix out-of-bounds access (#126734)
This PR fixes an out-of-bounds bug that occurs when there are no overlap
dimensions between the `sizes` and source of
`vector.extract_strided_slice`, causing access to `sizes` to go out of
bounds. Fixes #126196.
2025-02-13 09:17:43 +08:00
Andrzej Warzyński
fcbf04e40e [mlir][vector][nfc] Add clarification on "dim-1" bcast (#125425)
Adds a small note to VectorOps.td on what "dim-1" broadcast is. Also
updates comments to consistently use quotes, i.e.

* "dim-1" broadcasting instead of dim-1 broadcasting.

This way it is clear that we are referring to "stretching" one of the
trailing dims rather than e.g. broadcasting a dim at idx 1.
2025-02-11 21:37:23 +00:00
lonely eagle
7ae78a6cdb [mlir][vector]add extractInsertFoldConstantOp fold function and apply it to extractOp and insertOp. (#124399)
add extractInsertFoldConstantOp fold function and apply it to extractOp and insertOp.
2025-02-11 00:21:59 +08:00
Diego Caballero
68325148d3 [mlir][Vector] Fold vector.extract from poison vector (#126122)
This PR adds a folder for `vector.extract(ub.poison) -> ub.poison`. It
also replaces `create` with `createOrFold` insert/extract ops in vector
unroll and transpose lowering patterns to trigger the poison foldings
introduced recently.
2025-02-07 10:20:07 -08:00
Ivan Butygin
6e52a12811 [mlir][vector] Create VectorToLLVMDialectInterface (#121440)
Create `VectorToLLVMDialectInterface` which allows automatic conversion
discovery by generic `--convert-to-llvm` pass. This only covers final
dialect conversion step and not any previous preparation steps. Also,
currently there is no way to pass any additional parameters through this
conversion interface, but most users using default parameters anyway.
2025-02-05 23:21:25 +03:00
Diego Caballero
c6eef00a09 [mlir][Vector] Add vector.shuffle fold for poison inputs (#125608)
https://github.com/llvm/llvm-project/pull/124863 added folding support
for poison indices to `vector.shuffle`. This PR adds support for folding
`vector.shuffle` ops with one or two poison input vectors.
2025-02-04 18:03:26 -08:00
Diego Caballero
d13940ee26 [mlir][Vector] Teach how to materialize UB constant to Vector (#125596)
This PR adds support for UB constant materialization (i.e., generating
`ub::PoisonOp` to `VectorDialect::materializeConstant`. This was the
reason why the vector folders generating poison didn't work.
2025-02-04 11:18:30 -08:00
Diego Caballero
c3c326213e [mlir][Vector] Fix vector.shuffle folder for poison indices (#124863)
This PR fixes the folder of a `vector.shuffle` with constant input
vectors in the presence of a poison index. Partially poison vectors are
currently not supported in UB so the folder select v1[0] for elements
indexed by poison.
2025-01-31 15:47:47 -08:00
Jay Foad
aa2952165c Fix typo "tranpose" (#124929) 2025-01-29 17:49:54 +00:00
Diego Caballero
35df525fd0 [mlir][Vector] Add support for poison indices to Extract/IndexOp (#123488)
Following up on #122188, this PR adds support for poison indices to
`ExtractOp` and `InsertOp`. It also includes canonicalization patterns
to turn extract/insert ops with poison indices into `ub.poison`.
2025-01-28 13:51:50 -08:00
Ivan Butygin
88136f9645 [mlir][vector] Canonicalize gathers/scatters with trivial offsets (#117939)
Canonicalize gathers/scatters with contiguous (i.e. [0, 1, 2, ...])
offsets into vector masked load/store ops.
2025-01-24 14:14:53 +03:00
Matthias Springer
6aaa8f25b6 [mlir][IR][NFC] Move free-standing functions to MemRefType (#123465)
Turn free-standing `MemRefType`-related helper functions in
`BuiltinTypes.h` into member functions.
2025-01-21 08:48:09 +01:00
Diego Caballero
eae5ca9b45 [mlir][Vector] Support poison in vector.shuffle mask (#122188)
This PR extends the existing poison support in
https://mlir.llvm.org/docs/Dialects/UBOps/ by representing poison mask
values in `vector.shuffle`. Similar to LLVM (see
https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/IR/Instructions.h#L1884)
this requires defining an integer value (`-1`) to represent poison in
the `vector.shuffle` mask.
2025-01-18 15:16:51 -08:00
Kai Sasaki
d3846eca20 [mlir] Guard sccp pass from crashing with different source type (#120656)
Vector::BroadCastOp expects the identical element type in folding. It
causes the crash if the different source type is given to the SCCP pass.
We need to guard the pass from crashing if the nonidentical element type
is given, but still compatible. (e.g. index vs integer type)

https://github.com/llvm/llvm-project/issues/120193
2024-12-25 12:19:52 +09:00
Kazu Hirata
6e41483b84 [MemRef] Migrate away from PointerUnion::{is,get} (NFC) (#120382)
Note that PointerUnion::{is,get} have been soft deprecated in
PointerUnion.h:

  // FIXME: Replace the uses of is(), get() and dyn_cast() with
  //        isa<T>, cast<T> and the llvm::dyn_cast<T>

I'm not touching PointerUnion::dyn_cast for now because it's a bit
complicated; we could blindly migrate it to dyn_cast_if_present, but
we should probably use dyn_cast when the operand is known to be
non-null.
2024-12-18 10:56:27 -08:00
Kunwar Grover
a201ba1b57 [mlir][Vector] Add support for 0-d shapes in extract-shape_cast folder (#116650)
The extract <-> shape cast folder was conservatively asserting and
failing on 0-d vectors. This pr fixes this.

This pr also adds more tests for 0d cases and updates related tests to
better reflect what they test.
2024-12-04 00:03:50 +00:00
Petr Kurapov
ecaf2c335c [MLIR] Move warp_execute_on_lane_0 from vector to gpu (#116994)
Please see the related RFC here:
https://discourse.llvm.org/t/rfc-move-execute-on-lane-0-from-vector-to-gpu-dialect/82989.

This patch does exactly one thing - moves the op to gpu.
2024-11-22 15:30:47 +01:00
Diego Caballero
32913724ac [mlir][vector] Fix 0-d vector transfer mask inference (#116526)
When inferring the mask of a transfer operation that results in a single `i1` element, 
we could represent it using either `vector<i1>` or vector<1xi1>. To avoid type mismatches,
this PR updates the mask inference logic to consistently generate `vector<1xi1>` for
these cases. We can enable 0-D masks if they are needed in the future.

See: https://github.com/llvm/llvm-project/issues/116197
2024-11-20 20:57:39 -08:00
Diego Caballero
af5c471a4d [mlir][Vector] Add vector.extract(vector.shuffle) folder (#115105)
This PR adds a folder for extracting an element from a vector shuffle.
It turns something like:

```
   %shuffle = vector.shuffle %a, %b [0, 8, 7, 15]
     : vector<8xf32>, vector<8xf32>
   %extract = vector.extract %shuffle[3] : f32 from vector<4xf32>
```

into:

```
   %extract = vector.extract %b[7] : f32 from vector<8xf32>
```
2024-11-06 18:17:12 -08:00
Ivan Butygin
f54cdc5d6e [mlir] IntegerRangeAnalysis: add support for vector type (#112292)
Treat integer range for vector type as union of ranges of individual
elements. With this semantics, most arith ops on vectors will work out
of the box, the only special handling needed for constants and vector
elements manipulation ops.

The end goal of these changes is to be able to optimize vectorized index
calculations.
2024-11-01 23:58:16 +03:00
Manupa Karunaratne
a6e72f9392 [MLIR][Vector] Add Lowering for vector.step (#113655)
Currently, the lowering for vector.step lives
under a folder. This is not ideal if we want
to do transformation on it and defer the
 materizaliztion of the constants much later.

This commits adds a rewrite pattern that
could be used by using
`transform.structured.vectorize_children_and_apply_patterns`
transform dialect operation.

Moreover, the rewriter of vector.step is also
now used in -convert-vector-to-llvm pass where
it handles scalable and non-scalable types as
LLVM expects it.

As a consequence of removing the vector.step
lowering as its folder, linalg vectorization
will keep vector.step intact.
2024-11-01 16:38:36 +00:00
Kunwar Grover
2c5eea0e88 [mlir][Vector] Fix vector.insert folder for scalar to 0-d inserts (#113828)
The current vector.insert folder tries to replace a scalar with a 0-rank
vector. This patch fixes this crash by not folding unless they types of
the result and replacement are same.
2024-10-29 22:47:44 +00:00
Jacques Pienaar
bb00f5b1ed [mlir][vector] Remove unneeded mask restriction (#113742)
These were added when the only mapping was to LLVM.
2024-10-25 20:45:44 -07:00
Benoit Jacob
a9ebdbb5ac [MLIR] Vector: turn the ExtractStridedSlice rewrite pattern from #111541 into a canonicalization (#111614)
This is a reasonable canonicalization because `extract` is more
constrained than `extract_strided_slices`, so there is no loss of
semantics here, just lifting an op to a special-case higher/constrained
op. And the additional `shape_cast` is merely adding leading unit dims
to match the original result type.

Context: discussion on #111541. I wasn't sure how this would turn out,
but in the process of writing this PR, I discovered at least 2 bugs in
the pattern introduced in #111541, which shows the value of shared
canonicalization patterns which are exercised on a high number of
testcases.

---------

Signed-off-by: Benoit Jacob <jacob.benoit.1@gmail.com>
2024-10-09 09:24:23 -04:00
Kunwar Grover
32db6fbdb9 [mlir][vector] Implement speculation for vector.transferx ops (#111533)
This patch implements speculation for
vector.transfer_read/vector.transfer_write ops, allowing these ops to
work with LICM.
2024-10-09 13:50:33 +01:00
Andrzej Warzyński
56d6b56739 [mlir][vector] Relax the requirements on broadcast dims (#99341)
NOTE: This is a follow-up for #97049 in which the `in_bounds` attribute
was made mandatory.

This PR updates the semantics of the `in_bounds` attribute so that
broadcast dimensions are no longer required to be "in bounds".
Specifically, these xfer_read/xfer_write Ops become valid after this
change:

```mlir
  %read = vector.transfer_read %A[%base1, %base2], %pad
      {in_bounds = [false], permutation_map = affine_map<(d0, d1) -> (0)>}
      {permutation_map = affine_map<(d0, d1) -> (0)>}
      : memref<?x?xf32>, vector<9xf32>

  vector.transfer_write %vec, %A[%base1, %base2],
      {in_bounds = [false], permutation_map = affine_map<(d0, d1) -> (0)>}
      {permutation_map = affine_map<(d0, d1) -> (0)>}
      : vector<9xf32>, memref<?x?xf32>
```

Note that the value `false` merely means "may run out-of-bounds", i.e.,
the corresponding access can still be "in bounds". In fact, the folder
for xfer Ops is also updated (*) and will update the attribute value
corresponding to broadcast dims to `true` if all non-broadcast dims
are marked as "in bounds". 

Note that this PR doesn't change any of the lowerings. The changes in
"SuperVectorize.cpp", "Vectorization.cpp" and "AffineMap.cpp" are simple
reverts of recent changes in #97049. Those were only meant to facilitate
making `in_bounds` mandatory and to work around the extra requirements
for broadcast dims (those requirements ere removed in this PR). All
changes in tests are also reverts of changes from #97049.

For context, here's a PR in which "broadcast" dims where forced to
always be "in-bounds":
  * https://reviews.llvm.org/D102566

(*) See `foldTransferInBoundsAttribute`.
2024-10-04 07:41:20 +01:00
Andrzej Warzyński
1335a11176 [mlir][vector][nfc] Clean-up VectorOps.{h|cpp} (#109316) 2024-09-19 21:45:01 +01:00
Diego Caballero
bcd65ba612 [mlir][Vector] Verify that masked ops implement MaskableOpInterface (#108123)
This PR fixes a bug in `MaskOp::verifier` that allowed `vector.mask` to
mask operations that did not implement the MaskableOpInterface.
2024-09-19 10:17:13 -07:00
Ivan Butygin
f325085878 [mlir][vector] Relax strides check for 1-element vector load/stores (#108998)
Single elememst vector load/stores are equivalent to scalar load/stores,
so they don't need memref to be contigious.
2024-09-19 13:12:32 +03:00
Jie Fu
9d8950a8d9 [mlir] 'dyn_cast' is deprecated: Use mlir::dyn_cast<U>() instead (NFC)
/llvm-project/mlir/lib/Dialect/Vector/IR/VectorOps.cpp:2923:29: error: 'dyn_cast' is deprecated: Use mlir::dyn_cast<U>() instead [-Werror,-Wdeprecated-declarations]
 2923 |     if (auto intAttr = attr.dyn_cast<IntegerAttr>()) {
      |                             ^
/llvm-project/mlir/include/mlir/IR/Attributes.h:184:14: note: 'dyn_cast' has been explicitly marked deprecated here
  184 | U Attribute::dyn_cast() const {
      |              ^
2024-09-09 09:00:15 +08:00
Rajveer Singh Bharadwaj
1c4b04ce1f [mlir] Fix crash in InsertOpConstantFolder when vector.insert operand is from a llvm.mlir.constant op (#88314)
In cases where llvm.mlir.constant has an attribute with a different type than the returned type,
the folder use to create an incorrect DenseElementsAttr and crash.

Resolves #74236
2024-09-09 01:44:30 +02:00
Benjamin Maxwell
5f26497da7 [mlir][vector] Use DenseI64ArrayAttr in vector.multi_reduction (#102637)
This prevents some unnecessary conversions to/from int64_t and
IntegerAttr.
2024-08-10 14:10:24 +01:00
Benjamin Maxwell
9b06e25e73 [mlir][vector] Add mask elimination transform (#99314)
This adds a new transform `eliminateVectorMasks()` which aims at
removing scalable `vector.create_masks` that will be all-true at
runtime. It attempts to do this by simply pattern-matching the mask
operands (similar to some canonicalizations), if that does not lead to
an answer (is all-true? yes/no), then value bounds analysis will be used
to find the lower bound of the unknown operands. If the lower bound is
>= to the corresponding mask vector type dim, then that dimension of the
mask is all true.

Note that the pattern matching prevents expensive value-bounds analysis
in cases where the mask won't be all true.

For example:
```mlir
%mask = vector.create_mask %dynamicValue, %c2 : vector<8x4xi1>
```
From looking at `%c2` we can tell this is not going to be an all-true
mask, so we don't need to run the value-bounds analysis for
`%dynamicValue` (and can exit the transform early).

Note: Eliminating create_masks here means replacing them with all-true
constants (which will then lead to the masks folding away).
2024-08-09 10:51:49 +01:00
Andrzej Warzyński
22a130220c [mlir][vector] Add more tests for ConvertVectorToLLVM (1/n) (#101936)
Adds tests with scalable vectors for the Vector-To-LLVM conversion pass.
Covers the following Ops:
  * vector.bitcast
  * vector.broadcast

Note, this has uncovered some missing logic in `BroadcastOpLowering`.
This PR fixes the most basic cases where the scalable flags were dropped
and the generated code was incorrect. Also, the conditions in
`vector::isBroadcastableTo` are relaxed to allow cases like this:
```mlir
%0 = vector.broadcast %arg0 : vector<1xf32> to vector<[4]xf32>
```

The `BroadcastOpLowering` pattern is effectively disabled for scalable
vectors in more complex cases where an SCF loop would be required to
loop over the scalable dims, e.g.:
```mlir
 %0 = vector.broadcast %arg0 : vector<[4]x1x2xf32> to vector<[4]x3x2xf32>
```

These cases are marked as "Stretch not at start" in the code. In those
cases, support for scalable vectors is left as a TODO.
2024-08-08 15:57:36 +01:00
Andrzej Warzyński
1919db907b [mlir][vector] Clarify the semantics of BroadcastOp (#101928)
Clarifies the semantics of `vector.broadcast` in the context of scalable
vectors. In particular, broadcasting a unit scalable dim, `[1]`, is not
valid unless there's a match between the output and the input dims.
See the examples below for an illustration:

```mlir
// VALID
 %0 = vector.broadcast %arg0 : vector<[1]xf32> to vector<4x[1]xf32>
// INVALID
 %0 = vector.broadcast %arg0 : vector<[1]xf32> to vector<[4]xf32>
// VALID FIXED-WIDTH EQUIVALENT
 %0 = vector.broadcast %arg0 : vector<1xf32> to vector<4xf32>
```

Documentation, the Op verifier and tests are updated accordingly.
2024-08-08 09:11:19 +01:00
Benjamin Maxwell
dd8a9e2b8c [mlir][vector] Remove vector.reshape operation (#101645)
This operation was added five years ago and has no lowerings or uses
within upstream MLIR (and no reported uses downstream). There’s only a
handful of round-trip tests.

See related RFC:

https://discourse.llvm.org/t/rfc-should-vector-reshape-be-removed/80478/3
2024-08-05 10:15:02 +01:00
Kazu Hirata
5262865aac [mlir] Construct SmallVector with ArrayRef (NFC) (#101896) 2024-08-04 11:43:05 -07:00
Benjamin Maxwell
b4444dca47 [mlir][vector] Use DenseI64ArrayAttr for shuffle masks (#101163)
Follow on from #100997. This again removes from boilerplate conversions
to/from IntegerAttr and int64_t (otherwise, this is a NFC).
2024-07-30 15:00:14 +01:00
Benjamin Maxwell
0d9b439408 [mlir][vector] Use DenseI64ArrayAttr for constant_mask dim sizes (#100997)
This prevents a bunch of boilerplate conversions to/from IntegerAttrs
and int64_ts. Other than that this is a NFC.
2024-07-29 18:08:37 +01:00
Andrzej Warzyński
98c73d5df7 [mlir][vector] Restrict vector.shape_cast (scalable vectors) (#100331)
Updates the verifier for `vector.shape_cast` so that incorrect cases
where "scalability" is dropped are immediately rejected. For example:
```mlir
  vector.shape_cast %vec : vector<1x1x[4]xindex> to vector<4xindex>
```

Also, as a separate PR, I've prepared a fix for the Linalg vectorizer to
avoid generating such shape casts (*):
* https://github.com/llvm/llvm-project/pull/100325

(*) Note, that's just one specific case that I've identified so far.
2024-07-25 09:50:25 +01:00
Andrzej Warzyński
2ee5586ac7 [mlir][vector] Make the in_bounds attribute mandatory (#97049)
At the moment, the in_bounds attribute has two confusing/contradicting
properties:
  1. It is both optional _and_ has an effective default-value.
  2. The default value is "out-of-bounds" for non-broadcast dims, and
     "in-bounds" for broadcast dims.

(see the `isDimInBounds` vector interface method for an example of this
"default" behaviour [1]).

This PR aims to clarify the logic surrounding the `in_bounds` attribute
by:
  * making the attribute mandatory (i.e. it is always present),
  * always setting the default value to "out of bounds" (that's
    consistent with the current behaviour for the most common cases).

#### Broadcast dimensions in tests

As per [2], the broadcast dimensions requires the corresponding
`in_bounds` attribute to be `true`:
```
  vector.transfer_read op requires broadcast dimensions to be in-bounds
```

The changes in this PR mean that we can no longer rely on the
default value in cases like the following (dim 0 is a broadcast dim):
```mlir
  %read = vector.transfer_read %A[%base1, %base2], %f, %mask
      {permutation_map = affine_map<(d0, d1) -> (0, d1)>} :
    memref<?x?xf32>, vector<4x9xf32>
```

Instead, the broadcast dimension has to explicitly be marked as "in
bounds:

```mlir
  %read = vector.transfer_read %A[%base1, %base2], %f, %mask
      {in_bounds = [true, false], permutation_map = affine_map<(d0, d1) -> (0, d1)>} :
    memref<?x?xf32>, vector<4x9xf32>
```

All tests with broadcast dims are updated accordingly.

#### Changes in "SuperVectorize.cpp" and "Vectorization.cpp"

The following patterns in "Vectorization.cpp" are updated to explicitly
set the `in_bounds` attribute to `false`:
* `LinalgCopyVTRForwardingPattern` and `LinalgCopyVTWForwardingPattern`

Also, `vectorizeAffineLoad` (from "SuperVectorize.cpp") and
`vectorizeAsLinalgGeneric` (from "Vectorization.cpp") are updated to
make sure that xfer Ops created by these hooks set the dimension
corresponding to broadcast dims as "in bounds". Otherwise, the Op
verifier would complain

Note that there is no mechanism to verify whether the corresponding
memory access are indeed in bounds. Still, this is consistent with the
current behaviour where the broadcast dim would be implicitly assumed
to be "in bounds".

[1]
4145ad2bac/mlir/include/mlir/Interfaces/VectorInterfaces.td (L243-L246)
[2]
https://mlir.llvm.org/docs/Dialects/Vector/#vectortransfer_read-vectortransferreadop
2024-07-16 16:49:52 +01:00