From 151e5a1e30d1e5e30557650c2022fec2d0a1020e Mon Sep 17 00:00:00 2001 From: Patrick Damme Date: Wed, 7 Aug 2024 19:14:04 +0200 Subject: [PATCH] [DAPHNE-#755] Initial support for lists in DaphneDSL. - This PR adds basic support for lists of matrices of homogeneous physical data type and value type. - Supported operations: - createList(), length(), append(), remove(), print() - append() and remove() do not modify the given list, but return the resulting list (consistent with the bahavior of matrix/frame operations) - Concrete changes: - DaphneDSL - Four new built-in functions: createList(), length(), append(), remove(). - Updated the DaphneDSL language reference and the list of built-in functions. - DaphneIR/DAPHNE compiler - A new custom MLIR type: List. - Four new DaphneIR operations: CreateListOp, LengthOp, AppendOp, RemoveOp. - Type inference for CreateListOp, AppendOp, and RemoveOp. - Consideration of list types in the kernel extension catalog. - Lowering of the four new ops to kernel calls. - Lowering of the List type in LowerToLLVMPass in the same way as matrices/frames. - DAPHNE runtime - Four new kernels: createList, length, append, remove. - Registration of the new kernels in kernels.json. - Garbage collection of list items - ManageObjRefsPass treats lists like matrices/frames. - Besides that: - The reference counter of a data object is increased when it is inserted into a list to ensure that the object is not freed as long as the list exists. - When a list is destroyed, the reference counter of all its elements is decreased by 1. - When an element is removed from a list, its reference counter is *not* decreased, because we return the removed element (it lives on). - Added script-level test cases for the usage of lists. - Current limitations: - No support for heterogeneous data/value type (e.g., one cannot store DenseMatrix and CSRMatrix or DenseMatrix and DenseMatrix in the same list). - Not possible to create an empty list in DaphneDSL, because that would complicate type inference. - Lists are not supported in DaphneLib yet. - No get/set on list elements yet, only append and remove. - The append/remove kernels copy the input list instead of modifying it in place. While this is consistent with the behavior of matrix/frame ops, it is inefficient. However, compared to workarounds to lists, this is still much faster; Furthermore, we can use the same update-in-place mechanisms as for matrices/frames in the future, when the input list is not used anymore afterwards (see PR #609). - Information about interesting data properties gets lost by inserting and removing a matrix into/from a list. - Subclassing Structure may not be optimal; it might be better to subclass a new superclass of Structure, but I wanted to keep the refactoring overhead low for now. - Effect on decision trees DaphneDSL script - The decision trees script uses queues to keep certain matrices around. - So far, we emulated these queues through matrix concatenation (cbind/rbind), which turned out to be very inefficient. - The script uses lists for the queues now, which leads to significant performance improvements (~15x for a concrete use-case pipeline on my machine). - Closes #755. --- doc/DaphneDSL/Builtins.md | 22 ++ doc/DaphneDSL/LanguageRef.md | 3 +- scripts/algorithms/decisionTree_.daph | 63 ++---- src/compiler/lowering/LowerToLLVMPass.cpp | 5 + src/compiler/lowering/ManageObjRefsPass.cpp | 11 +- .../lowering/RewriteToCallKernelOpPass.cpp | 15 +- src/compiler/utils/CompilerUtils.h | 8 + src/ir/daphneir/DaphneDialect.cpp | 3 + .../daphneir/DaphneInferTypesOpInterface.cpp | 58 +++++ src/ir/daphneir/DaphneOps.td | 46 +++- src/ir/daphneir/DaphneTypes.td | 10 + src/parser/catalog/KernelCatalogParser.cpp | 9 + src/parser/daphnedsl/DaphneDSLBuiltins.cpp | 37 ++++ src/runtime/local/datastructures/CSRMatrix.h | 4 + .../local/datastructures/DenseMatrix.h | 4 + src/runtime/local/datastructures/List.h | 157 ++++++++++++++ src/runtime/local/kernels/Append.h | 31 +++ src/runtime/local/kernels/CreateList.h | 32 +++ src/runtime/local/kernels/Length.h | 30 +++ src/runtime/local/kernels/Remove.h | 31 +++ src/runtime/local/kernels/kernels.json | 202 ++++++++++++++++++ test/CMakeLists.txt | 1 + test/api/cli/lists/ListsTest.cpp | 47 ++++ test/api/cli/lists/lists_failure_1.daphne | 4 + test/api/cli/lists/lists_failure_2.daphne | 5 + test/api/cli/lists/lists_failure_3.daphne | 4 + test/api/cli/lists/lists_success_1.daphne | 4 + test/api/cli/lists/lists_success_1.txt | 3 + test/api/cli/lists/lists_success_2.daphne | 4 + test/api/cli/lists/lists_success_2.txt | 8 + test/api/cli/lists/lists_success_3.daphne | 20 ++ test/api/cli/lists/lists_success_3.txt | 50 +++++ test/api/cli/lists/lists_success_4.daphne | 9 + test/api/cli/lists/lists_success_4.txt | 11 + test/tags.h | 1 + 35 files changed, 897 insertions(+), 55 deletions(-) create mode 100644 src/runtime/local/datastructures/List.h create mode 100644 src/runtime/local/kernels/Append.h create mode 100644 src/runtime/local/kernels/CreateList.h create mode 100644 src/runtime/local/kernels/Length.h create mode 100644 src/runtime/local/kernels/Remove.h create mode 100644 test/api/cli/lists/ListsTest.cpp create mode 100644 test/api/cli/lists/lists_failure_1.daphne create mode 100644 test/api/cli/lists/lists_failure_2.daphne create mode 100644 test/api/cli/lists/lists_failure_3.daphne create mode 100644 test/api/cli/lists/lists_success_1.daphne create mode 100644 test/api/cli/lists/lists_success_1.txt create mode 100644 test/api/cli/lists/lists_success_2.daphne create mode 100644 test/api/cli/lists/lists_success_2.txt create mode 100644 test/api/cli/lists/lists_success_3.daphne create mode 100644 test/api/cli/lists/lists_success_3.txt create mode 100644 test/api/cli/lists/lists_success_4.daphne create mode 100644 test/api/cli/lists/lists_success_4.txt diff --git a/doc/DaphneDSL/Builtins.md b/doc/DaphneDSL/Builtins.md index 438f84aa3..cd963b64e 100644 --- a/doc/DaphneDSL/Builtins.md +++ b/doc/DaphneDSL/Builtins.md @@ -49,6 +49,7 @@ DaphneDSL's built-in functions can be categorized as follows: - Input/output - Data preprocessing - Measurements +- List operations ## Data generation @@ -652,3 +653,24 @@ These must be provided in a separate [`.meta`-file](/doc/FileMetaDataFormat.md). - **`now`**`()` Returns the current time since the epoch in nano seconds. + +## List operations + +- **`createList`**`(elm:matrix, ...)` + + Creates and returns a new list from the given elements `elm`. + At least one element must be specified. + +- **`length`**`(lst:list)` + + Returns the number of elements in the given list `lst`. + +- **`append`**`(lst:list, elm:matrix)` + + Appends the given matrix `elm` to the given list `lst`. + Returns the result as a new list (the argument list stays unchanged). + +- **`remove`**`(lst:list, idx:size)` + + Removes the element at position `idx` (counting starts at zero) from the given list `lst`. + Returns (1) the result as a new list (the argument list stays unchanged), and (2) the removed element. \ No newline at end of file diff --git a/doc/DaphneDSL/LanguageRef.md b/doc/DaphneDSL/LanguageRef.md index 1f7588262..3a0b6492b 100644 --- a/doc/DaphneDSL/LanguageRef.md +++ b/doc/DaphneDSL/LanguageRef.md @@ -45,7 +45,7 @@ Variables are used to refer to values. **Valid identifiers** start with a letter (`a-z`, `A-Z`) or an underscore (`_`) that can be followed by any number of letters (`a-z`, `A-Z`), underscores (`_`), and decimal digits (`0-9`). -The following reserved keywords must not be used as identifiers: `if`, `else`, `while`, `do`, `for`, `in`, `true`, `false`, `as`, `def`, `return`, `import`, `matrix`, `frame`, `scalar`, `f64`, `f32`, `si64`, `si8`, `ui64`, `ui32`, `ui8`, `str`, `nan`, and `inf`. +The following reserved keywords must not be used as identifiers: `if`, `else`, `while`, `do`, `for`, `in`, `true`, `false`, `as`, `def`, `return`, `import`, `matrix`, `frame`, `scalar`, `list`, `f64`, `f32`, `si64`, `si8`, `ui64`, `ui32`, `ui8`, `str`, `nan`, and `inf`. *Examples:* @@ -69,6 +69,7 @@ Currently, DaphneDSL supports the following *abstract* **data types**: - `matrix`: homogeneous value type for all cells - `frame`: a table with columns of potentially different value types - `scalar`: a single value +- `list`: an ordered sequence of elements of homogeneous data/value type; currently, only matrices can be elements of lists **Value types** specify the representation of individual values. We currently support: diff --git a/scripts/algorithms/decisionTree_.daph b/scripts/algorithms/decisionTree_.daph index 282b4f023..05276c3f2 100644 --- a/scripts/algorithms/decisionTree_.daph +++ b/scripts/algorithms/decisionTree_.daph @@ -297,41 +297,20 @@ def decisionTree(X:matrix, y:matrix, ctypes:matrix, # we (a) emulate lists by matrices to which we add and remove rows/columns (the "lists" part), # and (b) split the data structure into its four components (the "of lists" part). #queue = list(list(1,I,X2,y2)); # node IDs / data indicators - queue_nID = as.matrix(1); # "list" of scalars (one scalar per row) - queue_sizeni = as.matrix(ncol(I)); # "list" of scalars indicating the #cols of each element in queue_nI (one scalar per row) - queue_nI = I; # "list" of row-vectors with different #cols (all row-vectors in the list cbinded) - queue_sizeX2y2 = as.matrix(nrow(X2)); # "list" of scalars indicating the #rows of each element in queue_X2 and queue_y2 (one scalar per row) - queue_X2 = X2; # "list" of matrices with different #rows (all matrices in the list rbinded) - queue_y2 = y2; # "list" of matrices with different #rows (all matrices in the list rbinded) + queue_nID = createList([1]); + queue_nI = createList(I); + queue_X2 = createList(X2); + queue_y2 = createList(y2); # TODO .0 should not be necessary. maxPath = 1.0; - while( nrow(queue_nID) > 0 ) { + while( length(queue_nID) > 0 ) { # pop next node from queue for splitting - nID = as.scalar(queue_nID[0, ]); - sizeni = as.scalar(queue_sizeni[0, ]); - nI = queue_nI[, :sizeni]; - sizeX2y2 = as.scalar(queue_sizeX2y2[0, ]); - X2 = queue_X2[:sizeX2y2, ]; - y2 = queue_y2[:sizeX2y2, ]; - # TODO Instead of this if-then-else, it should be valid to slice rows 1:1 on 1 row, result should be 0 rows - if(nrow(queue_nID) > 1) { - queue_nID = queue_nID[1:, ]; - queue_sizeni = queue_sizeni[1: ,]; - queue_nI = queue_nI[, sizeni:]; - queue_sizeX2y2 = queue_sizeX2y2[1:, ]; - queue_X2 = queue_X2[sizeX2y2:, ]; - queue_y2 = queue_y2[sizeX2y2:, ]; - } - else { - # Create 0-row matrices for "lists" along the row axis - # and 0-col matrices for "lists" along the column axis. - queue_nID = fill(1, 0, 1); - queue_sizeni = fill(1, 0, 1); - queue_nI = fill(1.0, 1, 0); - queue_sizeX2y2 = fill(1, 0, 1); - queue_X2 = fill(1.0, 0, ncol(X2)); - queue_y2 = fill(1.0, 0, ncol(y2)); - } + queue_nID, nIDmat = remove(queue_nID, 0); + # TODO should not be necessary here. + nID = as.scalar(nIDmat); + queue_nI, nI = remove(queue_nI, 0); + queue_X2, X2 = remove(queue_X2, 0); + queue_y2, y2 = remove(queue_y2, 0); if(verbose) print("decisionTree: attempting split of node "+nID+" ("+sum(nI)+" rows)"); @@ -365,12 +344,10 @@ def decisionTree(X:matrix, y:matrix, ctypes:matrix, # split data, finalize or recurse if( validSplit ) { if( sum(Ileft) >= min_split && floor(log(IDleft,2))+2 < max_depth ) { - queue_nID = rbind(queue_nID, as.matrix(IDleft)); - queue_sizeni = rbind(queue_sizeni, as.matrix(ncol(Ileft))); - queue_nI = cbind(queue_nI, Ileft); - queue_sizeX2y2 = rbind(queue_sizeX2y2, as.matrix(nrow(X2))); - queue_X2 = rbind(queue_X2, X2); - queue_y2 = rbind(queue_y2, y2); + queue_nID = append(queue_nID, as.matrix(IDleft)); + queue_nI = append(queue_nI, Ileft); + queue_X2 = append(queue_X2, X2); + queue_y2 = append(queue_y2, y2); } else { # TODO as.bool() should not be necessary, should be casted automatically (see #661). @@ -378,12 +355,10 @@ def decisionTree(X:matrix, y:matrix, ctypes:matrix, M[,2*IDleft - 1] = as.matrix(computeLeafLabel(y2, Ileft, as.bool(classify), verbose)); } if( sum(Iright) >= min_split && floor(log(IDright,2))+2 < max_depth ) { - queue_nID = rbind(queue_nID, as.matrix(IDright)); - queue_sizeni = rbind(queue_sizeni, as.matrix(ncol(Iright))); - queue_nI = cbind(queue_nI, Iright); - queue_sizeX2y2 = rbind(queue_sizeX2y2, as.matrix(nrow(X2))); - queue_X2 = rbind(queue_X2, X2); - queue_y2 = rbind(queue_y2, y2); + queue_nID = append(queue_nID, as.matrix(IDright)); + queue_nI = append(queue_nI, Iright); + queue_X2 = append(queue_X2, X2); + queue_y2 = append(queue_y2, y2); } else { # TODO as.bool() should not be necessary, should be casted automatically (see #661). diff --git a/src/compiler/lowering/LowerToLLVMPass.cpp b/src/compiler/lowering/LowerToLLVMPass.cpp index 94fef5d36..29c393980 100644 --- a/src/compiler/lowering/LowerToLLVMPass.cpp +++ b/src/compiler/lowering/LowerToLLVMPass.cpp @@ -944,6 +944,11 @@ void DaphneLowerToLLVMPass::runOnOperation() return LLVM::LLVMPointerType::get( IntegerType::get(t.getContext(), 1)); }); + typeConverter.addConversion([&](daphne::ListType t) + { + return LLVM::LLVMPointerType::get( + IntegerType::get(t.getContext(), 1)); + }); typeConverter.addConversion([&](daphne::StringType t) { return LLVM::LLVMPointerType::get( diff --git a/src/compiler/lowering/ManageObjRefsPass.cpp b/src/compiler/lowering/ManageObjRefsPass.cpp index 96ed976d9..f0e89d6d7 100644 --- a/src/compiler/lowering/ManageObjRefsPass.cpp +++ b/src/compiler/lowering/ManageObjRefsPass.cpp @@ -108,9 +108,9 @@ void processValue(OpBuilder builder, Value v) { builder.setInsertionPointAfter(defOp); builder.create(v.getLoc(), v); } - - if(!llvm::isa(v.getType())) + if (!llvm::isa(v.getType())) return; Operation* decRefAfterOp = nullptr; @@ -176,7 +176,7 @@ void processValue(OpBuilder builder, Value v) { /** * @brief Inserts an `IncRefOp` for the given value if its type is a DAPHNE - * data type (matrix, frame, string). + * data type (matrix, frame, list, string). * * If the type is unknown, throw an exception. * @@ -185,7 +185,7 @@ void processValue(OpBuilder builder, Value v) { */ void incRefIfObj(Value v, OpBuilder & b) { Type t = v.getType(); - if(llvm::isa(t)) + if(llvm::isa(t)) b.create(v.getLoc(), v); else if(llvm::isa(t)) throw ErrorHandler::compilerError( @@ -196,7 +196,8 @@ void incRefIfObj(Value v, OpBuilder & b) { /** * @brief Inserts an `IncRefOp` for each operand of the given operation whose - * type is a DAPHNE data type (matrix, frame, string), right before the operation. + * type is a DAPHNE data type (matrix, frame, list, string), right before the + * operation. * * @param op * @param b diff --git a/src/compiler/lowering/RewriteToCallKernelOpPass.cpp b/src/compiler/lowering/RewriteToCallKernelOpPass.cpp index b29428622..8f41d8f06 100644 --- a/src/compiler/lowering/RewriteToCallKernelOpPass.cpp +++ b/src/compiler/lowering/RewriteToCallKernelOpPass.cpp @@ -60,7 +60,7 @@ namespace return 3; if(llvm::isa(op)) return 2; - if(llvm::isa(op)) + if(llvm::isa(op)) return 1; throw ErrorHandler::compilerError( @@ -85,6 +85,15 @@ namespace isVariadic[index] ); } + if(auto concreteOp = llvm::dyn_cast(op)) { + auto idxAndLen = concreteOp.getODSOperandIndexAndLength(index); + static bool isVariadic[] = {true}; + return std::make_tuple( + idxAndLen.first, + idxAndLen.second, + isVariadic[index] + ); + } if(auto concreteOp = llvm::dyn_cast(op)) { auto idxAndLen = concreteOp.getODSOperandIndexAndLength(index); static bool isVariadic[] = {false, true}; @@ -148,12 +157,14 @@ namespace mlir::Type adaptType(mlir::Type t, bool generalizeToStructure) const { MLIRContext * mctx = t.getContext(); - if(generalizeToStructure && t.isa()) + if(generalizeToStructure && t.isa()) return mlir::daphne::StructureType::get(mctx); if(auto mt = t.dyn_cast()) return mt.withSameElementTypeAndRepr(); if(t.isa()) return mlir::daphne::FrameType::get(mctx, {mlir::daphne::UnknownType::get(mctx)}); + if(auto lt = t.dyn_cast()) + return mlir::daphne::ListType::get(mctx, adaptType(lt.getElementType(), generalizeToStructure)); if(auto mrt = t.dyn_cast()) // Remove any dimension information ({0, 0}), but retain the element type. return mlir::MemRefType::get({0, 0}, mrt.getElementType()); diff --git a/src/compiler/utils/CompilerUtils.h b/src/compiler/utils/CompilerUtils.h index 48c823bf3..fe418419e 100644 --- a/src/compiler/utils/CompilerUtils.h +++ b/src/compiler/utils/CompilerUtils.h @@ -176,6 +176,14 @@ struct CompilerUtils { return "Structure"; else return "Frame"; + else if(auto lstTy = t.dyn_cast()) { + if(generalizeToStructure) + return "Structure"; + else { + const std::string dtName = mlirTypeToCppTypeName(lstTy.getElementType(), angleBrackets, false); + return angleBrackets ? ("List<" + dtName + ">") : ("List_" + dtName); + } + } else if(llvm::isa(t)) // This becomes "const char *" (which makes perfect sense for // strings) when inserted into the typical "const DT *" template of diff --git a/src/ir/daphneir/DaphneDialect.cpp b/src/ir/daphneir/DaphneDialect.cpp index 1a7e9f70d..430862340 100644 --- a/src/ir/daphneir/DaphneDialect.cpp +++ b/src/ir/daphneir/DaphneDialect.cpp @@ -299,6 +299,9 @@ void mlir::daphne::DaphneDialect::printType(mlir::Type type, os << '?'; os << '>'; } + else if (auto t = type.dyn_cast()) { + os << "List<" << t.getElementType() << '>'; + } else if (auto handle = type.dyn_cast()) { os << "Handle<" << handle.getDataType() << ">"; } diff --git a/src/ir/daphneir/DaphneInferTypesOpInterface.cpp b/src/ir/daphneir/DaphneInferTypesOpInterface.cpp index 7de779209..5e63fd34c 100644 --- a/src/ir/daphneir/DaphneInferTypesOpInterface.cpp +++ b/src/ir/daphneir/DaphneInferTypesOpInterface.cpp @@ -604,6 +604,64 @@ std::vector daphne::MaxPoolForwardOp::inferTypes() { return {restype2, builder.getIndexType(), builder.getIndexType()}; } +std::vector daphne::CreateListOp::inferTypes() { + ValueRange elems = getElems(); + const size_t numElems = elems.size(); + + if(numElems == 0) + throw ErrorHandler::compilerError( + getLoc(), + "InferTypesOpInterface", + "type inference for CreateListOp requires at least one argument" + ); + + // All elements must be matrices of the same value type. + // If the type of some element is (still) unknown or if the data type + // of some element is matrix, but the value type is (still) unknown, + // then we ignore this element for now. + Type etRes = nullptr; + for(size_t i = 0; i < numElems; i++) { + Type etCur = elems[i].getType(); + if(etCur.isa()) + continue; + if(auto mtCur = etCur.dyn_cast()) { + Type vtCur = mtCur.getElementType(); + if(vtCur.isa()) + continue; + else if(!etRes) + etRes = mtCur.withSameElementType(); + else if(etRes != mtCur.withSameElementType()) + throw ErrorHandler::compilerError( + getLoc(), + "InferTypesOpInterface", + "all arguments to CreateListOp must be matrices of the same value type" + ); + } + else + throw ErrorHandler::compilerError( + getLoc(), + "InferTypesOpInterface", + "the arguments of CreateListOp must be matrices" + ); + } + + return {daphne::ListType::get(getContext(), etRes)}; +} + +std::vector daphne::RemoveOp::inferTypes() { + // The type of the first result is the same as that of the argument list. + // The type of the second result is the element type of the argument list. + Type argListTy = getArgList().getType(); + if(auto lt = argListTy.dyn_cast()) + return {lt, lt.getElementType()}; + else + throw ErrorHandler::compilerError( + getLoc(), + "InferTypesOpInterface", + "RemoveOp expects a list as its first argument" + ); +} + // **************************************************************************** // Type inference function // **************************************************************************** diff --git a/src/ir/daphneir/DaphneOps.td b/src/ir/daphneir/DaphneOps.td index 69a750f21..f9b312437 100644 --- a/src/ir/daphneir/DaphneOps.td +++ b/src/ir/daphneir/DaphneOps.td @@ -1352,7 +1352,7 @@ def Daphne_PrintOp : Daphne_Op<"print"> { // TODO We might change it to only accept scalars here and enforce toString // for matrices and frames. But currently, we need it like that for the // rest of the program. - let arguments = (ins AnyTypeOf<[AnyScalar, MatrixOrFrame, AnyMemRef, Unknown]>:$arg, BoolScalar:$newline, BoolScalar:$err); + let arguments = (ins AnyTypeOf<[AnyScalar, MatrixOrFrame, List, AnyMemRef, Unknown]>:$arg, BoolScalar:$newline, BoolScalar:$err); let results = (outs); // no results } @@ -1579,14 +1579,14 @@ def Daphne_StoreVariadicPackOp : Daphne_Op<"storeVariadicPack"> { def Daphne_IncRefOp : Daphne_Op<"incRef"> { let summary = "Increases the reference counter of the underlying runtime data object."; - let arguments = (ins MatrixOrFrameOrString:$arg); + let arguments = (ins AnyTypeOf<[MatrixOrFrameOrString, List]>:$arg); let results = (outs); // no results } def Daphne_DecRefOp : Daphne_Op<"decRef"> { let summary = "Decreases the reference counter of the underlying runtime data object and frees it if the reference counter becomes zero."; - let arguments = (ins MatrixOrFrameOrString:$arg); + let arguments = (ins AnyTypeOf<[MatrixOrFrameOrString, List]>:$arg); let results = (outs); // no results } @@ -1608,6 +1608,46 @@ def Daphne_StopProfilingOp : Daphne_Op<"stopProfiling"> { let results = (outs); // no results } +// **************************************************************************** +// List operations +// **************************************************************************** + +def Daphne_CreateListOp : Daphne_Op<"createList", [ + DeclareOpInterfaceMethods +]> { + let summary = "Creates a new list from the given elements"; + + let arguments = (ins Variadic:$elems); + let results = (outs ListOrU:$res); +} + +def Daphne_LengthOp : Daphne_Op<"length", [ + DataTypeSca, ValueTypeSize +]> { + let summary = "Returns the number of elements in the given list"; + + let arguments = (ins ListOrU:$arg); + let results = (outs Size:$res); +} + +def Daphne_AppendOp : Daphne_Op<"append", [ + TypeFromFirstArg +]> { + let summary = "Appends the given element to the end of the given list"; + + let arguments = (ins ListOrU:$argList, MatrixOrU:$elem); + let results = (outs ListOrU:$resList); +} + +def Daphne_RemoveOp : Daphne_Op<"remove", [ + DeclareOpInterfaceMethods +]> { + let summary = "Removes and returns the element at the specified index from the given list"; + + let arguments = (ins ListOrU:$argList, Size:$idx); + let results = (outs ListOrU:$resList, MatrixOrU:$elem); +} + // **************************************************************************** // Old operations // **************************************************************************** diff --git a/src/ir/daphneir/DaphneTypes.td b/src/ir/daphneir/DaphneTypes.td index d1509c602..c0d307fe7 100644 --- a/src/ir/daphneir/DaphneTypes.td +++ b/src/ir/daphneir/DaphneTypes.td @@ -226,6 +226,16 @@ def MatrixOrFrame : AnyTypeOf<[Matrix, Frame, Unknown]>; def MatrixOrU : AnyTypeOf<[Matrix, Unknown]>; def FrameOrU : AnyTypeOf<[Frame, Unknown]>; +def List : Daphne_Type<"List"> { + let summary = "list"; + + let parameters = (ins + "::mlir::Type":$elementType + ); +} + +def ListOrU : AnyTypeOf<[List, Unknown]>; + // **************************************************************************** // Distributed types // **************************************************************************** diff --git a/src/parser/catalog/KernelCatalogParser.cpp b/src/parser/catalog/KernelCatalogParser.cpp index dbdb36245..cbd0ec140 100644 --- a/src/parser/catalog/KernelCatalogParser.cpp +++ b/src/parser/catalog/KernelCatalogParser.cpp @@ -52,6 +52,7 @@ KernelCatalogParser::KernelCatalogParser(mlir::MLIRContext * mctx) { for(mlir::Type st : scalarTypes) { // Scalar type. typeMap.emplace(CompilerUtils::mlirTypeToCppTypeName(st), st); + // Matrix type for DenseMatrix. // TODO This should have withRepresentation(mlir::daphne::MatrixRepresentation::Dense). mlir::Type mtDense = mlir::daphne::MatrixType::get(mctx, st); @@ -59,6 +60,14 @@ KernelCatalogParser::KernelCatalogParser(mlir::MLIRContext * mctx) { // Matrix type for CSRMatrix. mlir::Type mtCSR = mlir::daphne::MatrixType::get(mctx, st).withRepresentation(mlir::daphne::MatrixRepresentation::Sparse); typeMap.emplace(CompilerUtils::mlirTypeToCppTypeName(mtCSR), mtCSR); + + // List type for list of DenseMatrix. + mlir::Type ltDense = mlir::daphne::ListType::get(mctx, mtDense); + typeMap.emplace(CompilerUtils::mlirTypeToCppTypeName(ltDense), ltDense); + // List type for list of CSRMatrix. + mlir::Type ltCSR = mlir::daphne::ListType::get(mctx, mtCSR); + typeMap.emplace(CompilerUtils::mlirTypeToCppTypeName(ltCSR), ltCSR); + // MemRef type. if(!st.isa()) { // DAPHNE's StringType is not supported as the element type of a MemRef. diff --git a/src/parser/daphnedsl/DaphneDSLBuiltins.cpp b/src/parser/daphnedsl/DaphneDSLBuiltins.cpp index 81af617dc..b7b02d61b 100644 --- a/src/parser/daphnedsl/DaphneDSLBuiltins.cpp +++ b/src/parser/daphnedsl/DaphneDSLBuiltins.cpp @@ -1249,9 +1249,46 @@ antlrcpp::Any DaphneDSLBuiltins::build(mlir::Location loc, const std::string & f return static_cast(builder.create( loc, source.getType(), source, attr.dyn_cast() )); + } + + // **************************************************************************** + // List operations + // **************************************************************************** + + if(func == "createList") { + checkNumArgsMin(loc, func, numArgs, 1); + return static_cast(builder.create( + loc, utils.unknownType, args + )); } + if(func == "length") { + checkNumArgsExact(loc, func, numArgs, 1); + return static_cast(builder.create( + loc, utils.sizeType, args[0] + )); + } + if(func == "append") { + checkNumArgsExact(loc, func, numArgs, 2); + + mlir::Value list = args[0]; + mlir::Value elem = args[1]; + + return static_cast(builder.create( + loc, utils.unknownType, list, elem + )); + } + if(func == "remove") { + checkNumArgsExact(loc, func, numArgs, 2); + + mlir::Value list = args[0]; + mlir::Value idx = utils.castSizeIf(args[1]); + + return builder.create( + loc, utils.unknownType, utils.unknownType, list, idx + ).getResults(); + } // ******************************************************************** diff --git a/src/runtime/local/datastructures/CSRMatrix.h b/src/runtime/local/datastructures/CSRMatrix.h index 9f6f310d2..710af6c23 100644 --- a/src/runtime/local/datastructures/CSRMatrix.h +++ b/src/runtime/local/datastructures/CSRMatrix.h @@ -153,6 +153,10 @@ class CSRMatrix : public Matrix { template using WithValueType = CSRMatrix; + + static std::string getName() { + return "CSRMatrix"; + } void shrinkNumRows(size_t numRows) { if (numRows > this->numRows) diff --git a/src/runtime/local/datastructures/DenseMatrix.h b/src/runtime/local/datastructures/DenseMatrix.h index 4ebdd4bdd..2edd05ba8 100644 --- a/src/runtime/local/datastructures/DenseMatrix.h +++ b/src/runtime/local/datastructures/DenseMatrix.h @@ -174,6 +174,10 @@ class DenseMatrix : public Matrix template using WithValueType = DenseMatrix; + + static std::string getName() { + return "DenseMatrix"; + } [[nodiscard]] bool isPartialBuffer() const { return bufferSize != this->getNumRows() * this->getRowSkip() * sizeof(ValueType); } diff --git a/src/runtime/local/datastructures/List.h b/src/runtime/local/datastructures/List.h new file mode 100644 index 000000000..aa070fa9e --- /dev/null +++ b/src/runtime/local/datastructures/List.h @@ -0,0 +1,157 @@ +/* + * Copyright 2024 The DAPHNE Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +#include + +#include + +/** + * @brief An ordered sequence of homogeneous-typed elements of any data/value type. + * + * Most importantly, a list can store data objects (matrices/frames). + */ +template +class List : public Structure { + /** + * @brief The elements of this list. + */ + std::vector elements; + + // Grant DataObjectFactory access to the private constructors and + // destructors. + template + friend DataType_ * DataObjectFactory::create(ArgTypes ...); + template + friend void DataObjectFactory::destroy(const DataType_ * obj); + + /** + * @brief Creates a new empty list. + */ + List() : Structure(0, 1) { + // nothing to do + }; + + /** + * @brief Creates a new list containing the same elements as the given list. + * + * @param other The other list. + */ + List(const List * other) : Structure(other->length(), 1) { + for(const DT * elem : other->elements) { + // We must increase the reference counter of each element we put into + // this list to prevent the element from being freed as long as this + // list exists. + elem->increaseRefCounter(); + this->elements.push_back(elem); + } + } + +public: + + virtual ~List() { + // Decrease reference counters of each element by 1. + // If the reference counter becomes 0, destroy the element. + for(const DataType * element : elements) + DataObjectFactory::destroy(element); + }; + + /** + * @brief The common type of all elements in this list. + */ + using DT = DataType; + + size_t getNumDims() const override { + return 1; + } + + size_t getNumItems() const override { + return this->numRows; + } + + void print(std::ostream & os) const override { + os << "List(" << elements.size() << ", " << DataType::getName() << ", " + << ValueTypeUtils::cppNameFor << ')' << std::endl; + for(size_t i = 0; i < elements.size(); i++) + elements[i]->print(os); + } + + Structure* sliceRow(size_t rl, size_t ru) const override { + throw std::runtime_error("sliceRow is not supported for List yet"); + } + + Structure* sliceCol(size_t cl, size_t cu) const override { + throw std::runtime_error("sliceCol is not supported for List yet"); + } + + Structure* slice(size_t rl, size_t ru, size_t cl, size_t cu) const override { + throw std::runtime_error("slice is not supported for List yet"); + } + + size_t serialize(std::vector &buf) const override { + throw std::runtime_error("serialize is not supported for List yet"); + } + + /** + * @brief Returns the number of elements in this list. + * + * @return The number of elements in this list. + */ + size_t length() const { + return elements.size(); + } + + /** + * @brief Appends the given element to the end of this list. + * + * @param element The element to append. + */ + void append(const DataType * element) { + // We must increase the reference counter of the new element to prevent it + // from being freed as long as this list exists. + element->increaseRefCounter(); + elements.push_back(element); + } + + /** + * @brief Removes the element at the given position from this list and returns it. + * + * @param idx The position of the element to remove. + * @return The removed element. + */ + const DataType * remove(size_t idx) { + if(idx >= elements.size()) + throw std::runtime_error( + "trying to remove element at position " + std::to_string(idx) + + " from a list with " + std::to_string(elements.size()) + " elements" + ); + const DataType * element = elements[idx]; + elements.erase(elements.begin() + idx); + // Note that we do not decrease the reference counter of the element. It must + // not be freed here, since we return it. + return element; + } +}; + +template +std::ostream & operator<<(std::ostream & os, const List & obj) { + obj.print(os); + return os; +} \ No newline at end of file diff --git a/src/runtime/local/kernels/Append.h b/src/runtime/local/kernels/Append.h new file mode 100644 index 000000000..6822ede3b --- /dev/null +++ b/src/runtime/local/kernels/Append.h @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The DAPHNE Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +// **************************************************************************** +// Convenience function +// **************************************************************************** + +template +void append(List
*& resList, const List
* argList, const DT * elem, DCTX(ctx)) { + resList = DataObjectFactory::create>(argList); + resList->append(elem); +} \ No newline at end of file diff --git a/src/runtime/local/kernels/CreateList.h b/src/runtime/local/kernels/CreateList.h new file mode 100644 index 000000000..c2bdbbdfd --- /dev/null +++ b/src/runtime/local/kernels/CreateList.h @@ -0,0 +1,32 @@ +/* + * Copyright 2024 The DAPHNE Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +// **************************************************************************** +// Convenience function +// **************************************************************************** + +template +void createList(List
*& res, const DT ** elems, size_t numElems, DCTX(ctx)) { + res = DataObjectFactory::create>(); + for(size_t i = 0; i < numElems; i++) + res->append(elems[i]); +} \ No newline at end of file diff --git a/src/runtime/local/kernels/Length.h b/src/runtime/local/kernels/Length.h new file mode 100644 index 000000000..f7e8092a2 --- /dev/null +++ b/src/runtime/local/kernels/Length.h @@ -0,0 +1,30 @@ +/* + * Copyright 2024 The DAPHNE Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include + +// **************************************************************************** +// Convenience function +// **************************************************************************** + +// TODO Don't specialize for the data/value type, use the List-level. +template +size_t length(const List
* arg, DCTX(ctx)) { + return arg->length(); +} diff --git a/src/runtime/local/kernels/Remove.h b/src/runtime/local/kernels/Remove.h new file mode 100644 index 000000000..000bef283 --- /dev/null +++ b/src/runtime/local/kernels/Remove.h @@ -0,0 +1,31 @@ +/* + * Copyright 2024 The DAPHNE Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include + +// **************************************************************************** +// Convenience function +// **************************************************************************** + +template +void remove(List
*& resList, DT *& elem, const List
* argList, size_t idx, DCTX(ctx)) { + resList = DataObjectFactory::create>(argList); + elem = const_cast
(resList->remove(idx)); +} \ No newline at end of file diff --git a/src/runtime/local/kernels/kernels.json b/src/runtime/local/kernels/kernels.json index 36733c392..ca772b17e 100644 --- a/src/runtime/local/kernels/kernels.json +++ b/src/runtime/local/kernels/kernels.json @@ -2483,6 +2483,24 @@ [["CSRMatrix", "int64_t"]], [["CSRMatrix", "uint8_t"]], ["Frame"], + [["List", "DenseMatrix", "double"]], + [["List", "DenseMatrix", "float"]], + [["List", "DenseMatrix", "int64_t"]], + [["List", "DenseMatrix", "int32_t"]], + [["List", "DenseMatrix", "int8_t"]], + [["List", "DenseMatrix", "uint64_t"]], + [["List", "DenseMatrix", "uint32_t"]], + [["List", "DenseMatrix", "uint8_t"]], + [["List", "DenseMatrix", "size_t"]], + [["List", "CSRMatrix", "double"]], + [["List", "CSRMatrix", "float"]], + [["List", "CSRMatrix", "int64_t"]], + [["List", "CSRMatrix", "int32_t"]], + [["List", "CSRMatrix", "int8_t"]], + [["List", "CSRMatrix", "uint64_t"]], + [["List", "CSRMatrix", "uint32_t"]], + [["List", "CSRMatrix", "uint8_t"]], + [["List", "CSRMatrix", "size_t"]], ["char"] ] }, @@ -4326,5 +4344,189 @@ "instantiations": [ [] ] + }, + { + "kernelTemplate": { + "header": "CreateList.h", + "opName": "createList", + "returnType": "void", + "templateParams": [ + { + "name": "DT", + "isDataType": true + } + ], + "runtimeParams": [ + { + "type": "List
*&", + "name": "res" + }, + { + "type": "const DT **", + "name": "elems" + }, + { + "type": "size_t", + "name": "numElems" + } + ] + }, + "instantiations": [ + [["DenseMatrix", "double"]], + [["DenseMatrix", "float"]], + [["DenseMatrix", "int64_t"]], + [["DenseMatrix", "int32_t"]], + [["DenseMatrix", "int8_t"]], + [["DenseMatrix", "uint64_t"]], + [["DenseMatrix", "uint32_t"]], + [["DenseMatrix", "uint8_t"]], + [["DenseMatrix", "size_t"]], + [["CSRMatrix", "double"]], + [["CSRMatrix", "float"]], + [["CSRMatrix", "int64_t"]], + [["CSRMatrix", "int32_t"]], + [["CSRMatrix", "int8_t"]], + [["CSRMatrix", "uint64_t"]], + [["CSRMatrix", "uint32_t"]], + [["CSRMatrix", "uint8_t"]], + [["CSRMatrix", "size_t"]] + ] + }, + { + "kernelTemplate": { + "header": "Length.h", + "opName": "length", + "returnType": "size_t", + "templateParams": [ + { + "name": "DT", + "isDataType": true + } + ], + "runtimeParams": [ + { + "type": "const List
*", + "name": "arg" + } + ] + }, + "instantiations": [ + [["DenseMatrix", "double"]], + [["DenseMatrix", "float"]], + [["DenseMatrix", "int64_t"]], + [["DenseMatrix", "int32_t"]], + [["DenseMatrix", "int8_t"]], + [["DenseMatrix", "uint64_t"]], + [["DenseMatrix", "uint32_t"]], + [["DenseMatrix", "uint8_t"]], + [["DenseMatrix", "size_t"]], + [["CSRMatrix", "double"]], + [["CSRMatrix", "float"]], + [["CSRMatrix", "int64_t"]], + [["CSRMatrix", "int32_t"]], + [["CSRMatrix", "int8_t"]], + [["CSRMatrix", "uint64_t"]], + [["CSRMatrix", "uint32_t"]], + [["CSRMatrix", "uint8_t"]], + [["CSRMatrix", "size_t"]] + ] + }, + { + "kernelTemplate": { + "header": "Append.h", + "opName": "append", + "returnType": "void", + "templateParams": [ + { + "name": "DT", + "isDataType": true + } + ], + "runtimeParams": [ + { + "type": "List
*&", + "name": "resList" + }, + { + "type": "const List
*", + "name": "argList" + }, + { + "type": "const DT *", + "name": "elem" + } + ] + }, + "instantiations": [ + [["DenseMatrix", "double"]], + [["DenseMatrix", "float"]], + [["DenseMatrix", "int64_t"]], + [["DenseMatrix", "int32_t"]], + [["DenseMatrix", "int8_t"]], + [["DenseMatrix", "uint64_t"]], + [["DenseMatrix", "uint32_t"]], + [["DenseMatrix", "uint8_t"]], + [["DenseMatrix", "size_t"]], + [["CSRMatrix", "double"]], + [["CSRMatrix", "float"]], + [["CSRMatrix", "int64_t"]], + [["CSRMatrix", "int32_t"]], + [["CSRMatrix", "int8_t"]], + [["CSRMatrix", "uint64_t"]], + [["CSRMatrix", "uint32_t"]], + [["CSRMatrix", "uint8_t"]], + [["CSRMatrix", "size_t"]] + ] + }, + { + "kernelTemplate": { + "header": "Remove.h", + "opName": "remove", + "returnType": "void", + "templateParams": [ + { + "name": "DT", + "isDataType": true + } + ], + "runtimeParams": [ + { + "type": "List
*&", + "name": "resList" + }, + { + "type": "DT *&", + "name": "elem" + }, + { + "type": "const List
*", + "name": "argList" + }, + { + "type": "size_t", + "name": "idx" + } + ] + }, + "instantiations": [ + [["DenseMatrix", "double"]], + [["DenseMatrix", "float"]], + [["DenseMatrix", "int64_t"]], + [["DenseMatrix", "int32_t"]], + [["DenseMatrix", "int8_t"]], + [["DenseMatrix", "uint64_t"]], + [["DenseMatrix", "uint32_t"]], + [["DenseMatrix", "uint8_t"]], + [["DenseMatrix", "size_t"]], + [["CSRMatrix", "double"]], + [["CSRMatrix", "float"]], + [["CSRMatrix", "int64_t"]], + [["CSRMatrix", "int32_t"]], + [["CSRMatrix", "int8_t"]], + [["CSRMatrix", "uint64_t"]], + [["CSRMatrix", "uint32_t"]], + [["CSRMatrix", "uint8_t"]], + [["CSRMatrix", "size_t"]] + ] } ] diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d434396d8..0788a2a59 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -38,6 +38,7 @@ set(TEST_SOURCES api/cli/indexing/IndexingTest.cpp api/cli/inference/InferenceTest.cpp api/cli/io/ReadTest.cpp + api/cli/lists/ListsTest.cpp api/cli/literals/LiteralsTest.cpp api/cli/operations/ConstantFoldingTest.cpp api/cli/operations/OperationsTest.cpp diff --git a/test/api/cli/lists/ListsTest.cpp b/test/api/cli/lists/ListsTest.cpp new file mode 100644 index 000000000..445c98e61 --- /dev/null +++ b/test/api/cli/lists/ListsTest.cpp @@ -0,0 +1,47 @@ +/* + * Copyright 2024 The DAPHNE Consortium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +#include + +#include +#include + +const std::string dirPath = "test/api/cli/lists/"; + +#define MAKE_SUCCESS_TEST_CASE(name, count) \ + TEST_CASE(name ", success", TAG_LISTS) { \ + for(unsigned i = 1; i <= count; i++) { \ + DYNAMIC_SECTION(name "_success_" << i << ".daphne") { \ + compareDaphneToRefSimple(dirPath, name "_success", i); \ + } \ + } \ + } + +#define MAKE_FAILURE_TEST_CASE(name, count) \ + TEST_CASE(name ", failure", TAG_LISTS) { \ + for(unsigned i = 1; i <= count; i++) { \ + DYNAMIC_SECTION(name "_failure_" << i << ".daphne") { \ + checkDaphneFailsSimple(dirPath, name "_failure", i); \ + } \ + } \ + } + +MAKE_SUCCESS_TEST_CASE("lists", 4) +MAKE_FAILURE_TEST_CASE("lists", 3) \ No newline at end of file diff --git a/test/api/cli/lists/lists_failure_1.daphne b/test/api/cli/lists/lists_failure_1.daphne new file mode 100644 index 000000000..06cdbcfc5 --- /dev/null +++ b/test/api/cli/lists/lists_failure_1.daphne @@ -0,0 +1,4 @@ +// Remove an element from a list, out-of-bounds (too high) position. + +l = createList([1]); +l, e = remove(l, 1); \ No newline at end of file diff --git a/test/api/cli/lists/lists_failure_2.daphne b/test/api/cli/lists/lists_failure_2.daphne new file mode 100644 index 000000000..930c8b1cf --- /dev/null +++ b/test/api/cli/lists/lists_failure_2.daphne @@ -0,0 +1,5 @@ +// Remove an element from an empty list. + +l = createList([1]); +l, e = remove(l, 0); +l, e = remove(l, 0); // remove from empty list \ No newline at end of file diff --git a/test/api/cli/lists/lists_failure_3.daphne b/test/api/cli/lists/lists_failure_3.daphne new file mode 100644 index 000000000..f6ec2f63a --- /dev/null +++ b/test/api/cli/lists/lists_failure_3.daphne @@ -0,0 +1,4 @@ +// Remove an element from a list, out-of-bounds (too low) position. + +l = createList([1]); +l, e = remove(l, -1); \ No newline at end of file diff --git a/test/api/cli/lists/lists_success_1.daphne b/test/api/cli/lists/lists_success_1.daphne new file mode 100644 index 000000000..460a5a3e1 --- /dev/null +++ b/test/api/cli/lists/lists_success_1.daphne @@ -0,0 +1,4 @@ +// Create a list from one element. + +l = createList([1]); +print(l); \ No newline at end of file diff --git a/test/api/cli/lists/lists_success_1.txt b/test/api/cli/lists/lists_success_1.txt new file mode 100644 index 000000000..e7b3185ca --- /dev/null +++ b/test/api/cli/lists/lists_success_1.txt @@ -0,0 +1,3 @@ +List(1, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 diff --git a/test/api/cli/lists/lists_success_2.daphne b/test/api/cli/lists/lists_success_2.daphne new file mode 100644 index 000000000..aae419475 --- /dev/null +++ b/test/api/cli/lists/lists_success_2.daphne @@ -0,0 +1,4 @@ +// Create a list from multiple elements of heterogeneous shapes. + +l = createList([1.0], [3.0, 2.0], [-2.0, -5.0](1, 2)); +print(l); \ No newline at end of file diff --git a/test/api/cli/lists/lists_success_2.txt b/test/api/cli/lists/lists_success_2.txt new file mode 100644 index 000000000..30368280a --- /dev/null +++ b/test/api/cli/lists/lists_success_2.txt @@ -0,0 +1,8 @@ +List(3, DenseMatrix, double) +DenseMatrix(1x1, double) +1 +DenseMatrix(2x1, double) +3 +2 +DenseMatrix(1x2, double) +-2 -5 diff --git a/test/api/cli/lists/lists_success_3.daphne b/test/api/cli/lists/lists_success_3.daphne new file mode 100644 index 000000000..1a9beea5e --- /dev/null +++ b/test/api/cli/lists/lists_success_3.daphne @@ -0,0 +1,20 @@ +// Create a list, append, and remove some elements. + +l = createList([1], [2, 22]); +print(length(l)); +print(l); +print(""); + +l = append(l, [3, 33, 333]); +print(length(l)); +print(l); +print(""); + +l = append(l, [4, 44, 444, 4444]); +print(length(l)); +print(l); +print(""); + +l, e = remove(l, 1); +print(length(l)); +print(l); \ No newline at end of file diff --git a/test/api/cli/lists/lists_success_3.txt b/test/api/cli/lists/lists_success_3.txt new file mode 100644 index 000000000..d07875bf3 --- /dev/null +++ b/test/api/cli/lists/lists_success_3.txt @@ -0,0 +1,50 @@ +2 +List(2, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 +DenseMatrix(2x1, int64_t) +2 +22 + +3 +List(3, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 +DenseMatrix(2x1, int64_t) +2 +22 +DenseMatrix(3x1, int64_t) +3 +33 +333 + +4 +List(4, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 +DenseMatrix(2x1, int64_t) +2 +22 +DenseMatrix(3x1, int64_t) +3 +33 +333 +DenseMatrix(4x1, int64_t) +4 +44 +444 +4444 + +3 +List(3, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 +DenseMatrix(3x1, int64_t) +3 +33 +333 +DenseMatrix(4x1, int64_t) +4 +44 +444 +4444 diff --git a/test/api/cli/lists/lists_success_4.daphne b/test/api/cli/lists/lists_success_4.daphne new file mode 100644 index 000000000..e7a346b91 --- /dev/null +++ b/test/api/cli/lists/lists_success_4.daphne @@ -0,0 +1,9 @@ +// Arguments to append/remove stay unchanged. + +l1 = createList([1]); +l2 = append(l1, [2]); +l3, e = remove(l2, 0); + +print(l1); +print(l2); +print(l3); \ No newline at end of file diff --git a/test/api/cli/lists/lists_success_4.txt b/test/api/cli/lists/lists_success_4.txt new file mode 100644 index 000000000..cfa97aa5c --- /dev/null +++ b/test/api/cli/lists/lists_success_4.txt @@ -0,0 +1,11 @@ +List(1, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 +List(2, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +1 +DenseMatrix(1x1, int64_t) +2 +List(1, DenseMatrix, int64_t) +DenseMatrix(1x1, int64_t) +2 diff --git a/test/tags.h b/test/tags.h index 7dbd65435..6f13b8d60 100644 --- a/test/tags.h +++ b/test/tags.h @@ -41,6 +41,7 @@ #define TAG_IO "[io]" #define TAG_KERNELS "[kernels]" #define TAG_DNN "[dnn]" +#define TAG_LISTS "[lists]" #define TAG_LITERALS "[literals]" #define TAG_OPERATIONS "[operations]" #define TAG_PARSER "[parser]"