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
33 changes: 33 additions & 0 deletions tslang/include/TypeScript/LowerToLLVM/LLVMCodeHelperBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,39 @@ class LLVMCodeHelperBase
return _MemoryAlloc<int>(sizeOfAlloc, zero);
}

// A copy of `count` bytes into a block with `destSize` bytes left, which ends the process
// rather than write past the block. A Windows target calls the CRT's memcpy_s (its invalid
// parameter handler ends the process); glibc, Bionic and Darwin have no memcpy_s, and get the
// fortified __memcpy_chk instead ("buffer overflow detected"), which LLVM turns back into a
// plain memcpy where it can prove the copy fits. Wasm has neither: llvm.memcpy.
void MemoryCopy(mlir::Value dest, mlir::Value destSize, mlir::Value src, mlir::Value count)
{
auto loc = op->getLoc();
if (compileOptions.isWasm)
{
rewriter.create<LLVM::MemcpyOp>(loc, dest, src, count, /*isVolatile=*/false);
return;
}

TypeHelper th(rewriter);
auto llvmIndexType = count.getType();
if (compileOptions.isWindows)
{
// errno_t memcpy_s(void *dest, size_t destSize, const void *src, size_t count)
auto memcpySFuncOp = getOrInsertFunction(
"memcpy_s",
th.getFunctionType(rewriter.getI32Type(), {th.getPtrType(), llvmIndexType, th.getPtrType(), llvmIndexType}));
rewriter.create<LLVM::CallOp>(loc, memcpySFuncOp, ValueRange{dest, destSize, src, count});
return;
}

// void *__memcpy_chk(void *dest, const void *src, size_t count, size_t destSize)
auto memcpyChkFuncOp = getOrInsertFunction(
"__memcpy_chk",
th.getFunctionType(th.getPtrType(), {th.getPtrType(), th.getPtrType(), llvmIndexType, llvmIndexType}));
rewriter.create<LLVM::CallOp>(loc, memcpyChkFuncOp, ValueRange{dest, src, count, destSize});
}

mlir::Value MemoryAlloc(mlir::Type storageType, MemoryAllocSet zero = MemoryAllocSet::None)
{
TypeHelper th(rewriter);
Expand Down
66 changes: 39 additions & 27 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -249,30 +249,38 @@ class ParseIntOpLowering : public TsLlvmPattern<mlir_ts::ParseIntOp>
TypeHelper th(rewriter);
LLVMCodeHelper ch(op, rewriter, getTypeConverter(), tsLlvmContext->compileOptions);

// A result wider than 32 bits (a bigint is an i64) needs the `long long` functions: `atoi`
// returns an int, and `strtol` a `long`, which is also 32 bits on Windows.
auto resultType = getTypeConverter()->convertType(op.getType());
auto wide = resultType.isIntOrFloat() && resultType.getIntOrFloatBitWidth() > 32;
auto parsedType = wide ? rewriter.getI64Type() : rewriter.getI32Type();
CodeLogicHelper clh(op, rewriter);

// Insert the `atoi` declaration if necessary.
auto loc = op->getLoc();

// Always `strtoll`, base 10 unless one is given: `long long` is 64 bits on every target,
// while `strtol` returns a `long`, 64 bits on Linux and 32 on Windows, and `atoi` leaves a
// value out of range undefined. strtoll saturates at the i64 range.
auto i8PtrTy = th.getPtrType();
LLVM::LLVMFuncOp parseIntFuncOp;
if (transformed.getBase())
{
parseIntFuncOp = ch.getOrInsertFunction(
wide ? "strtoll" : "strtol",
th.getFunctionType(parsedType, {i8PtrTy, th.getPtrType(), rewriter.getI32Type()}));
auto nullOp = rewriter.create<LLVM::ZeroOp>(op->getLoc(), th.getPtrType());
rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, parseIntFuncOp,
ValueRange{transformed.getArg(), nullOp, transformed.getBase()});
}
else
auto i64Type = rewriter.getI64Type();
auto parseIntFuncOp = ch.getOrInsertFunction(
"strtoll", th.getFunctionType(i64Type, {i8PtrTy, th.getPtrType(), rewriter.getI32Type()}));
auto nullOp = rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType());
mlir::Value base = transformed.getBase() ? transformed.getBase() : clh.createI32ConstantOf(10);
mlir::Value parsed =
rewriter.create<LLVM::CallOp>(loc, parseIntFuncOp, ValueRange{transformed.getArg(), nullOp, base}).getResult();

// A result narrower than 64 bits saturates at its own range, as strtoll does at i64's
auto resultType = getTypeConverter()->convertType(op.getType());
auto width = resultType.isIntOrFloat() ? resultType.getIntOrFloatBitWidth() : 64;
if (width < 64)
{
parseIntFuncOp = ch.getOrInsertFunction(wide ? "atoll" : "atoi", th.getFunctionType(parsedType, {i8PtrTy}));
rewriter.replaceOpWithNewOp<LLVM::CallOp>(op, parseIntFuncOp, ValueRange{transformed.getArg()});
auto maxValue = rewriter.create<LLVM::ConstantOp>(
loc, i64Type, rewriter.getI64IntegerAttr(llvm::APInt::getSignedMaxValue(width).getSExtValue()));
auto minValue = rewriter.create<LLVM::ConstantOp>(
loc, i64Type, rewriter.getI64IntegerAttr(llvm::APInt::getSignedMinValue(width).getSExtValue()));
parsed = rewriter.create<LLVM::SMinOp>(loc, parsed, maxValue);
parsed = rewriter.create<LLVM::SMaxOp>(loc, parsed, minValue);
parsed = rewriter.create<LLVM::TruncOp>(loc, resultType, parsed);
}

rewriter.replaceOp(op, parsed);

return success();
}
};
Expand All @@ -292,12 +300,15 @@ class ParseFloatOpLowering : public TsLlvmPattern<mlir_ts::ParseFloatOp>

auto loc = op->getLoc();

// Insert the `atof` declaration if necessary.
// `strtod`, not `atof`: atof leaves a value out of range undefined, strtod gives an infinity
// or 0
auto i8PtrTy = th.getPtrType();
auto parseFloatFuncOp = ch.getOrInsertFunction("atof", th.getFunctionType(rewriter.getF64Type(), {i8PtrTy}));
auto parseFloatFuncOp = ch.getOrInsertFunction(
"strtod", th.getFunctionType(rewriter.getF64Type(), {i8PtrTy, th.getPtrType()}));
auto nullOp = rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType());

// atof gives a double whatever the result is: `<f32>"2.5"` wants a float
auto funcCall = rewriter.create<LLVM::CallOp>(loc, parseFloatFuncOp, ValueRange{transformed.getArg()});
// strtod gives a double whatever the result is: `<f32>"2.5"` wants a float
auto funcCall = rewriter.create<LLVM::CallOp>(loc, parseFloatFuncOp, ValueRange{transformed.getArg(), nullOp});
auto resultType = getTypeConverter()->convertType(op.getType());
if (resultType == rewriter.getF64Type())
{
Expand Down Expand Up @@ -781,7 +792,7 @@ class StringResizeOpLowering : public TsLlvmPattern<mlir_ts::StringResizeOp>
auto fits = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::ult, sourceBytes, size);
auto copyBytes = rewriter.create<LLVM::SelectOp>(loc, fits, sourceBytes, size);

rewriter.create<LLVM::MemcpyOp>(loc, newStringValue, source, copyBytes, /*isVolatile=*/false);
ch.MemoryCopy(newStringValue, size, source, copyBytes);

rewriter.replaceOp(op, ValueRange{newStringValue});
return success();
Expand Down Expand Up @@ -865,7 +876,8 @@ class StringConcatOpLowering : public TsLlvmPattern<mlir_ts::StringConcatOp>
for (auto [oper, length] : llvm::zip(opers, lengths))
{
auto dest = rewriter.create<LLVM::GEPOp>(loc, i8PtrTy, th.getI8Type(), newStringValue, ValueRange{offset});
rewriter.create<LLVM::MemcpyOp>(loc, dest, oper, length, /*isVolatile=*/false);
auto left = rewriter.create<LLVM::SubOp>(loc, llvmIndexType, ValueRange{size, offset});
ch.MemoryCopy(dest, left, oper, length);
offset = rewriter.create<LLVM::AddOp>(loc, llvmIndexType, ValueRange{offset, length});
}

Expand Down Expand Up @@ -909,7 +921,7 @@ class StringCopyOpLowering : public TsLlvmPattern<mlir_ts::StringCopyOp>
loc, llvmIndexType,
ValueRange{bytes, rewriter.create<LLVM::ConstantOp>(loc, llvmIndexType, rewriter.getIntegerAttr(llvmIndexType, 1))});
auto copy = ch.MemoryAlloc(bytes);
rewriter.create<LLVM::MemcpyOp>(loc, copy, source, bytes, /*isVolatile=*/false);
ch.MemoryCopy(copy, bytes, source, bytes);
return copy;
},
[&](OpBuilder &, Location) -> mlir::Value { return source; });
Expand Down Expand Up @@ -3650,7 +3662,7 @@ struct ArrayViewOpLowering : public TsLlvmPattern<mlir_ts::ArrayViewOp>
auto sizeOfTypeValue = rewriter.create<mlir_ts::DialectCastOp>(loc, llvmIndexType, sizeOfTypeValueMLIR);
auto bytes = rewriter.create<LLVM::MulOp>(loc, llvmIndexType, ValueRange{sizeOfTypeValue, count});
auto copy = ch.MemoryAlloc(bytes);
rewriter.create<LLVM::MemcpyOp>(loc, copy, arrayOffset, bytes, /*isVolatile=*/false);
ch.MemoryCopy(copy, bytes, arrayOffset, bytes);

// the copy holds a second reference to each element, and its release gives them back
{
Expand Down
3 changes: 3 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,7 @@ tslang_add_test(NAME test-compile-00-for-condition-narrowing COMMAND test-runner
tslang_add_test(NAME test-compile-00-param-assigned-owned COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00param_assigned_owned.ts")
tslang_add_test(NAME test-compile-00-string-empty-falsy COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_empty_falsy.ts")
tslang_add_test(NAME test-compile-00-string-concat-bounds COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_concat_bounds.ts")
tslang_add_test(NAME test-compile-00-parse-number-range COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00parse_number_range.ts")
tslang_add_test(NAME test-compile-00-narrowed-assign-other-member COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00narrowed_assign_other_member.ts")
tslang_add_test(NAME test-compile-00-const-record-owned-fields COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-compile-00-array-length-valid COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
Expand Down Expand Up @@ -878,6 +879,7 @@ tslang_add_test(NAME test-jit-00-for-condition-narrowing COMMAND test-runner -ji
tslang_add_test(NAME test-jit-00-param-assigned-owned COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00param_assigned_owned.ts")
tslang_add_test(NAME test-jit-00-string-empty-falsy COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_empty_falsy.ts")
tslang_add_test(NAME test-jit-00-string-concat-bounds COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00string_concat_bounds.ts")
tslang_add_test(NAME test-jit-00-parse-number-range COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00parse_number_range.ts")
tslang_add_test(NAME test-jit-00-narrowed-assign-other-member COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00narrowed_assign_other_member.ts")
tslang_add_test(NAME test-jit-00-const-record-owned-fields COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00const_record_owned_fields.ts")
tslang_add_test(NAME test-jit-00-array-length-valid COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00array_length_valid.ts")
Expand Down Expand Up @@ -1725,6 +1727,7 @@ set(TSLANG_CORPUS
00param_assigned_owned.ts
00string_empty_falsy.ts
00string_concat_bounds.ts
00parse_number_range.ts
00narrowed_assign_other_member.ts
00const_record_owned_fields.ts
00array_length_valid.ts
Expand Down
30 changes: 30 additions & 0 deletions tslang/test/tester/tests/00parse_number_range.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// parseInt, parseFloat and a cast from a string to a number parse with strtoll/strtod: atoi and
// atof leave a value out of range undefined (MSVC saturated, glibc wrapped), and strtol returns a
// `long`, 64 bits on Linux, which was read as 32. A 32-bit result saturates at the i32 range.
function main() {
assert(parseInt("42") == 42, "parseInt");
assert(parseInt(" -17abc") == -17, "leading spaces, sign, trailing text");
assert(parseInt("abc") == 0, "no digits");
assert(parseInt("010") == 10, "decimal, not octal, without a radix");
assert(parseInt("ff", 16) == 255, "radix 16");
assert(parseInt("-101", 2) == -5, "radix 2");

assert(parseInt("2147483647") == 2147483647, "i32 max");
assert(parseInt("-2147483648") == -2147483648, "i32 min");
assert(parseInt("3000000000") == 2147483647, "above i32 saturates");
assert(parseInt("-3000000000") == -2147483648, "below i32 saturates");
assert(parseInt("99999999999999999999999") == 2147483647, "above i64 saturates");
assert(parseInt("100000000", 16) == 2147483647, "radix 16 above i32 saturates");

const s = "123";
assert(<i32>s == 123, "cast to i32");
assert(<i64>"9007199254740993" == 9007199254740993, "cast to i64");

assert(parseFloat("2.5e3") == 2500, "parseFloat");
assert(parseFloat(" -0.25x") == -0.25, "leading spaces, trailing text");
assert(parseFloat("1e400") > 1e308, "above the double range is infinite");
assert(parseFloat("1e-400") == 0, "below the double range is 0");
assert(<f32>"2.5" == 2.5, "cast to f32");

print("done.");
}
15 changes: 13 additions & 2 deletions tslang/tslang/exe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,10 +546,21 @@ static llvm::Error writeAsInvokerManifestObj(llvm::COFF::MachineTypes machine, l
}

char *buf = res->getBufferStart();
char *bufEnd = res->getBufferEnd();
memset(buf, 0, resSize);

// a copy bounded by what is left of the resource: memcpy_s is MSVC only
auto copyInto = [&](char *dest, const void *src, size_t count) {
#ifdef _WIN32
memcpy_s(dest, static_cast<size_t>(bufEnd - dest), src, count);
#else
assert(count <= static_cast<size_t>(bufEnd - dest));
memcpy(dest, src, count);
#endif
};

// the file header: the magic, then a null entry
memcpy(buf, llvm::COFF::WinResMagic, sizeof(llvm::COFF::WinResMagic));
copyInto(buf, llvm::COFF::WinResMagic, sizeof(llvm::COFF::WinResMagic));
buf += llvm::object::WIN_RES_MAGIC_SIZE + llvm::object::WIN_RES_NULL_ENTRY_SIZE;

auto *prefix = reinterpret_cast<llvm::object::WinResHeaderPrefix *>(buf);
Expand All @@ -570,7 +581,7 @@ static llvm::Error writeAsInvokerManifestObj(llvm::COFF::MachineTypes machine, l
suffix->Characteristics = 0;
buf += sizeof(llvm::object::WinResHeaderSuffix);

memcpy(buf, manifest, manifestSize);
copyInto(buf, manifest, manifestSize);

auto resource = llvm::object::WindowsResource::createWindowsResource(res->getMemBufferRef());
if (!resource)
Expand Down
7 changes: 7 additions & 0 deletions tslang/tslang/jit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,10 @@ static int prepareJitProcess(CompileOptions &compileOptions)
}
addSym("memset", (void*)&memset);
addSym("memcpy", (void*)&memcpy);
#ifdef _WIN32
// the bounded copy a Windows target emits (LLVMCodeHelperBase::MemoryCopy)
addSym("memcpy_s", (void*)&memcpy_s);
#endif
addSym("fflush", (void*)&fflush);
// see jitAssertFailed above: bound to ucrtbase this is a modal message box, which an
// unattended run cannot answer
Expand Down Expand Up @@ -874,6 +878,9 @@ static std::unique_ptr<llvm::orc::LLJIT> createJit(llvm::orc::JITTargetMachineBu
}
addOverride("memset", (void *)&memset);
addOverride("memcpy", (void *)&memcpy);
#ifdef _WIN32
addOverride("memcpy_s", (void *)&memcpy_s);
#endif
addOverride("fflush", (void *)&fflush);
// see jitAssertFailed above: bound to ucrtbase this is a modal message box, which an
// unattended run cannot answer
Expand Down
Loading