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
39 changes: 32 additions & 7 deletions tslang/include/TypeScript/LowerToLLVM/AssertLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,39 @@ class AssertLogic
typeOfValueType = th.getPtrType();
}

mlir::LogicalResult logic(mlir::Value condValue, std::string msg)
// `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);
return logicWin32(condValue, msg, message);
#else
return logicUnix(condValue, msg);
return logicUnix(condValue, msg, message);
#endif
}

mlir::LogicalResult logicWin32(mlir::Value condValue, std::string msg)
// `_assert` and `__assert_fail` abort, which flushes no stream: what the program printed before
// the failure was lost whenever stdout was not a console (a pipe, a file, the test runner)
void flushOutput()
{
auto fflushFuncOp = ch.getOrInsertFunction("fflush", th.getFunctionType(rewriter.getI32Type(), {th.getPtrType()}));
mlir::Value allStreams = rewriter.create<LLVM::ZeroOp>(loc, th.getPtrType());
rewriter.create<LLVM::CallOp>(loc, fflushFuncOp, ValueRange{allStreams});
}

// a null string shows the constant message instead
mlir::Value messageOrConstant(mlir::Value message, mlir::Value msgCst)
{
if (!message)
{
return msgCst;
}

auto nullPtr = rewriter.create<LLVM::ZeroOp>(loc, message.getType());
auto isNull = rewriter.create<LLVM::ICmpOp>(loc, LLVM::ICmpPredicate::eq, message, nullPtr);
return rewriter.create<LLVM::SelectOp>(loc, isNull, msgCst, message);
}

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

Expand Down Expand Up @@ -95,7 +118,8 @@ class AssertLogic

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

rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{msgCst, fileCst, lineNumberRes});
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);

Expand All @@ -106,7 +130,7 @@ class AssertLogic
return success();
}

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

Expand Down Expand Up @@ -149,7 +173,8 @@ class AssertLogic
mlir::Value lineNumberRes = rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(line));
mlir::Value funcName = rewriter.create<LLVM::ZeroOp>(loc, i8PtrTy);

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

Expand Down
38 changes: 23 additions & 15 deletions tslang/include/TypeScript/MLIRLogic/MLIRCodeLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -580,28 +580,36 @@ class MLIRCustomMethods
return mlir::failure();
}

// The message: a constant string is kept as text, anything else - `what + " failed"` - is
// shown as it is at run time. Such a message used to be erased as if it were a constant,
// with its other uses still there ("operation's operand is unlinked").
auto msg = StringRef("assert");
mlir::Value message;
if (operands.size() > 1)
{
for (auto opIndex = 1; opIndex < operands.size(); opIndex++)
auto param2 = operands[1];
auto constantOp = param2.getDefiningOp<mlir_ts::ConstantOp>();
auto type = constantOp ? constantOp.getType() : mlir::Type();
if (auto literalType = dyn_cast_or_null<mlir_ts::LiteralType>(type))
{
auto param2 = operands[opIndex];
auto constantOp = dyn_cast<mlir_ts::ConstantOp>(param2.getDefiningOp());
if (constantOp)
{
auto type = constantOp.getType();
if (auto literalType = dyn_cast<mlir_ts::LiteralType>(type))
{
type = literalType.getElementType();
}
type = literalType.getElementType();
}

if (isa<mlir_ts::StringType>(type))
if (constantOp && isa<mlir_ts::StringType>(type))
{
msg = cast<mlir::StringAttr>(constantOp.getValue()).getValue();
}
else
{
message = param2;
if (!isa<mlir_ts::StringType>(message.getType()))
{
message = castFn(location, mlir_ts::StringType::get(builder.getContext()), message, genContext, false);
if (!message)
{
msg = cast<mlir::StringAttr>(constantOp.getValue()).getValue();
return mlir::failure();
}
}

param2.getDefiningOp()->erase();
}
}

Expand All @@ -612,7 +620,7 @@ class MLIRCustomMethods
}

auto assertOp =
builder.create<mlir_ts::AssertOp>(location, op, mlir::StringAttr::get(builder.getContext(), msg));
builder.create<mlir_ts::AssertOp>(location, op, message, mlir::StringAttr::get(builder.getContext(), msg));

return mlir::success();
}
Expand Down
6 changes: 3 additions & 3 deletions tslang/include/TypeScript/TypeScriptOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,9 @@ def TypeScript_AssertOp : TypeScript_Op<"Assert"> {
```
}];

let arguments = (ins TypeScript_Boolean:$arg, StrAttr:$msg);


// `message`, when present, is a message known only at run time (`assert(ok, what + " failed")`)
// and is shown instead of `msg`
let arguments = (ins TypeScript_Boolean:$arg, Optional<TypeScript_String>:$message, StrAttr:$msg);

let hasCanonicalizer = 1;
}
Expand Down
2 changes: 1 addition & 1 deletion tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class AssertOpLowering : public TsLlvmPattern<mlir_ts::AssertOp>
{
TypeConverterHelper tch(getTypeConverter());
AssertLogic al(op, rewriter, tch, op->getLoc(), tsLlvmContext->compileOptions);
return al.logic(transformed.getArg(), op.getMsg().str());
return al.logic(transformed.getArg(), op.getMsg().str(), transformed.getMessage());
}
};

Expand Down
16 changes: 16 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ tslang_add_test(NAME test-compile-00-funcs-vararg COMMAND test-runner "${PROJECT
tslang_add_test(NAME test-compile-00-declare-function-twice COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00declare_function_twice.ts")
tslang_add_test(NAME test-compile-00-function-overloads COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00function_overloads.ts")
tslang_add_test(NAME test-compile-00-gc-allocations-distinct COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00gc_allocations_distinct.ts")
tslang_add_test(NAME test-compile-00-assert-message COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/00assert_message.ts")
tslang_add_test(NAME test-compile-01-funcs-vararg COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/01funcs_vararg.ts")
# printf is avaliable in JIT only, C has snprintf
#tslang_add_test(NAME test-compile-02-funcs-vararg COMMAND test-runner "${PROJECT_SOURCE_DIR}/test/tester/tests/02funcs_vararg.ts")
Expand Down Expand Up @@ -730,6 +731,7 @@ tslang_add_test(NAME test-jit-00-funcs-vararg COMMAND test-runner -jit "${PROJEC
tslang_add_test(NAME test-jit-00-declare-function-twice COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00declare_function_twice.ts")
tslang_add_test(NAME test-jit-00-function-overloads COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00function_overloads.ts")
tslang_add_test(NAME test-jit-00-gc-allocations-distinct COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00gc_allocations_distinct.ts")
tslang_add_test(NAME test-jit-00-assert-message COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00assert_message.ts")
tslang_add_test(NAME test-jit-01-funcs-vararg COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/01funcs_vararg.ts")
tslang_add_test(NAME test-jit-02-funcs-vararg COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/02funcs_vararg.ts")
tslang_add_test(NAME test-jit-00-funcs-bindings COMMAND test-runner -jit "${PROJECT_SOURCE_DIR}/test/tester/tests/00funcs_bindings.ts")
Expand Down Expand Up @@ -1659,6 +1661,7 @@ set(TSLANG_CORPUS
00funcs_vararg.ts
00function_overloads.ts
00gc_allocations_distinct.ts
00assert_message.ts
00funcs.ts
00generator_manual_next.ts
00generator_manual_next2.ts
Expand Down Expand Up @@ -2535,6 +2538,19 @@ set_tests_properties(test-compile-specialization-cast-pair-error
PROPERTIES PASS_REGULAR_EXPRESSION "is not assignable to type Pair<!ts.number,!ts.string>"
FAIL_REGULAR_EXPRESSION "Stack dump|Assertion failed")

# A failing assert shows a message known only at run time, after the output printed before it.
# Windows only: that is where the JIT reports a failed assert; on Linux abort() reaches LLVM's
# crash handler.
if (WIN32)
add_test(NAME test-jit-assert-message-failing
COMMAND $<TARGET_FILE:tslang> --emit=jit --no-default-lib
--shared-libs=$<TARGET_FILE:TypeScriptRuntime>
"${PROJECT_SOURCE_DIR}/test/tester/assert-message/failing.ts")
set_tests_properties(test-jit-assert-message-failing
PROPERTIES PASS_REGULAR_EXPRESSION "printed before.*assertion failed: n is 7"
FAIL_REGULAR_EXPRESSION "Stack dump|unlinked")
endif()

# The shared-component tier under the other two models. A shared library records the model
# it was built under, so both halves of a pair are built with the same flag - which is what
# these run. The file pairs are the default model's, verbatim.
Expand Down
7 changes: 7 additions & 0 deletions tslang/test/tester/assert-message/failing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// A failing assert shows its message as it is at run time, after what the program printed
// before it: abort flushes nothing, and that output was lost whenever stdout was a pipe.
function main() {
print("printed before");
const n = 7;
assert(n == 3, "n is " + n);
}
19 changes: 19 additions & 0 deletions tslang/test/tester/tests/00assert_message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// An assert message known only at run time used to fail to compile ("operation's operand is
// unlinked"): the message was erased as if it were a constant.
function check(ok: boolean, what: string) {
assert(ok, what + ": failed");
}

function main() {
check(true, "check");

const n = 3;
assert(n == 3, `n is ${n}`);

let none: string | null = null;
assert(true, none);

assert(n > 0, n);

print("done.");
}
5 changes: 5 additions & 0 deletions tslang/tslang/jit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,9 @@ static void *jitSearchForAddressOfSymbol(const char *symbolName)
// instead. The lowering passes the source position along, so this says more than the box did.
static void jitAssertFailed(const char *message, const char *file, unsigned line)
{
// what the program printed before failing comes first, and _exit below flushes nothing
fflush(stdout);

if (file != nullptr && *file != '\0')
{
fprintf(stderr, "%s:%u: ", file, line);
Expand Down Expand Up @@ -641,6 +644,7 @@ int runJit(int argc, char **argv, mlir::ModuleOp module, CompileOptions &compile
addSym("calloc", (void*)&calloc);
addSym("memset", (void*)&memset);
addSym("memcpy", (void*)&memcpy);
addSym("fflush", (void*)&fflush);
// see jitAssertFailed above: bound to ucrtbase this is a modal message box, which an
// unattended run cannot answer
addSym("_assert", (void*)&jitAssertFailed);
Expand Down Expand Up @@ -874,6 +878,7 @@ int runJit(int argc, char **argv, mlir::ModuleOp module, CompileOptions &compile
addOverride("calloc", (void *)&calloc);
addOverride("memset", (void *)&memset);
addOverride("memcpy", (void *)&memcpy);
addOverride("fflush", (void *)&fflush);
// see jitAssertFailed above: bound to ucrtbase this is a modal message box, which an
// unattended run cannot answer
addOverride("_assert", (void *)&jitAssertFailed);
Expand Down
Loading