diff --git a/tslang/include/TypeScript/TypeScriptOps.td b/tslang/include/TypeScript/TypeScriptOps.td index 1133f6c04..8400332d6 100644 --- a/tslang/include/TypeScript/TypeScriptOps.td +++ b/tslang/include/TypeScript/TypeScriptOps.td @@ -2302,13 +2302,13 @@ def TypeScript_BodyResultInternalOp : TypeScript_YieldOpBase<"BodyResultInternal let summary = "termination operation"; } -def TypeScript_ArrayPushOp : TypeScript_Op<"ArrayPush", [ - RangedTypesMatchWith<"array element type match all items types", - "op", "items", - "TypeRange((cast(cast($_self).getElementType()).getElementType()))"> -]> { +def TypeScript_ArrayPushOp : TypeScript_Op<"ArrayPush"> { let arguments = (ins Arg:$op, Variadic:$items); let results = (outs Index:$new_size); + + // every item is of the element type (a RangedTypesMatchWith against a one-type range + // accepted only a single item) + let hasVerifier = 1; } def TypeScript_ArrayPopOp : TypeScript_Op<"ArrayPop", [ diff --git a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp index e6c9f9ac1..61126340a 100644 --- a/tslang/lib/TypeScript/MLIRGenAccessCall.cpp +++ b/tslang/lib/TypeScript/MLIRGenAccessCall.cpp @@ -1365,10 +1365,200 @@ namespace mlirgen functionName, typeArgs, operands, - [this](mlir::Location location, mlir::Type type, mlir::Value value, const GenContext &genContext, bool disableStrictNullCheck) { return cast(location, type, value, genContext, disableStrictNullCheck); }, + [this](mlir::Location location, mlir::Type type, mlir::Value value, const GenContext &genContext, bool disableStrictNullCheck) { return cast(location, type, value, genContext, disableStrictNullCheck); }, genContext); } + // `a.push(...xs)`, `a.unshift(...xs)`, `a.splice(start, count, ...xs)`. The builtins take one + // operand per item, and a spread, whose length is known only at run time, has none to give: + // it reached ArrayPush as the array itself and failed to verify. Every item argument, spread + // or not, goes into one array literal instead (a literal spreads at run time), and its elements + // go in one at a time through the same builtin with no spread, so the casts and the retains + // stay the builtin's. + std::optional MLIRGenImpl::mlirGenArrayInsertWithSpread( + mlir::Location location, mlir::Value funcResult, NodeArray arguments, const GenContext &genContext) + { + auto thisSymbolRefOp = funcResult.getDefiningOp(); + if (!thisSymbolRefOp || !mth.isBuiltinFunctionType(funcResult) + || llvm::none_of(arguments, [](auto argument) { return argument == SyntaxKind::SpreadElement; })) + { + return std::nullopt; + } + + auto calleeName = thisSymbolRefOp->getAttrOfType(StringRef(IDENTIFIER_ATTR_NAME)); + auto functionName = calleeName ? calleeName.getValue() : StringRef(); + auto isPush = functionName == "__array_push"; + auto isSplice = functionName == "__array_splice"; + if (!isPush && !isSplice && functionName != "__array_unshift") + { + return std::nullopt; + } + + auto thisValue = thisSymbolRefOp.getThisVal(); + auto arrayType = dyn_cast(thisValue.getType()); + if (!arrayType) + { + return std::nullopt; + } + + auto generate = [&]() -> ValueOrLogicalResult { + MLIRCodeLogic mcl(builder, compileOptions); + auto arrayRef = mcl.GetReferenceFromValue(location, thisValue); + if (!arrayRef) + { + emitError(location) << "Can't get reference of the array, ensure const array is not used"; + return mlir::failure(); + } + + // splice's start and delete count are positions, not items, and come first + auto itemsFrom = isSplice ? 2 : 0; + SmallVector positions; + for (auto index = 0; index < itemsFrom; index++) + { + if (index >= (int)arguments.size() || arguments[index] == SyntaxKind::SpreadElement) + { + emitError(location) << "splice with spread items needs its start and delete count as arguments of their own"; + return mlir::failure(); + } + + auto result = mlirGen(arguments[index], genContext); + EXIT_IF_FAILED_OR_NO_VALUE(result) + auto value = V(result); + CAST(value, location, getNumberType(), value, genContext); + positions.push_back(value); + } + + NodeFactory nf(NodeFactoryFlags::None); + + NodeArray itemArguments; + for (auto index = itemsFrom; index < (int)arguments.size(); index++) + { + itemArguments.push_back(arguments[index]); + } + + GenContext itemsGenContext(genContext); + itemsGenContext.clearReceiverTypes(); + itemsGenContext.receiverType = arrayType; + auto itemsResult = mlirGen(nf.createArrayLiteralExpression(itemArguments), itemsGenContext); + EXIT_IF_FAILED_OR_NO_VALUE(itemsResult) + auto items = V(itemsResult); + if (items.getType() != arrayType) + { + CAST(items, location, arrayType, items, genContext); + } + + if (isPush) + { + if (mlir::failed(mlirGenAppendArrayByEachElement(location, arrayRef, items, genContext))) + { + return mlir::failure(); + } + } + else + { + SymbolTableScopeT varScope(symbolTable); + + auto dstArrayVarDecl = std::make_shared(".dst_array", arrayType, location); + dstArrayVarDecl->setReadWriteAccess(true); + DECLARE(dstArrayVarDecl, arrayRef); + + auto itemsVarDecl = std::make_shared(".items", arrayType, location); + DECLARE(itemsVarDecl, items); + + auto _dst_array = nf.createIdentifier(S(".dst_array")); + auto _items = nf.createIdentifier(S(".items")); + auto _at = nf.createIdentifier(S(".at")); + auto _i = nf.createIdentifier(S(".i")); + auto _length = [&](Identifier array) { + return nf.createPropertyAccessExpression(array, nf.createIdentifier(S(LENGTH_FIELD_NAME))); + }; + auto _splice = [&](Expression start, Expression count, Expression item) { + NodeArray spliceArguments; + spliceArguments.push_back(start); + spliceArguments.push_back(count); + if (item) + { + spliceArguments.push_back(item); + } + + return nf.createExpressionStatement(nf.createCallExpression( + nf.createPropertyAccessExpression(_dst_array, nf.createIdentifier(S("splice"))), undefined, spliceArguments)); + }; + auto _assign = [&](Expression value) { + return nf.createExpressionStatement(nf.createBinaryExpression(_at, nf.createToken(SyntaxKind::EqualsToken), value)); + }; + auto _zero = [&]() { return nf.createNumericLiteral(S("0")); }; + + // let .at = 0 (unshift), or splice's start where JavaScript puts it: a negative one + // counts from the end and stops at 0, one past the end is the end + NodeArray atDeclarations; + if (isSplice) + { + auto startVarDecl = std::make_shared(".start", getNumberType(), location); + DECLARE(startVarDecl, positions[0]); + auto countVarDecl = std::make_shared(".count", getNumberType(), location); + DECLARE(countVarDecl, positions[1]); + + auto _start = nf.createIdentifier(S(".start")); + atDeclarations.push_back(nf.createVariableDeclaration(_at, undefined, undefined, _start)); + if (mlir::failed(mlirGen(nf.createVariableStatement(undefined, nf.createVariableDeclarationList(atDeclarations, NodeFlags::Let)), genContext))) + { + return mlir::failure(); + } + + // if (.at < 0) { .at = .at + .dst_array.length; if (.at < 0) .at = 0; } + // else if (.at > .dst_array.length) .at = .dst_array.length; + NodeArray fromEnd; + fromEnd.push_back(_assign(nf.createBinaryExpression(_at, nf.createToken(SyntaxKind::PlusToken), _length(_dst_array)))); + fromEnd.push_back(nf.createIfStatement( + nf.createBinaryExpression(_at, nf.createToken(SyntaxKind::LessThanToken), _zero()), _assign(_zero()))); + auto normalize = nf.createIfStatement( + nf.createBinaryExpression(_at, nf.createToken(SyntaxKind::LessThanToken), _zero()), + nf.createBlock(fromEnd), + nf.createIfStatement( + nf.createBinaryExpression(_at, nf.createToken(SyntaxKind::GreaterThanToken), _length(_dst_array)), + _assign(_length(_dst_array)))); + if (mlir::failed(mlirGen(normalize, genContext))) + { + return mlir::failure(); + } + + // the delete runs first; everything before .at stays where it is + if (mlir::failed(mlirGen(_splice(_start, nf.createIdentifier(S(".count")), undefined), genContext))) + { + return mlir::failure(); + } + } + else + { + atDeclarations.push_back(nf.createVariableDeclaration(_at, undefined, undefined, _zero())); + if (mlir::failed(mlirGen(nf.createVariableStatement(undefined, nf.createVariableDeclarationList(atDeclarations, NodeFlags::Let)), genContext))) + { + return mlir::failure(); + } + } + + // for (let .i = 0; .i < .items.length; ++.i) .dst_array.splice(.at + .i, 0, .items[.i]); + NodeArray declarations; + declarations.push_back(nf.createVariableDeclaration(_i, undefined, undefined, _zero())); + auto initVars = nf.createVariableDeclarationList(declarations, NodeFlags::Let); + auto cond = nf.createBinaryExpression(_i, nf.createToken(SyntaxKind::LessThanToken), _length(_items)); + auto incr = nf.createPrefixUnaryExpression(nf.createToken(SyntaxKind::PlusPlusToken), _i); + auto insert = _splice( + nf.createBinaryExpression(_at, nf.createToken(SyntaxKind::PlusToken), _i), _zero(), nf.createElementAccessExpression(_items, _i)); + if (mlir::failed(mlirGen(nf.createForStatement(initVars, cond, incr, insert), genContext))) + { + return mlir::failure(); + } + } + + auto loadedArray = builder.create(location, arrayType, arrayRef); + return V(builder.create(location, builder.getIndexType(), loadedArray)); + }; + + return generate(); + } + ValueOrLogicalResult MLIRGenImpl::mlirGenCallExpression(mlir::Location location, mlir::Value funcResult, NodeArray typeArguments, SmallVector &operands, const GenContext &genContext) diff --git a/tslang/lib/TypeScript/MLIRGenExpressions.cpp b/tslang/lib/TypeScript/MLIRGenExpressions.cpp index f48afc288..052b6b73b 100644 --- a/tslang/lib/TypeScript/MLIRGenExpressions.cpp +++ b/tslang/lib/TypeScript/MLIRGenExpressions.cpp @@ -1032,6 +1032,11 @@ namespace mlirgen mth.isGenericType(funcResult.getType()) && callExpression->typeArguments.size() == 0; + if (auto spreadResult = mlirGenArrayInsertWithSpread(location, funcResult, callExpression->arguments, genContext)) + { + return *spreadResult; + } + SmallVector operands; auto offsetArgs = isa(funcType) || isa(funcType) ? 1 : 0; if (mlir::failed(mlirGenOperands(callExpression->arguments, operands, funcResult.getType(), genContext, offsetArgs, noReceiverTypesForGenericCall))) diff --git a/tslang/lib/TypeScript/MLIRGenImpl.h b/tslang/lib/TypeScript/MLIRGenImpl.h index 034bc0acc..1c5ea278b 100644 --- a/tslang/lib/TypeScript/MLIRGenImpl.h +++ b/tslang/lib/TypeScript/MLIRGenImpl.h @@ -7337,9 +7337,12 @@ class MLIRGenImpl const GenContext &genContext); ValueOrLogicalResult mlirGenCallBuiltInFunction( - mlir::Location location, mlir::Value actualFuncRefValue, NodeArray typeArguments, + mlir::Location location, mlir::Value actualFuncRefValue, NodeArray typeArguments, SmallVector &operands, const GenContext &genContext); + std::optional mlirGenArrayInsertWithSpread( + mlir::Location location, mlir::Value funcResult, NodeArray arguments, const GenContext &genContext); + ValueOrLogicalResult mlirGenCallExpression(mlir::Location location, mlir::Value funcResult, NodeArray typeArguments, SmallVector &operands, const GenContext &genContext); diff --git a/tslang/lib/TypeScript/TypeScriptOps.cpp b/tslang/lib/TypeScript/TypeScriptOps.cpp index 82db55cb6..d19957ca5 100644 --- a/tslang/lib/TypeScript/TypeScriptOps.cpp +++ b/tslang/lib/TypeScript/TypeScriptOps.cpp @@ -1062,6 +1062,24 @@ LogicalResult mlir_ts::FuncOp::verify() return success(); } +//===----------------------------------------------------------------------===// +// ArrayPushOp +//===----------------------------------------------------------------------===// + +LogicalResult mlir_ts::ArrayPushOp::verify() +{ + auto elementType = mlir::cast(mlir::cast(getOp().getType()).getElementType()).getElementType(); + for (auto item : getItems()) + { + if (item.getType() != elementType) + { + return emitOpError("item of type ") << item.getType() << " does not match array element type " << elementType; + } + } + + return success(); +} + //===----------------------------------------------------------------------===// // InvokeOp //===----------------------------------------------------------------------===// diff --git a/tslang/test/tester/CMakeLists.txt b/tslang/test/tester/CMakeLists.txt index dabe5728a..a76d3db15 100644 --- a/tslang/test/tester/CMakeLists.txt +++ b/tslang/test/tester/CMakeLists.txt @@ -335,6 +335,7 @@ tslang_add_test(NAME test-compile-00-arrays4-push-pop COMMAND test-runner "${PRO tslang_add_test(NAME test-compile-00-array-shift COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_shift.ts") tslang_add_test(NAME test-compile-00-array-splice COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_splice.ts") tslang_add_test(NAME test-compile-00-const-array-push COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_push.ts") +tslang_add_test(NAME test-compile-00-array-spread-args COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_spread_args.ts") tslang_add_test(NAME test-compile-00-arrays5-deconstruct COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array5_deconst.ts") tslang_add_test(NAME test-compile-00-arrays6 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array6.ts") tslang_add_test(NAME test-compile-00-arrays7 COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array7.ts") @@ -787,6 +788,7 @@ tslang_add_test(NAME test-jit-00-arrays4-push-pop COMMAND test-runner -jit "${PR tslang_add_test(NAME test-jit-00-array-shift COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_shift.ts") tslang_add_test(NAME test-jit-00-array-splice COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_splice.ts") tslang_add_test(NAME test-jit-00-const-array-push COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_array_push.ts") +tslang_add_test(NAME test-jit-00-array-spread-args COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_spread_args.ts") tslang_add_test(NAME test-jit-00-arrays5-deconstruct COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array5_deconst.ts") tslang_add_test(NAME test-jit-00-arrays6 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array6.ts") tslang_add_test(NAME test-jit-00-arrays7 COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array7.ts") @@ -1551,6 +1553,7 @@ set(TSLANG_CORPUS 00array_of.ts 00array_shift.ts 00array_splice.ts + 00array_spread_args.ts 00array_view.ts 00const_array_push.ts 00array.ts diff --git a/tslang/test/tester/tests/00array_spread_args.ts b/tslang/test/tester/tests/00array_spread_args.ts new file mode 100644 index 000000000..48aa4e12c --- /dev/null +++ b/tslang/test/tester/tests/00array_spread_args.ts @@ -0,0 +1,63 @@ +// push/unshift/splice with spread arguments, whose count is known only at run time, and push with +// more than one item (the op's verifier accepted exactly one). +function same(a: number[], expected: number[]) { + if (a.length != expected.length) return false; + for (let i = 0; i < a.length; i++) { + if (a[i] != expected[i]) return false; + } + + return true; +} + +function spreadOf(values: number[]) { + let a: number[] = [1, 2]; + a.push(...values); + return a; +} + +function main() { + let m: number[] = []; + assert(m.push(1, 2) == 2, "push two: result"); + assert(same(m, [1, 2]), "push two"); + + const extra = [3, 4, 5]; + + let a: number[] = [1, 2]; + assert(a.push(...extra) == 5, "push spread: result"); + assert(same(a, [1, 2, 3, 4, 5]), "push spread"); + + a.push(0, ...extra, 9); + assert(same(a, [1, 2, 3, 4, 5, 0, 3, 4, 5, 9]), "push mixed"); + + assert(same(spreadOf([]), [1, 2]), "push empty spread"); + assert(same(spreadOf([7, 8]), [1, 2, 7, 8]), "push parameter spread"); + + let b: number[] = [9]; + b.unshift(...extra); + assert(same(b, [3, 4, 5, 9]), "unshift spread"); + b.unshift(7, ...[8]); + assert(same(b, [7, 8, 3, 4, 5, 9]), "unshift mixed"); + + let c: number[] = [1, 2, 3]; + c.splice(1, 1, ...extra); + assert(same(c, [1, 3, 4, 5, 3]), "splice spread"); + c.splice(-1, 1, ...[10, 11]); + assert(same(c, [1, 3, 4, 5, 10, 11]), "splice negative start"); + c.splice(100, 0, ...[12]); + assert(same(c, [1, 3, 4, 5, 10, 11, 12]), "splice start past the end"); + c.splice(-100, 2, ...[0]); + assert(same(c, [0, 4, 5, 10, 11, 12]), "splice start before the beginning"); + + const ints = [1, 2]; + let nums: number[] = []; + nums.push(...ints); + assert(same(nums, [1, 2]), "push s32 spread into number[]"); + + let ss: string[] = []; + ss.push(...["a"], "b"); + assert(ss.length == 2, "strings: length"); + assert(ss[0] == "a", "strings: first"); + assert(ss[1] == "b", "strings: second"); + + print("done."); +}