github.com/apache/arrow/go/v14@v14.0.2/arrow/compute/exec/span.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one 2 // or more contributor license agreements. See the NOTICE file 3 // distributed with this work for additional information 4 // regarding copyright ownership. The ASF licenses this file 5 // to you under the Apache License, Version 2.0 (the 6 // "License"); you may not use this file except in compliance 7 // with the License. You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 //go:build go1.18 18 19 package exec 20 21 import ( 22 "reflect" 23 "sync/atomic" 24 "unsafe" 25 26 "github.com/apache/arrow/go/v14/arrow" 27 "github.com/apache/arrow/go/v14/arrow/array" 28 "github.com/apache/arrow/go/v14/arrow/bitutil" 29 "github.com/apache/arrow/go/v14/arrow/memory" 30 "github.com/apache/arrow/go/v14/arrow/scalar" 31 ) 32 33 // BufferSpan is a lightweight Buffer holder for ArraySpans that does not 34 // take ownership of the underlying memory.Buffer at all or could be 35 // used to reference raw byte slices instead. 36 type BufferSpan struct { 37 // Buf should be the byte slice representing this buffer, if this is 38 // nil then this bufferspan should be considered empty. 39 Buf []byte 40 // Owner should point to an underlying parent memory.Buffer if this 41 // memory is owned by a different, existing, buffer. Retain is not 42 // called on this buffer, so it must not be released as long as 43 // this BufferSpan refers to it. 44 Owner *memory.Buffer 45 // SelfAlloc tracks whether or not this bufferspan is the only owner 46 // of the Owning memory.Buffer. This happens when preallocating 47 // memory or if a kernel allocates it's own buffer for a result. 48 // In these cases, we have to know so we can properly maintain the 49 // refcount if this is later turned into an ArrayData object. 50 SelfAlloc bool 51 } 52 53 // SetBuffer sets the given buffer into this BufferSpan and marks 54 // SelfAlloc as false. This should be called when setting a buffer 55 // that is externally owned/created. 56 func (b *BufferSpan) SetBuffer(buf *memory.Buffer) { 57 b.Buf = buf.Bytes() 58 b.Owner = buf 59 b.SelfAlloc = false 60 } 61 62 // WrapBuffer wraps this bufferspan around a buffer and marks 63 // SelfAlloc as true. This should be called when setting a buffer 64 // that was allocated as part of an execution rather than just 65 // re-using an existing buffer from an input array. 66 func (b *BufferSpan) WrapBuffer(buf *memory.Buffer) { 67 b.Buf = buf.Bytes() 68 b.Owner = buf 69 b.SelfAlloc = true 70 } 71 72 // ArraySpan is a light-weight, non-owning version of arrow.ArrayData 73 // for more efficient handling with computation and engines. We use 74 // explicit go Arrays to define the buffers and some scratch space 75 // for easily populating and shifting around pointers to memory without 76 // having to worry about and deal with retain/release during calculations. 77 type ArraySpan struct { 78 Type arrow.DataType 79 Len int64 80 Nulls int64 81 Offset int64 82 Buffers [3]BufferSpan 83 84 // Scratch is a holding spot for things such as 85 // offsets or union type codes when converting from scalars 86 Scratch [2]uint64 87 88 Children []ArraySpan 89 } 90 91 // if an error is encountered, call Release on a preallocated span 92 // to ensure it releases any self-allocated buffers, it will 93 // not call release on buffers it doesn't own (SelfAlloc != true) 94 func (a *ArraySpan) Release() { 95 for _, c := range a.Children { 96 c.Release() 97 } 98 99 for _, b := range a.Buffers { 100 if b.SelfAlloc { 101 b.Owner.Release() 102 } 103 } 104 } 105 106 func (a *ArraySpan) MayHaveNulls() bool { 107 return atomic.LoadInt64(&a.Nulls) != 0 && a.Buffers[0].Buf != nil 108 } 109 110 // UpdateNullCount will count the bits in the null bitmap and update the 111 // number of nulls if the current null count is unknown, otherwise it just 112 // returns the value of a.Nulls 113 func (a *ArraySpan) UpdateNullCount() int64 { 114 curNulls := atomic.LoadInt64(&a.Nulls) 115 if curNulls != array.UnknownNullCount { 116 return curNulls 117 } 118 119 newNulls := a.Len - int64(bitutil.CountSetBits(a.Buffers[0].Buf, int(a.Offset), int(a.Len))) 120 atomic.StoreInt64(&a.Nulls, newNulls) 121 return newNulls 122 } 123 124 // Dictionary returns a pointer to the array span for the dictionary which 125 // we will always place as the first (and only) child if it exists. 126 func (a *ArraySpan) Dictionary() *ArraySpan { return &a.Children[0] } 127 128 // NumBuffers returns the number of expected buffers for this type 129 func (a *ArraySpan) NumBuffers() int { return getNumBuffers(a.Type) } 130 131 // MakeData generates an arrow.ArrayData object for this ArraySpan, 132 // properly updating the buffer ref count if necessary. 133 func (a *ArraySpan) MakeData() arrow.ArrayData { 134 var bufs [3]*memory.Buffer 135 for i := range bufs { 136 b := a.GetBuffer(i) 137 bufs[i] = b 138 if b != nil && a.Buffers[i].SelfAlloc { 139 // if this buffer is just a pointer to another existing buffer 140 // then we never bumped the refcount for that buffer. 141 // As a result, we won't call release here so that the call 142 // to array.NewData properly updates the ref counts of the buffers. 143 // If instead this buffer was allocated during calculation 144 // (such as during prealloc or by a kernel itself) 145 // then we need to release after we create the ArrayData so that it 146 // maintains the correct refcount of 1, giving the resulting 147 // ArrayData object ownership of this buffer. 148 defer b.Release() 149 } 150 } 151 152 var ( 153 nulls = int(atomic.LoadInt64(&a.Nulls)) 154 length = int(a.Len) 155 off = int(a.Offset) 156 dt = a.Type 157 children []arrow.ArrayData 158 ) 159 160 if a.Type.ID() == arrow.NULL { 161 nulls = length 162 } else if len(a.Buffers[0].Buf) == 0 { 163 nulls = 0 164 } 165 166 // we use a.Type for the NewData call at the end, so we can 167 // handle extension types by using dt to point to the storage type 168 // and let the proper extension type get set into the ArrayData 169 // object we return. 170 if dt.ID() == arrow.EXTENSION { 171 dt = dt.(arrow.ExtensionType).StorageType() 172 } 173 174 if dt.ID() == arrow.DICTIONARY { 175 result := array.NewData(a.Type, length, bufs[:a.NumBuffers()], nil, nulls, off) 176 dict := a.Dictionary().MakeData() 177 defer dict.Release() 178 result.SetDictionary(dict) 179 return result 180 } else if dt.ID() == arrow.DENSE_UNION || dt.ID() == arrow.SPARSE_UNION { 181 bufs[0] = nil 182 nulls = 0 183 } 184 185 if len(a.Children) > 0 { 186 children = make([]arrow.ArrayData, len(a.Children)) 187 for i, c := range a.Children { 188 d := c.MakeData() 189 defer d.Release() 190 children[i] = d 191 } 192 } 193 return array.NewData(a.Type, length, bufs[:a.NumBuffers()], children, nulls, off) 194 } 195 196 // MakeArray is a convenience function for calling array.MakeFromData(a.MakeData()) 197 func (a *ArraySpan) MakeArray() arrow.Array { 198 d := a.MakeData() 199 defer d.Release() 200 return array.MakeFromData(d) 201 } 202 203 // SetSlice updates the offset and length of this ArraySpan to refer to 204 // a specific slice of the underlying buffers. 205 func (a *ArraySpan) SetSlice(off, length int64) { 206 if off == a.Offset && length == a.Len { 207 // don't modify the nulls if the slice is the entire span 208 return 209 } 210 211 if a.Type.ID() != arrow.NULL { 212 if a.Nulls != 0 { 213 if a.Nulls == a.Len { 214 a.Nulls = length 215 } else { 216 a.Nulls = array.UnknownNullCount 217 } 218 } 219 } else { 220 a.Nulls = length 221 } 222 223 a.Offset, a.Len = off, length 224 } 225 226 // GetBuffer returns the buffer for the requested index. If this buffer 227 // is owned by another array/arrayspan the Owning buffer is returned, 228 // otherwise if this slice has no owning buffer, we call NewBufferBytes 229 // to wrap it as a memory.Buffer. Can also return nil if there is no 230 // buffer in this index. 231 func (a *ArraySpan) GetBuffer(idx int) *memory.Buffer { 232 buf := a.Buffers[idx] 233 switch { 234 case buf.Owner != nil: 235 return buf.Owner 236 case buf.Buf != nil: 237 return memory.NewBufferBytes(buf.Buf) 238 } 239 return nil 240 } 241 242 // convenience function to resize the children slice if necessary, 243 // or just shrink the slice without re-allocating if there's enough 244 // capacity already. 245 func (a *ArraySpan) resizeChildren(i int) { 246 if cap(a.Children) >= i { 247 a.Children = a.Children[:i] 248 } else { 249 a.Children = make([]ArraySpan, i) 250 } 251 } 252 253 // convenience function for populating the offsets buffer from a scalar 254 // value's size. 255 func setOffsetsForScalar[T int32 | int64](span *ArraySpan, buf []T, valueSize int64, bufidx int) { 256 buf[0] = 0 257 buf[1] = T(valueSize) 258 259 b := (*reflect.SliceHeader)(unsafe.Pointer(&buf)) 260 s := (*reflect.SliceHeader)(unsafe.Pointer(&span.Buffers[bufidx].Buf)) 261 s.Data = b.Data 262 s.Len = 2 * int(unsafe.Sizeof(T(0))) 263 s.Cap = s.Len 264 265 span.Buffers[bufidx].Owner = nil 266 span.Buffers[bufidx].SelfAlloc = false 267 } 268 269 // FillFromScalar populates this ArraySpan as if it were a 1 length array 270 // with the single value equal to the passed in Scalar. 271 func (a *ArraySpan) FillFromScalar(val scalar.Scalar) { 272 var ( 273 trueBit byte = 0x01 274 falseBit byte = 0x00 275 ) 276 277 a.Type = val.DataType() 278 a.Len = 1 279 typeID := a.Type.ID() 280 if val.IsValid() { 281 a.Nulls = 0 282 } else { 283 a.Nulls = 1 284 } 285 286 if !arrow.IsUnion(typeID) && typeID != arrow.NULL { 287 if val.IsValid() { 288 a.Buffers[0].Buf = []byte{trueBit} 289 } else { 290 a.Buffers[0].Buf = []byte{falseBit} 291 } 292 a.Buffers[0].Owner = nil 293 a.Buffers[0].SelfAlloc = false 294 } 295 296 switch { 297 case typeID == arrow.BOOL: 298 if val.(*scalar.Boolean).Value { 299 a.Buffers[1].Buf = []byte{trueBit} 300 } else { 301 a.Buffers[1].Buf = []byte{falseBit} 302 } 303 a.Buffers[1].Owner = nil 304 a.Buffers[1].SelfAlloc = false 305 case arrow.IsPrimitive(typeID) || arrow.IsDecimal(typeID): 306 sc := val.(scalar.PrimitiveScalar) 307 a.Buffers[1].Buf = sc.Data() 308 a.Buffers[1].Owner = nil 309 a.Buffers[1].SelfAlloc = false 310 case typeID == arrow.DICTIONARY: 311 sc := val.(scalar.PrimitiveScalar) 312 a.Buffers[1].Buf = sc.Data() 313 a.Buffers[1].Owner = nil 314 a.Buffers[1].SelfAlloc = false 315 a.resizeChildren(1) 316 a.Children[0].SetMembers(val.(*scalar.Dictionary).Value.Dict.Data()) 317 case arrow.IsBaseBinary(typeID): 318 sc := val.(scalar.BinaryScalar) 319 a.Buffers[1].Buf = arrow.Uint64Traits.CastToBytes(a.Scratch[:]) 320 a.Buffers[1].Owner = nil 321 a.Buffers[1].SelfAlloc = false 322 323 var dataBuffer []byte 324 if sc.IsValid() { 325 dataBuffer = sc.Data() 326 a.Buffers[2].Owner = sc.Buffer() 327 a.Buffers[2].SelfAlloc = false 328 } 329 if arrow.IsBinaryLike(typeID) { 330 setOffsetsForScalar(a, 331 unsafe.Slice((*int32)(unsafe.Pointer(&a.Scratch[0])), 2), 332 int64(len(dataBuffer)), 1) 333 } else { 334 // large_binary_like 335 setOffsetsForScalar(a, 336 unsafe.Slice((*int64)(unsafe.Pointer(&a.Scratch[0])), 2), 337 int64(len(dataBuffer)), 1) 338 } 339 a.Buffers[2].Buf = dataBuffer 340 case typeID == arrow.FIXED_SIZE_BINARY: 341 sc := val.(scalar.BinaryScalar) 342 if !sc.IsValid() { 343 a.Buffers[1].Buf = make([]byte, sc.DataType().(*arrow.FixedSizeBinaryType).ByteWidth) 344 a.Buffers[1].Owner = nil 345 a.Buffers[1].SelfAlloc = false 346 break 347 } 348 a.Buffers[1].Buf = sc.Data() 349 a.Buffers[1].Owner = sc.Buffer() 350 a.Buffers[1].SelfAlloc = false 351 case arrow.IsListLike(typeID): 352 sc := val.(scalar.ListScalar) 353 valueLen := 0 354 a.resizeChildren(1) 355 356 if sc.GetList() != nil { 357 a.Children[0].SetMembers(sc.GetList().Data()) 358 valueLen = sc.GetList().Len() 359 } else { 360 // even when the value is null, we must populate 361 // child data to yield a valid array. ugh 362 FillZeroLength(sc.DataType().(arrow.NestedType).Fields()[0].Type, &a.Children[0]) 363 } 364 365 switch typeID { 366 case arrow.LIST, arrow.MAP: 367 setOffsetsForScalar(a, 368 unsafe.Slice((*int32)(unsafe.Pointer(&a.Scratch[0])), 2), 369 int64(valueLen), 1) 370 case arrow.LARGE_LIST: 371 setOffsetsForScalar(a, 372 unsafe.Slice((*int64)(unsafe.Pointer(&a.Scratch[0])), 2), 373 int64(valueLen), 1) 374 default: 375 // fixed size list has no second buffer 376 a.Buffers[1].Buf, a.Buffers[1].Owner = nil, nil 377 a.Buffers[1].SelfAlloc = false 378 } 379 case typeID == arrow.STRUCT: 380 sc := val.(*scalar.Struct) 381 a.Buffers[1].Buf = nil 382 a.Buffers[1].Owner = nil 383 a.Buffers[1].SelfAlloc = false 384 a.resizeChildren(len(sc.Value)) 385 for i, v := range sc.Value { 386 a.Children[i].FillFromScalar(v) 387 } 388 case arrow.IsUnion(typeID): 389 // first buffer is kept null since unions have no validity vector 390 a.Buffers[0].Buf, a.Buffers[0].Owner = nil, nil 391 a.Buffers[0].SelfAlloc = false 392 393 a.Buffers[1].Buf = arrow.Uint64Traits.CastToBytes(a.Scratch[:])[:1] 394 a.Buffers[1].Owner = nil 395 a.Buffers[1].SelfAlloc = false 396 codes := unsafe.Slice((*arrow.UnionTypeCode)(unsafe.Pointer(&a.Buffers[1].Buf[0])), 1) 397 398 a.resizeChildren(len(a.Type.(arrow.UnionType).Fields())) 399 switch sc := val.(type) { 400 case *scalar.DenseUnion: 401 codes[0] = sc.TypeCode 402 // has offset, start 4 bytes in so it's aligned to the 32-bit boundaries 403 off := unsafe.Slice((*int32)(unsafe.Add(unsafe.Pointer(&a.Scratch[0]), arrow.Int32SizeBytes)), 2) 404 setOffsetsForScalar(a, off, 1, 2) 405 // we can't "see" the other arrays in the union, but we put the "active" 406 // union array in the right place and fill zero-length arrays for 407 // the others. 408 childIDS := a.Type.(arrow.UnionType).ChildIDs() 409 for i, f := range a.Type.(arrow.UnionType).Fields() { 410 if i == childIDS[sc.TypeCode] { 411 a.Children[i].FillFromScalar(sc.Value) 412 } else { 413 FillZeroLength(f.Type, &a.Children[i]) 414 } 415 } 416 case *scalar.SparseUnion: 417 codes[0] = sc.TypeCode 418 // sparse union scalars have a full complement of child values 419 // even though only one of them is relevant, so we just fill them 420 // in here 421 for i, v := range sc.Value { 422 a.Children[i].FillFromScalar(v) 423 } 424 } 425 case typeID == arrow.EXTENSION: 426 // pass through storage 427 sc := val.(*scalar.Extension) 428 a.FillFromScalar(sc.Value) 429 // restore the extension type 430 a.Type = val.DataType() 431 case typeID == arrow.NULL: 432 for i := range a.Buffers { 433 a.Buffers[i].Buf = nil 434 a.Buffers[i].Owner = nil 435 a.Buffers[i].SelfAlloc = false 436 } 437 } 438 } 439 440 func (a *ArraySpan) SetDictionary(span *ArraySpan) { 441 a.resizeChildren(1) 442 a.Children[0].Release() 443 a.Children[0] = *span 444 } 445 446 // TakeOwnership is like SetMembers only this takes ownership of 447 // the buffers by calling Retain on them so that the passed in 448 // ArrayData can be released without negatively affecting this 449 // ArraySpan 450 func (a *ArraySpan) TakeOwnership(data arrow.ArrayData) { 451 a.Type = data.DataType() 452 a.Len = int64(data.Len()) 453 if a.Type.ID() == arrow.NULL { 454 a.Nulls = a.Len 455 } else { 456 a.Nulls = int64(data.NullN()) 457 } 458 a.Offset = int64(data.Offset()) 459 460 for i, b := range data.Buffers() { 461 if b != nil { 462 a.Buffers[i].WrapBuffer(b) 463 b.Retain() 464 } else { 465 a.Buffers[i].Buf = nil 466 a.Buffers[i].Owner = nil 467 a.Buffers[i].SelfAlloc = false 468 } 469 } 470 471 typeID := a.Type.ID() 472 if a.Buffers[0].Buf == nil { 473 switch typeID { 474 case arrow.NULL, arrow.SPARSE_UNION, arrow.DENSE_UNION: 475 default: 476 // should already be zero, but we make sure 477 a.Nulls = 0 478 } 479 } 480 481 for i := len(data.Buffers()); i < 3; i++ { 482 a.Buffers[i].Buf = nil 483 a.Buffers[i].Owner = nil 484 a.Buffers[i].SelfAlloc = false 485 } 486 487 if typeID == arrow.DICTIONARY { 488 a.resizeChildren(1) 489 dict := data.Dictionary() 490 if dict != (*array.Data)(nil) { 491 a.Children[0].TakeOwnership(dict) 492 } 493 } else { 494 a.resizeChildren(len(data.Children())) 495 for i, c := range data.Children() { 496 a.Children[i].TakeOwnership(c) 497 } 498 } 499 } 500 501 // SetMembers populates this ArraySpan from the given ArrayData object. 502 // As this is a non-owning reference, the ArrayData object must not 503 // be fully released while this ArraySpan is in use, otherwise any buffers 504 // referenced will be released too 505 func (a *ArraySpan) SetMembers(data arrow.ArrayData) { 506 a.Type = data.DataType() 507 a.Len = int64(data.Len()) 508 if a.Type.ID() == arrow.NULL { 509 a.Nulls = a.Len 510 } else { 511 a.Nulls = int64(data.NullN()) 512 } 513 a.Offset = int64(data.Offset()) 514 515 for i, b := range data.Buffers() { 516 if b != nil { 517 a.Buffers[i].SetBuffer(b) 518 } else { 519 a.Buffers[i].Buf = nil 520 a.Buffers[i].Owner = nil 521 a.Buffers[i].SelfAlloc = false 522 } 523 } 524 525 typeID := a.Type.ID() 526 if a.Buffers[0].Buf == nil { 527 switch typeID { 528 case arrow.NULL, arrow.SPARSE_UNION, arrow.DENSE_UNION: 529 default: 530 // should already be zero, but we make sure 531 a.Nulls = 0 532 } 533 } 534 535 for i := len(data.Buffers()); i < 3; i++ { 536 a.Buffers[i].Buf = nil 537 a.Buffers[i].Owner = nil 538 a.Buffers[i].SelfAlloc = false 539 } 540 541 if typeID == arrow.DICTIONARY { 542 a.resizeChildren(1) 543 dict := data.Dictionary() 544 if dict != (*array.Data)(nil) { 545 a.Children[0].SetMembers(dict) 546 } 547 } else { 548 if cap(a.Children) >= len(data.Children()) { 549 a.Children = a.Children[:len(data.Children())] 550 } else { 551 a.Children = make([]ArraySpan, len(data.Children())) 552 } 553 for i, c := range data.Children() { 554 a.Children[i].SetMembers(c) 555 } 556 } 557 } 558 559 // ExecValue represents a single input to an execution which could 560 // be either an Array (ArraySpan) or a Scalar value 561 type ExecValue struct { 562 Array ArraySpan 563 Scalar scalar.Scalar 564 } 565 566 func (e *ExecValue) IsArray() bool { return e.Scalar == nil } 567 func (e *ExecValue) IsScalar() bool { return !e.IsArray() } 568 569 func (e *ExecValue) Type() arrow.DataType { 570 if e.IsArray() { 571 return e.Array.Type 572 } 573 return e.Scalar.DataType() 574 } 575 576 // ExecResult is the result of a kernel execution and should be populated 577 // by the execution functions and/or a kernel. For now we're just going to 578 // alias an ArraySpan. 579 type ExecResult = ArraySpan 580 581 // ExecSpan represents a slice of inputs and is used to provide slices 582 // of input values to iterate over. 583 // 584 // Len is the length of the span (all elements in Values should either 585 // be scalar or an array with a length + offset of at least Len). 586 type ExecSpan struct { 587 Len int64 588 Values []ExecValue 589 } 590 591 func getNumBuffers(dt arrow.DataType) int { 592 switch dt.ID() { 593 case arrow.RUN_END_ENCODED: 594 return 0 595 case arrow.NULL, arrow.STRUCT, arrow.FIXED_SIZE_LIST: 596 return 1 597 case arrow.BINARY, arrow.LARGE_BINARY, arrow.STRING, arrow.LARGE_STRING, arrow.DENSE_UNION: 598 return 3 599 case arrow.EXTENSION: 600 return getNumBuffers(dt.(arrow.ExtensionType).StorageType()) 601 default: 602 return 2 603 } 604 } 605 606 // FillZeroLength fills an ArraySpan with the appropriate information for 607 // a Zero Length Array of the provided type. 608 func FillZeroLength(dt arrow.DataType, span *ArraySpan) { 609 span.Scratch[0], span.Scratch[1] = 0, 0 610 span.Type = dt 611 span.Len = 0 612 numBufs := getNumBuffers(dt) 613 for i := 0; i < numBufs; i++ { 614 span.Buffers[i].Buf = arrow.Uint64Traits.CastToBytes(span.Scratch[:])[:0] 615 span.Buffers[i].Owner = nil 616 } 617 618 for i := numBufs; i < 3; i++ { 619 span.Buffers[i].Buf, span.Buffers[i].Owner = nil, nil 620 } 621 622 if dt.ID() == arrow.DICTIONARY { 623 span.resizeChildren(1) 624 FillZeroLength(dt.(*arrow.DictionaryType).ValueType, &span.Children[0]) 625 return 626 } 627 628 nt, ok := dt.(arrow.NestedType) 629 if !ok { 630 if len(span.Children) > 0 { 631 span.Children = span.Children[:0] 632 } 633 return 634 } 635 636 span.resizeChildren(len(nt.Fields())) 637 for i, f := range nt.Fields() { 638 FillZeroLength(f.Type, &span.Children[i]) 639 } 640 } 641 642 // PromoteExecSpanScalars promotes the values of the passed in ExecSpan 643 // from scalars to Arrays of length 1 for each value. 644 func PromoteExecSpanScalars(span ExecSpan) { 645 for i := range span.Values { 646 if span.Values[i].Scalar != nil { 647 span.Values[i].Array.FillFromScalar(span.Values[i].Scalar) 648 span.Values[i].Scalar = nil 649 } 650 } 651 }