diff --git a/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h b/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h index 198505eb7..296661d43 100644 --- a/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h +++ b/tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h @@ -893,14 +893,29 @@ class MLIRCustomMethods { MLIRCodeLogic mcl(builder, compileOptions); - if (!isa(startValue.getType())) - { - startValue = castFn(location, mlir::IndexType::get(builder.getContext()), startValue, genContext, false); - } + // The lowering reads start and delete count as signed (a negative start counts from the + // end), so they go to index through a signed 64-bit integer: straight from s32 to index + // is a zero extension, and -1 arrived as 4294967295. + auto toSignedIndex = [&](mlir::Value value) -> mlir::Value { + if (isa(value.getType())) + { + return value; + } - if (!isa(deleteCountValue.getType())) + auto si64Type = mlir::IntegerType::get(builder.getContext(), 64, mlir::IntegerType::Signed); + if (value.getType() != si64Type) + { + value = castFn(location, si64Type, value, genContext, false); + } + + return castFn(location, mlir::IndexType::get(builder.getContext()), value, genContext, false); + }; + + startValue = toSignedIndex(startValue); + deleteCountValue = toSignedIndex(deleteCountValue); + if (!startValue || !deleteCountValue) { - deleteCountValue = castFn(location, mlir::IndexType::get(builder.getContext()), deleteCountValue, genContext, false); + return mlir::failure(); } auto arrayElement = cast(thisValue.getType()).getElementType(); @@ -936,7 +951,20 @@ class MLIRCustomMethods ValueOrLogicalResult mlirGenArraySplice(const mlir::Location &location, ArrayRef operands, std::function castFn, const GenContext &genContext) { - return mlirGenArraySplice(location, operands.front(), operands[1], operands[2], operands.slice(3), castFn, genContext); + if (operands.size() < 2) + { + emitError(location) << "splice needs the index to start at"; + return mlir::failure(); + } + + // a left-out delete count removes everything from start on; the lowering clamps it to + // what is there (INT32_MAX stays positive in a 32-bit index too) + auto deleteCountValue = operands.size() > 2 + ? operands[2] + : builder.create(location, builder.getIndexType(), builder.getIndexAttr(INT32_MAX)).getResult(); + + return mlirGenArraySplice(location, operands.front(), operands[1], deleteCountValue, + operands.size() > 3 ? operands.slice(3) : ArrayRef(), castFn, genContext); } ValueOrLogicalResult mlirGenArrayView(const mlir::Location &location, mlir::Value thisValue, ArrayRef values, diff --git a/tslang/lib/TypeScript/LowerToLLVM.cpp b/tslang/lib/TypeScript/LowerToLLVM.cpp index a190bc7c8..b3311de30 100644 --- a/tslang/lib/TypeScript/LowerToLLVM.cpp +++ b/tslang/lib/TypeScript/LowerToLLVM.cpp @@ -3218,9 +3218,30 @@ struct ArraySpliceOpLowering : public TsLlvmPattern auto startIndexAsIndexType = spliceOp.getStart(); auto decSizeAsIndexType = spliceOp.getDeleteCount(); - auto startIndexAsLLVMType = rewriter.create(loc, llvmIndexType, startIndexAsIndexType); + mlir::Value startIndexAsLLVMType = rewriter.create(loc, llvmIndexType, startIndexAsIndexType); mlir::Value decSizeAsLLVMType = rewriter.create(loc, llvmIndexType, decSizeAsIndexType); + // Start and delete count arrive signed (mlirGenArraySplice). As JavaScript has it, a + // negative start counts from the end and stops at 0, a start past the end is the end, and + // a negative delete count deletes nothing. `a.splice(-1, 1)` used to take -1 as the largest + // unsigned index and fault. + { + auto zero = clh.createIndexConstantOf(llvmIndexType, 0); + auto startIsNegative = + rewriter.create(loc, LLVM::ICmpPredicate::slt, startIndexAsLLVMType, zero); + auto fromEnd = rewriter.create(loc, llvmIndexType, ValueRange{startIndexAsLLVMType, countAsIndexType}); + auto fromEndIsNegative = rewriter.create(loc, LLVM::ICmpPredicate::slt, fromEnd, zero); + auto negativeStart = rewriter.create(loc, fromEndIsNegative, zero, fromEnd); + auto startPastEnd = + rewriter.create(loc, LLVM::ICmpPredicate::sgt, startIndexAsLLVMType, countAsIndexType); + auto positiveStart = rewriter.create(loc, startPastEnd, countAsIndexType, startIndexAsLLVMType); + startIndexAsLLVMType = rewriter.create(loc, startIsNegative, negativeStart, positiveStart); + + auto deleteIsNegative = + rewriter.create(loc, LLVM::ICmpPredicate::slt, decSizeAsLLVMType, zero); + decSizeAsLLVMType = rewriter.create(loc, deleteIsNegative, zero, decSizeAsLLVMType); + } + auto incSizeAsLLVMType = clh.createIndexConstantOf(llvmIndexType, transformed.getItems().size()); // Give back what the removed elements were holding, before anything moves or frees them. diff --git a/tslang/test/tester/tests/00array_splice.ts b/tslang/test/tester/tests/00array_splice.ts index a8580a187..a5ebc3bb7 100644 --- a/tslang/test/tester/tests/00array_splice.ts +++ b/tslang/test/tester/tests/00array_splice.ts @@ -25,5 +25,36 @@ function main() { assert(c[1] == 99, "equal 1"); assert(c[2] == 4, "equal 2"); + // a negative start counts from the end; -1 used to be the largest unsigned index, and faulted + let d: number[] = [1, 2, 3, 4]; + d.splice(-1, 1); + assert(d.length == 3 && d[2] == 3, "negative start"); + + // a start before the beginning is 0 + let e: number[] = [1, 2, 3, 4]; + e.splice(-10, 1); + assert(e.length == 3 && e[0] == 2, "start before 0"); + + // a start past the end deletes nothing + let f: number[] = [1, 2, 3, 4]; + f.splice(10, 1); + assert(f.length == 4, "start past end"); + + // a left-out delete count removes everything from start on; it used to read past the operands + let g: number[] = [1, 2, 3, 4]; + g.splice(2); + assert(g.length == 2 && g[1] == 2, "no delete count"); + + // a negative delete count deletes nothing + let h: number[] = [1, 2, 3, 4]; + h.splice(1, -3); + assert(h.length == 4, "negative delete count"); + + // a `number` start, negative + let k: number[] = [1, 2, 3, 4]; + const start: number = -2; + k.splice(start, 1); + assert(k.length == 3 && k[2] == 4, "number start"); + print("done."); }