Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions tslang/include/TypeScript/LowerToLLVM/ArrayLayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "TypeScript/MLIRLogic/MLIRHelper.h"
#include "TypeScript/LowerToLLVM/LLVMCodeHelperBase.h"
#include "TypeScript/LowerToLLVM/CodeLogicHelper.h"
#include "TypeScript/LowerToLLVM/AssertLogic.h"

#include "mlir/Dialect/LLVMIR/LLVMDialect.h"

Expand Down Expand Up @@ -106,6 +107,9 @@ class ArrayLayout : public LLVMCodeHelperBase
// MemoryRealloc, which keeps an rc data block's count across the move and births one grown
// from null with the header's reference.
//
// A `needed` past 2^32 - 1, a byte count that overflows, or a failed allocation stops the
// program before anything is stored or zeroed (#483).
//
// A static header (makeStatic) must never reach the ops that change an array in place: it lives
// in a constant global with capacity = length over static data, and pop, shift, splice and
// `length =` would store into that global and zero slots of the static data. MLIRGen copies
Expand Down Expand Up @@ -139,11 +143,14 @@ class ArrayLayout : public LLVMCodeHelperBase
rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ult, newCapacity, constant(4));
newCapacity = rewriter.create<LLVM::SelectOp>(loc, belowMinimum, constant(4), newCapacity);

checkLength(needed);
auto newBytes = bytesFor(arrayType, newCapacity);
auto grown = MemoryRealloc(currentData, newBytes);
checkAllocated(grown, newBytes);

auto sizeOfElement = rewriter.create<mlir_ts::DialectCastOp>(
loc, llvmIndexType,
rewriter.create<mlir_ts::SizeOfOp>(loc, th.getIndexType(), arrayType.getElementType()));
auto newBytes = rewriter.create<LLVM::MulOp>(loc, llvmIndexType, ValueRange{sizeOfElement, newCapacity});
auto grown = MemoryRealloc(currentData, newBytes);

auto oldBytes = rewriter.create<LLVM::MulOp>(loc, llvmIndexType, ValueRange{sizeOfElement, capacity});
auto tailStart = rewriter.create<LLVM::GEPOp>(loc, ptrType, th.getI8Type(), grown, ValueRange{oldBytes});
Expand All @@ -159,6 +166,56 @@ class ArrayLayout : public LLVMCodeHelperBase
[&](OpBuilder &, Location) -> mlir::Value { return currentData; });
}

// A length is at most 2^32 - 1, as in TypeScript (#483): past it - `length = 2^42`, or a
// negative integer, which is huge once sign-extended to an index - the program stops as a
// failing assert does. With a 32-bit index every value is in range, and the byte count and
// allocation checks below catch what does not fit.
void checkLength(mlir::Value length)
{
TypeHelper th(rewriter);
auto loc = op->getLoc();
auto llvmIndexType = typeConverter->convertType(th.getIndexType());
if (llvmIndexType.getIntOrFloatBitWidth() <= 32)
{
return;
}

auto maxLength = rewriter.create<LLVM::ConstantOp>(loc, llvmIndexType,
rewriter.getIntegerAttr(llvmIndexType, 0xFFFFFFFFll));
auto valid = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ule, length, maxLength);
AssertLogic(op, rewriter, typeConverter, loc, compileOptions).check(valid, "Invalid array length");
}

// the bytes `count` elements take; the program stops when they do not fit in an index (#483)
mlir::Value bytesFor(mlir_ts::ArrayType arrayType, mlir::Value count)
{
TypeHelper th(rewriter);
auto loc = op->getLoc();
auto llvmIndexType = typeConverter->convertType(th.getIndexType());
auto sizeOfElement = rewriter.create<mlir_ts::DialectCastOp>(
loc, llvmIndexType, rewriter.create<mlir_ts::SizeOfOp>(loc, th.getIndexType(), arrayType.getElementType()));
auto allOnes = rewriter.create<LLVM::ConstantOp>(loc, llvmIndexType, rewriter.getIntegerAttr(llvmIndexType, -1));
auto maxCount = rewriter.create<LLVM::UDivOp>(loc, llvmIndexType, ValueRange{allOnes, sizeOfElement});
auto fits = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ule, count, maxCount);
AssertLogic(op, rewriter, typeConverter, loc, compileOptions).check(fits, "Invalid array length");
return rewriter.create<LLVM::MulOp>(loc, llvmIndexType, ValueRange{sizeOfElement, count});
}

// the program stops when an allocation of `bytes` (more than none) gave no block (#483)
void checkAllocated(mlir::Value block, mlir::Value bytes)
{
TypeHelper th(rewriter);
auto loc = op->getLoc();
auto llvmIndexType = typeConverter->convertType(th.getIndexType());
auto got = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ne, block,
rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType()));
auto none = rewriter.create<LLVM::ICmpOp>(
loc, LLVM::ICmpPredicate::eq, bytes,
rewriter.create<LLVM::ConstantOp>(loc, llvmIndexType, rewriter.getIntegerAttr(llvmIndexType, 0)));
auto allocated = rewriter.create<LLVM::OrOp>(loc, got, none);
AssertLogic(op, rewriter, typeConverter, loc, compileOptions).check(allocated, "Out of memory");
}

// zero `count` elements of `data` from `index` on: the slots pop, shift, splice and a smaller
// `length =` vacate, so gc does not keep what they held alive and a later growth reads zero
void zeroElements(mlir_ts::ArrayType arrayType, mlir::Value data, mlir::Value index, mlir::Value count)
Expand Down
118 changes: 39 additions & 79 deletions tslang/include/TypeScript/LowerToLLVM/AssertLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include "TypeScript/TypeScriptOps.h"

#include "TypeScript/LowerToLLVM/CodeLogicHelper.h"
#include "TypeScript/LowerToLLVM/LLVMCodeHelper.h"
#include "TypeScript/LowerToLLVM/LLVMCodeHelperBase.h"
#include "TypeScript/LowerToLLVM/TypeConverterHelper.h"
#include "TypeScript/LowerToLLVM/TypeHelper.h"
#include "TypeScript/LowerToLLVM/LocationHelper.h"
Expand All @@ -28,7 +28,7 @@ class AssertLogic
Operation *op;
PatternRewriter &rewriter;
TypeHelper th;
LLVMCodeHelper ch;
LLVMCodeHelperBase ch;
CodeLogicHelper clh;
Location loc;

Expand All @@ -44,14 +44,29 @@ class AssertLogic
typeOfValueType = th.getPtrType();
}

AssertLogic(Operation *op, PatternRewriter &rewriter, const TypeConverter *typeConverter, Location loc, CompileOptions &compileOptions)
: op(op), rewriter(rewriter), th(rewriter), ch(op, rewriter, typeConverter, compileOptions), clh(op, rewriter), loc(loc)
{
sizeType = th.getIndexType();
typeOfValueType = th.getPtrType();
}

// `message`, when given, is the text known only at run time and is shown instead of `msg`
mlir::LogicalResult logic(mlir::Value condValue, std::string msg, mlir::Value message = mlir::Value())
{
#ifdef WIN32
return logicWin32(condValue, msg, message);
#else
return logicUnix(condValue, msg, message);
#endif
// the test replaces the assert op, which the split left at the top of the continuation
failUnless(condValue, msg, message);
rewriter.eraseOp(op);
return success();
}

// A check in the middle of another lowering (#483): execution goes on at the insertion point
// when `condValue` holds, and stops with `msg` and the operation's file and line, as a failing
// assert does, when it does not.
void check(mlir::Value condValue, std::string msg)
{
auto *continuationBlock = failUnless(condValue, msg, mlir::Value());
rewriter.setInsertionPointToStart(continuationBlock);
}

// `_assert` and `__assert_fail` abort, which flushes no stream: what the program printed before
Expand All @@ -76,113 +91,58 @@ class AssertLogic
return rewriter.create<LLVM::SelectOp>(loc, isNull, msgCst, message);
}

mlir::LogicalResult logicWin32(mlir::Value condValue, std::string msg, mlir::Value message)
private:
// Splits the block at the insertion point and ends the first half with a branch on
// `condValue`: on to the continuation, which is returned, or to a new block that reports the
// failure (`_assert` on Windows, `__assert_fail` elsewhere) and never returns.
mlir::Block *failUnless(mlir::Value condValue, std::string msg, mlir::Value message)
{
auto unreachable = clh.FindUnreachableBlockOrCreate();

auto [fileName, lineAndColumn] = LLVMLocationHelper::getLineAndColumnAndFileName(loc);
auto [line, column] = lineAndColumn;

// Insert the `_assert` declaration if necessary.
auto i8PtrTy = th.getPtrType();
#ifdef WIN32
auto assertFuncOp =
ch.getOrInsertFunction("_assert", th.getFunctionType(th.getVoidType(), {i8PtrTy, i8PtrTy, rewriter.getI32Type()}));

// Split block at `assert` operation.
auto *opBlock = rewriter.getInsertionBlock();
auto opPosition = rewriter.getInsertionPoint();
auto *continuationBlock = rewriter.splitBlock(opBlock, opPosition);

// Generate IR to call `assert`.
auto *failureBlock = rewriter.createBlock(opBlock->getParent());

std::stringstream msgWithNUL;
msgWithNUL << msg;

auto opHash = std::hash<std::string>{}(msgWithNUL.str());

std::stringstream msgVarName;
msgVarName << "m_" << opHash;

std::stringstream fileVarName;
fileVarName << "f_" << hash_value(fileName);

std::stringstream fileWithNUL;
fileWithNUL << fileName.str();

auto msgCst = ch.getOrCreateGlobalString(msgVarName.str(), msgWithNUL.str());

auto fileCst = ch.getOrCreateGlobalString(fileVarName.str(), fileName.str());

// auto nullCst = rewriter.create<LLVM::NullOp>(loc, getI8PtrType(context));

mlir::Value lineNumberRes = rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(line));

flushOutput();
rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{messageOrConstant(message, msgCst), fileCst, lineNumberRes});
// rewriter.create<LLVM::UnreachableOp>(loc);
rewriter.create<mlir::cf::BranchOp>(loc, unreachable);

// Generate assertion test.
rewriter.setInsertionPointToEnd(opBlock);
rewriter.replaceOpWithNewOp<LLVM::CondBrOp>(op, condValue, continuationBlock, failureBlock);

return success();
}

mlir::LogicalResult logicUnix(mlir::Value condValue, std::string msg, mlir::Value message)
{
auto unreachable = clh.FindUnreachableBlockOrCreate();

auto [fileName, lineAndColumn] = LLVMLocationHelper::getLineAndColumnAndFileName(loc);
auto [line, column] = lineAndColumn;

// Insert the `_assert` declaration if necessary.
auto i8PtrTy = th.getPtrType();
#else
auto assertFuncOp = ch.getOrInsertFunction(
"__assert_fail", th.getFunctionType(th.getVoidType(), {i8PtrTy, i8PtrTy, rewriter.getI32Type(), i8PtrTy}));
#endif

// Split block at `assert` operation.
auto *opBlock = rewriter.getInsertionBlock();
auto opPosition = rewriter.getInsertionPoint();
auto *continuationBlock = rewriter.splitBlock(opBlock, opPosition);

// Generate IR to call `assert`.
auto *failureBlock = rewriter.createBlock(opBlock->getParent());

std::stringstream msgWithNUL;
msgWithNUL << msg;

auto opHash = std::hash<std::string>{}(msgWithNUL.str());
auto opHash = std::hash<std::string>{}(msg);

std::stringstream msgVarName;
msgVarName << "m_" << opHash;

std::stringstream fileVarName;
fileVarName << "f_" << hash_value(fileName);

std::stringstream fileWithNUL;
fileWithNUL << fileName.str();

auto msgCst = ch.getOrCreateGlobalString(msgVarName.str(), msgWithNUL.str());

auto msgCst = ch.getOrCreateGlobalString(msgVarName.str(), msg);
auto fileCst = ch.getOrCreateGlobalString(fileVarName.str(), fileName.str());

// auto nullCst = rewriter.create<LLVM::NullOp>(loc, getI8PtrType(context));

mlir::Value lineNumberRes = rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(line));
mlir::Value funcName = rewriter.create<LLVM::ZeroOp>(loc, i8PtrTy);

flushOutput();
#ifdef WIN32
rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{messageOrConstant(message, msgCst), fileCst, lineNumberRes});
#else
mlir::Value funcName = rewriter.create<LLVM::ZeroOp>(loc, i8PtrTy);
rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{messageOrConstant(message, msgCst), fileCst, lineNumberRes, funcName});
// rewriter.create<LLVM::UnreachableOp>(loc);
#endif
rewriter.create<mlir::cf::BranchOp>(loc, unreachable);

// Generate assertion test.
rewriter.setInsertionPointToEnd(opBlock);
rewriter.replaceOpWithNewOp<LLVM::CondBrOp>(op, condValue, continuationBlock, failureBlock);
rewriter.create<LLVM::CondBrOp>(loc, condValue, continuationBlock, failureBlock);

return success();
return continuationBlock;
}
};
} // namespace typescript
Expand Down
12 changes: 6 additions & 6 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3124,20 +3124,20 @@ struct NewArrayOpLowering : public TsLlvmPattern<mlir_ts::NewArrayOp>
auto llvmIndexType = tch.convertType(th.getIndexType());
auto llvmElementType = tch.convertType(elementType);

auto sizeOfTypeValueMLIR = rewriter.create<mlir_ts::SizeOfOp>(loc, th.getIndexType(), elementType);
auto sizeOfTypeValue = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmIndexType, sizeOfTypeValueMLIR);

auto countAsIndexTypeMLIR = rewriter.create<mlir_ts::CastOp>(loc, th.getIndexType(), transformed.getCount());
auto countAsIndexType = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmIndexType, countAsIndexTypeMLIR);

auto multSizeOfTypeValue =
rewriter.create<LLVM::MulOp>(loc, llvmIndexType, ValueRange{sizeOfTypeValue, countAsIndexType});
// a length past 2^32 - 1 (a negative one included), a size that overflows, or a failed
// allocation stops the program (#483)
ArrayLayout layout(newArrOp, rewriter, getTypeConverter(), tsLlvmContext->compileOptions);
layout.checkLength(countAsIndexType);
auto multSizeOfTypeValue = layout.bytesFor(arrayType, countAsIndexType);

// zeroed: its elements are not set yet, and under `-mm=rc` the first store into one
// releases what the slot held - `malloc`'s leftovers would be released as a reference
auto allocated = ch.MemoryAlloc(multSizeOfTypeValue, MemoryAllocSet::Zero);
layout.checkAllocated(allocated, multSizeOfTypeValue);

ArrayLayout layout(newArrOp, rewriter, getTypeConverter(), tsLlvmContext->compileOptions);
auto structValue3 = layout.make(arrayType, allocated, transformed.getCount());

rewriter.replaceOp(newArrOp, ValueRange{structValue3});
Expand Down
31 changes: 31 additions & 0 deletions tslang/lib/TypeScript/MLIRGenImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -5947,6 +5947,37 @@ class MLIRGenImpl
return mlir::failure();
}

// A length is an integer in [0, 2^32 - 1] (TypeScript throws a RangeError). A number is
// checked here, before its conversion to an index drops a fraction and makes a negative
// or a NaN a huge size; an integer is checked against the upper bound where it is
// lowered (SetLengthOfOpLowering), which a negative one exceeds once sign-extended. A
// failed check stops the program as a failing assert does (#483).
if (isa<mlir_ts::NumberType>(savingValue.getType()))
{
auto numberConstant = [&](double value) -> mlir::Value {
#ifdef NUMBER_F64
return builder.create<mlir_ts::ConstantOp>(location, getNumberType(), builder.getF64FloatAttr(value));
#else
return builder.create<mlir_ts::ConstantOp>(location, getNumberType(), builder.getF32FloatAttr(value));
#endif
};

auto checkLength = [&](SyntaxKind opCode, mlir::Value left, mlir::Value right) {
auto holds = builder.create<mlir_ts::LogicalBinaryOp>(
location, getBooleanType(), builder.getI32IntegerAttr((int)opCode), left, right);
builder.create<mlir_ts::AssertOp>(location, holds, mlir::Value(),
builder.getStringAttr("Invalid array length"));
};

checkLength(SyntaxKind::GreaterThanEqualsToken, savingValue, numberConstant(0));
checkLength(SyntaxKind::LessThanEqualsToken, savingValue, numberConstant(4294967295.0));

// an integer survives the round trip through an index
CAST_A(asIndex, location, builder.getIndexType(), savingValue, genContext);
CAST_A(backToNumber, location, getNumberType(), asIndex, genContext);
checkLength(SyntaxKind::EqualsEqualsToken, backToNumber, savingValue);
}

// special case to resize array
syncSavingValue(lengthOf.getResult().getType());
builder.create<mlir_ts::SetLengthOfOp>(location, arrayValueLoaded, savingValue);
Expand Down
Loading
Loading