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
6 changes: 0 additions & 6 deletions tslang/include/TypeScript/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@
#define TUPLE_TYPE_PACKED false
#define UNION_TYPE_PACKED true

#ifdef WIN32
#define WIN_LOADSHAREDLIBS 1
#else
#define LINUX_LOADSHAREDLIBS 1
#endif

#define USE_NEW_AS_METHOD true
#define ADD_STATIC_MEMBERS_TO_VTABLE true

Expand Down
2 changes: 2 additions & 0 deletions tslang/include/TypeScript/DataStructs.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ struct CompileOptions
}
bool isWasm;
bool isWindows;
// Bionic has its own assert entry points (__assert/__assert2), not glibc's __assert_fail
bool isAndroid = false;
bool isExecutable;
bool isDLL;

Expand Down
47 changes: 30 additions & 17 deletions tslang/include/TypeScript/LowerToLLVM/AssertLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,26 @@ class AssertLogic
LLVMCodeHelperBase ch;
CodeLogicHelper clh;
Location loc;
// the target's CRT decides how a failure is reported, not the host's
bool isWindows;
bool isAndroid;

protected:
mlir::Type sizeType;
mlir::Type typeOfValueType;

public:
AssertLogic(Operation *op, PatternRewriter &rewriter, TypeConverterHelper &tch, Location loc, CompileOptions &compileOptions)
: op(op), rewriter(rewriter), th(rewriter), ch(op, rewriter, tch.typeConverter, compileOptions), clh(op, rewriter), loc(loc)
: op(op), rewriter(rewriter), th(rewriter), ch(op, rewriter, tch.typeConverter, compileOptions), clh(op, rewriter), loc(loc),
isWindows(compileOptions.isWindows), isAndroid(compileOptions.isAndroid)
{
sizeType = th.getIndexType();
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)
: op(op), rewriter(rewriter), th(rewriter), ch(op, rewriter, typeConverter, compileOptions), clh(op, rewriter), loc(loc),
isWindows(compileOptions.isWindows), isAndroid(compileOptions.isAndroid)
{
sizeType = th.getIndexType();
typeOfValueType = th.getPtrType();
Expand All @@ -69,7 +74,7 @@ class AssertLogic
rewriter.setInsertionPointToStart(continuationBlock);
}

// `_assert` and `__assert_fail` abort, which flushes no stream: what the program printed before
// `_assert`, `__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()
{
Expand All @@ -94,7 +99,8 @@ class AssertLogic
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.
// failure (`_assert` on Windows, `__assert` on Android, `__assert_fail` elsewhere) and never
// returns.
mlir::Block *failUnless(mlir::Value condValue, std::string msg, mlir::Value message)
{
auto unreachable = clh.FindUnreachableBlockOrCreate();
Expand All @@ -103,13 +109,6 @@ class AssertLogic
auto [line, column] = lineAndColumn;

auto i8PtrTy = th.getPtrType();
#ifdef WIN32
auto assertFuncOp =
ch.getOrInsertFunction("_assert", th.getFunctionType(th.getVoidType(), {i8PtrTy, i8PtrTy, rewriter.getI32Type()}));
#else
auto assertFuncOp = ch.getOrInsertFunction(
"__assert_fail", th.getFunctionType(th.getVoidType(), {i8PtrTy, i8PtrTy, rewriter.getI32Type(), i8PtrTy}));
#endif

auto *opBlock = rewriter.getInsertionBlock();
auto opPosition = rewriter.getInsertionPoint();
Expand All @@ -131,12 +130,26 @@ class AssertLogic
mlir::Value lineNumberRes = rewriter.create<LLVM::ConstantOp>(loc, rewriter.getI32Type(), rewriter.getI32IntegerAttr(line));

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});
#endif
if (isWindows)
{
auto assertFuncOp =
ch.getOrInsertFunction("_assert", th.getFunctionType(th.getVoidType(), {i8PtrTy, i8PtrTy, rewriter.getI32Type()}));
rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{messageOrConstant(message, msgCst), fileCst, lineNumberRes});
}
else if (isAndroid)
{
// Bionic exports no __assert_fail; __assert(file, line, msg) takes the file first
auto assertFuncOp =
ch.getOrInsertFunction("__assert", th.getFunctionType(th.getVoidType(), {i8PtrTy, rewriter.getI32Type(), i8PtrTy}));
rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{fileCst, lineNumberRes, messageOrConstant(message, msgCst)});
}
else
{
auto assertFuncOp = ch.getOrInsertFunction(
"__assert_fail", th.getFunctionType(th.getVoidType(), {i8PtrTy, i8PtrTy, rewriter.getI32Type(), i8PtrTy}));
mlir::Value funcName = rewriter.create<LLVM::ZeroOp>(loc, i8PtrTy);
rewriter.create<LLVM::CallOp>(loc, assertFuncOp, ValueRange{messageOrConstant(message, msgCst), fileCst, lineNumberRes, funcName});
}
rewriter.create<mlir::cf::BranchOp>(loc, unreachable);

rewriter.setInsertionPointToEnd(opBlock);
Expand Down
9 changes: 3 additions & 6 deletions tslang/lib/TypeScript/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,10 @@ class ConvertFOpLowering : public TsLlvmPattern<mlir_ts::ConvertFOp>
auto llvmI32Type = tch.convertType(th.getI32Type());
auto llvmIndexType = tch.convertType(rewriter.getIndexType());

#ifdef WIN32
// the target's CRT, not the host's: sprintf_s is MSVC only
auto sprintfFuncOp = ch.getOrInsertFunction(
"sprintf_s", th.getFunctionType(rewriter.getI32Type(), {th.getPtrType(), llvmIndexType, th.getPtrType()}, true));
#else
auto sprintfFuncOp = ch.getOrInsertFunction(
"snprintf", th.getFunctionType(rewriter.getI32Type(), {th.getPtrType(), llvmIndexType, th.getPtrType()}, true));
#endif
tsLlvmContext->compileOptions.isWindows ? "sprintf_s" : "snprintf",
th.getFunctionType(rewriter.getI32Type(), {th.getPtrType(), llvmIndexType, th.getPtrType()}, true));

auto bufferSizeValue = transformed.getBufferSize();
auto newStringValue = ch.MemoryAlloc(bufferSizeValue, MemoryAllocSet::Atomic);
Expand Down
7 changes: 2 additions & 5 deletions tslang/lib/TypeScript/MLIRGenClasses.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1291,11 +1291,8 @@ genContext);
if (!newClassPtr->isImport)
{
declarationMode = false;
#ifdef WIN32
varClass.comdat = Select::ExactMatch;
#else
varClass.comdat = Select::Any;
#endif
// decided by the target, not the host: ELF only supports Any (and NoDeduplicate)
varClass.comdat = compileOptions.isWindows ? Select::ExactMatch : Select::Any;
}
else if (newClassPtr->isDeclaration)
{
Expand Down
30 changes: 14 additions & 16 deletions tslang/lib/TypeScript/MLIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1617,27 +1617,25 @@ namespace mlirgen

auto stringVal = valueAttr.getValue();

// The library is named as the target names it, as --emit=dll does (utils.cpp): the path is
// also what the program loads at run time, so a host's naming would not be found there.
auto targetIsWindows = llvm::Triple(compileOptions.moduleTargetTriple).getOS() == llvm::Triple::Win32;

std::string fullPath;
fullPath += stringVal;
#ifdef WIN_LOADSHAREDLIBS
#endif
#ifdef LINUX_LOADSHAREDLIBS
// rebuild file path
auto fileName = sys::path::filename(stringVal);
auto path = stringVal.substr(0, stringVal.size() - fileName.size());
fullPath = path;
fullPath += "lib";
fullPath += fileName;
#endif
if (!targetIsWindows)
{
// rebuild file path
auto fileName = sys::path::filename(stringVal);
auto path = stringVal.substr(0, stringVal.size() - fileName.size());
fullPath = path;
fullPath += "lib";
fullPath += fileName;
}

if (sys::path::extension(fullPath) == "")
{
#ifdef WIN_LOADSHAREDLIBS
fullPath += ".dll";
#endif
#ifdef LINUX_LOADSHAREDLIBS
fullPath += ".so";
#endif
fullPath += targetIsWindows ? ".dll" : ".so";
}

if (sys::fs::exists(fullPath))
Expand Down
8 changes: 8 additions & 0 deletions tslang/test/tester/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2408,6 +2408,14 @@ if (WIN32)
endforeach()
endif()

# The CRT functions a program calls follow its target, not the host - see target-crt.cmake. IR
# only, so every host runs it.
add_test(NAME test-compile-target-crt
COMMAND ${CMAKE_COMMAND}
"-DTSLANG=$<TARGET_FILE:tslang>"
"-DWORK_DIR=${CMAKE_CURRENT_BINARY_DIR}/target-crt"
-P "${CMAKE_CURRENT_SOURCE_DIR}/target-crt.cmake")

# One collector per process with the real default library in it - see defaultlib-collector.cmake.
# Skipped (not failed) when no default library has been built; the release workflows run them after
# building one. The sources live outside tests/: without the default library they do not compile,
Expand Down
15 changes: 12 additions & 3 deletions tslang/test/tester/foreign-target-import.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
# compiled for x86_64 Linux. That target has the host's arch but another OS, so the DLL is read as
# a file, as a PE32+ image. The importer is only compiled, never linked or run.
#
# An import is looked for under the target's name for a library, which is also what the program
# loads at run time: `import './shared'` is ./libshared.so for x86_64 Linux. The x64 DLL is
# therefore written as libshared.so; it is still a PE image inside.
#
# MODE=read the declarations are read: the importer compiles, and uses what they declare.
# MODE=errors a file that is not a PE image, and a DLL for another machine, are refused with an
# error that says so.
Expand Down Expand Up @@ -52,7 +56,7 @@ set(foreign -mtriple=x86_64-unknown-linux-gnu)
if(MODE STREQUAL "read")
run("--emit=dll" "${WORK_DIR}" TRUE
"${TSLANG}" --emit=dll ${common} "--llvm-lib-path=${LLVM_LIB}" "--tslang-lib-path=${TSLANG_LIB}"
"${library}" -o shared.dll)
"${library}" -o libshared.so)

# use_shared.ts calls test1/test2 and prints val_str, which only the DLL's declarations
# declare: without them it does not compile ("can't resolve name").
Expand All @@ -72,15 +76,20 @@ if(MODE STREQUAL "read")
endif()
endforeach()

# ...from the library under the target's name for it
if(NOT ir MATCHES "libshared\\.so\\\\00\"")
message(FATAL_ERROR "the program does not load libshared.so:\n${ir}")
endif()

message(STATUS "declarations read from an x64 DLL's file for a foreign target")
elseif(MODE STREQUAL "errors")
# 1. A file that is not a PE image.
file(MAKE_DIRECTORY "${WORK_DIR}/not-pe")
file(WRITE "${WORK_DIR}/not-pe/foo.dll" "not a DLL\n")
file(WRITE "${WORK_DIR}/not-pe/libfoo.so" "not a DLL\n")
file(WRITE "${WORK_DIR}/not-pe/main.ts" "import './foo'\n\nfunction main() {\n print(\"done.\");\n}\n")
run("importing a non-PE file for a foreign target" "${WORK_DIR}/not-pe" FALSE
"${TSLANG}" --emit=obj ${common} ${foreign} main.ts -o main.o)
if(NOT run_output MATCHES "cannot read declarations from '[^']*foo\\.dll': for a target other than the host, only PE DLLs can be imported")
if(NOT run_output MATCHES "cannot read declarations from '[^']*libfoo\\.so': for a target other than the host, only PE DLLs can be imported")
message(FATAL_ERROR "importing a non-PE file failed for another reason:\n${run_output}")
endif()

Expand Down
70 changes: 70 additions & 0 deletions tslang/test/tester/target-crt.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# The C runtime a program calls into is the target's, not the host's.
#
# A failing assert, a number turned into a string and a class's comdat were once chosen with
# `#ifdef WIN32` in the compiler, so a Windows-hosted tslang gave an Android or Linux object the
# MSVC CRT's `_assert`/`sprintf_s` and an `exactmatch` comdat, which ELF cannot hold. Each triple
# here is compiled to LLVM IR only (never linked or run), from any host, and checked for its own:
#
# x86_64-pc-windows-msvc _assert(msg, file, line), sprintf_s, comdat exactmatch
# x86_64-unknown-linux-gnu __assert_fail(msg, file, line, func), snprintf, comdat any
# x86_64-linux-android __assert(file, line, msg) - Bionic has no __assert_fail -, snprintf,
# comdat any

cmake_minimum_required(VERSION 3.17.3)

foreach(var TSLANG WORK_DIR)
if(NOT DEFINED ${var})
message(FATAL_ERROR "${var} is required")
endif()
endforeach()

file(REMOVE_RECURSE "${WORK_DIR}")
file(MAKE_DIRECTORY "${WORK_DIR}")

file(WRITE "${WORK_DIR}/crt.ts" [=[
class Foo {
x = 1;
}

function main() {
const f = new Foo();
const n = 3.5;
const s = "v" + n;
assert(s.length > f.x, "too short");
print(s);
}
]=])

# check(<triple> <assert decl regex> <sprintf name> <comdat kind> <names that must not be declared...>)
function(check triple assert_decl sprintf_name comdat_kind)
execute_process(COMMAND "${TSLANG}" --emit=llvm -mm=none --no-default-lib -mtriple=${triple} crt.ts -o ${triple}.ll
WORKING_DIRECTORY "${WORK_DIR}"
OUTPUT_VARIABLE out
ERROR_VARIABLE err
RESULT_VARIABLE status)
if(NOT status EQUAL 0)
message(FATAL_ERROR "--emit=llvm for ${triple}: exit ${status}\n${out}\n${err}")
endif()

file(READ "${WORK_DIR}/${triple}.ll" ir)
if(NOT ir MATCHES "declare void @${assert_decl}")
message(FATAL_ERROR "${triple}: a failing assert does not call '${assert_decl}':\n${ir}")
endif()
if(NOT ir MATCHES "declare i32 @${sprintf_name}\\(")
message(FATAL_ERROR "${triple}: a number is not turned into a string with '${sprintf_name}':\n${ir}")
endif()
if(NOT ir MATCHES "\\$Foo\\.\\.size = comdat ${comdat_kind}")
message(FATAL_ERROR "${triple}: the class's comdat is not '${comdat_kind}':\n${ir}")
endif()
foreach(name ${ARGN})
if(ir MATCHES "declare [^\n]*@${name}\\(")
message(FATAL_ERROR "${triple}: declares '${name}', which is another CRT's:\n${ir}")
endif()
endforeach()
endfunction()

check(x86_64-pc-windows-msvc "_assert\\(ptr, ptr, i32\\)" sprintf_s exactmatch __assert_fail __assert snprintf)
check(x86_64-unknown-linux-gnu "__assert_fail\\(ptr, ptr, i32, ptr\\)" snprintf any _assert __assert sprintf_s)
check(x86_64-linux-android "__assert\\(ptr, i32, ptr\\)" snprintf any _assert __assert_fail sprintf_s)

message(STATUS "assert, number-to-string and class comdats follow the target's CRT")
1 change: 1 addition & 0 deletions tslang/tslang/opts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ CompileOptions prepareOptions()
compileOptions.lldbDebugInfo = lldbDebugInfo.getValue();
compileOptions.moduleTargetTriple = moduleTargetTriple;
compileOptions.isWindows = TheTriple.isKnownWindowsMSVCEnvironment();
compileOptions.isAndroid = TheTriple.isAndroid();
compileOptions.isWasm = TheTriple.getArch() == llvm::Triple::wasm64 || TheTriple.getArch() == llvm::Triple::wasm32;
compileOptions.targetInfo = TargetInfo::fromTriple(
TheTriple, llvm::Triple(llvm::sys::getDefaultTargetTriple()));
Expand Down
Loading