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
10 changes: 10 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2219,6 +2219,16 @@ add_test(NAME test-compile-debug-info-opt
-DTESTS_DIR=${PROJECT_SOURCE_DIR}/test/tester/tests
-P ${CMAKE_CURRENT_SOURCE_DIR}/debug-info-opt.cmake)

# A throw under the JIT without TypeScriptRuntime (which test-runner always passes), under every
# model but gc - see jit-throw-without-runtime.cmake. Windows only: the symbol is MSVC's type_info vtable.
if (WIN32)
add_test(NAME test-jit-throw-without-runtime
COMMAND ${CMAKE_COMMAND}
-DTSLANG=$<TARGET_FILE:tslang>
-DTESTS_DIR=${PROJECT_SOURCE_DIR}/test/tester/tests
-P ${CMAKE_CURRENT_SOURCE_DIR}/jit-throw-without-runtime.cmake)
endif()

# tslang choosing the collector's linkage itself under -mm=gc - see shared-collector-auto.cmake.
if (WIN32)
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release")
Expand Down
39 changes: 39 additions & 0 deletions tslang/test/tester/jit-throw-without-runtime.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Runs a program that throws under the JIT with NO --shared-libs, under every model but gc.
#
# `test-runner` always passes `--shared-libs=TypeScriptRuntime.dll`, and that DLL exports type_info's
# vtable (??_7type_info@@6B@), which every type descriptor a throw emits points at. A JIT run loads
# the DLL by itself only under gc; under rc, none and own without it, nothing in the process
# exported the vtable, and any program with a `throw` failed to materialize: "Symbols not found:
# [ ??_7type_info@@6B@ ]". The JIT now binds it itself (see jitTypeInfoVftable in jit.cpp).

if(NOT DEFINED TSLANG OR NOT DEFINED TESTS_DIR)
message(FATAL_ERROR "TSLANG and TESTS_DIR are both required")
endif()

set(name 00jit_throw_without_runtime.ts)

set(failures "")
foreach(model rc none own)
execute_process(
COMMAND "${TSLANG}" --emit=jit -mm=${model} --no-default-lib "${TESTS_DIR}/${name}"
OUTPUT_VARIABLE output
ERROR_VARIABLE diagnostics
RESULT_VARIABLE status)

if(NOT status EQUAL 0 OR NOT output MATCHES "ALL DONE")
# the first error, not a warning printed ahead of it
if(diagnostics MATCHES "[^\n]*error:[^\n]*")
set(first_line "${CMAKE_MATCH_0}")
else()
string(REGEX REPLACE "\n.*" "" first_line "${diagnostics}")
endif()
list(APPEND failures " -mm=${model}: exit ${status}: ${first_line}")
endif()
endforeach()

if(failures)
string(REPLACE ";" "\n" report "${failures}")
message(FATAL_ERROR "a throw under the JIT without TypeScriptRuntime:\n${report}")
endif()

message(STATUS "a throw under the JIT without TypeScriptRuntime runs under rc, none and own")
28 changes: 28 additions & 0 deletions tslang/test/tester/tests/00jit_throw_without_runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Run by jit-throw-without-runtime.cmake under the JIT with no --shared-libs: every throw needs
// type_info's vtable, which only TypeScriptRuntime.dll exported, and the JIT loads that for gc alone.
class Failure {
constructor(public code: number) {}
}

function throwNumber() {
throw 1;
}

function throwString() {
throw "text";
}

function throwClass() {
throw new Failure(3);
}

function main() {
let caught = 0;

try { throwNumber(); } catch (e) { caught++; }
try { throwString(); } catch (e) { caught++; }
try { throwClass(); } catch (e) { caught++; }

assert(caught == 3, "every throw is caught");
print("ALL DONE");
}
14 changes: 14 additions & 0 deletions tslang/tslang/jit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <cstdio>
#include <cstdlib>
#include <deque>
#include <typeinfo>
#ifdef _WIN32
#include <process.h>
#include <windows.h>
Expand Down Expand Up @@ -434,6 +435,16 @@ static void jitCxxThrowException(void *exceptionObject, void *throwInfo)
// exception personality so throw and catch sides use the same CRT
extern "C" EXCEPTION_DISPOSITION __CxxFrameHandler3(struct _EXCEPTION_RECORD *, void *, struct _CONTEXT *,
struct _DISPATCHER_CONTEXT *);

// The vtable of type_info (??_7type_info@@6B@), which every type descriptor a throw emits points
// at. tslang.exe links the CRT statically, so no DLL in the process exports it - only
// TypeScriptRuntime.dll, which re-exports its own copy and which the JIT loads for `gc` alone.
// Without it every rc/none/own program that throws failed to materialize. Every type_info object
// starts with this pointer, so take it from one: the static CRT's, like __CxxFrameHandler3's.
static void *jitTypeInfoVftable()
{
return *reinterpret_cast<void *const *>(&typeid(int));
}
#endif

#define JIT_ENTRY_THUNK_NAME "__tslang_jit_main"
Expand Down Expand Up @@ -720,6 +731,7 @@ static int prepareJitProcess(CompileOptions &compileOptions)
// jitCxxThrowException above).
addSym("__CxxFrameHandler3", (void *)&__CxxFrameHandler3);
addSym("_CxxThrowException", (void *)&jitCxxThrowException);
addSym("??_7type_info@@6B@", jitTypeInfoVftable());
#endif
}
#endif
Expand Down Expand Up @@ -879,6 +891,8 @@ static std::unique_ptr<llvm::orc::LLJIT> createJit(llvm::orc::JITTargetMachineBu
// fixes up the throw-site image base (see jitCxxThrowException above)
addOverride("__CxxFrameHandler3", (void *)&__CxxFrameHandler3);
addOverride("_CxxThrowException", (void *)&jitCxxThrowException);
// see jitTypeInfoVftable above
addOverride("??_7type_info@@6B@", jitTypeInfoVftable());
#endif
// The stand-in decided above. It goes here rather than through
// DynamicLibrary::AddSymbol because the process generator resolves from export tables
Expand Down
Loading