Skip to content

Commit b921416

Browse files
committed
Merge Windows native runtime fixes
2 parents 9d8a757 + e9f1835 commit b921416

17 files changed

Lines changed: 163 additions & 70 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ if(NOT EMSCRIPTEN)
480480
target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json)
481481
dynlex_link_llvm(${PROJECT_NAME} ${DYNLEX_LLVM_NATIVE_COMPONENTS})
482482
if(WIN32)
483-
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 shell32 ole32)
483+
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 shell32 ole32 uuid)
484484
endif()
485485
set_target_properties (${PROJECT_NAME} PROPERTIES
486486
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}
@@ -513,6 +513,7 @@ if(NOT EMSCRIPTEN)
513513
add_executable(
514514
dynlex_native_target_test EXCLUDE_FROM_ALL
515515
tests/cpp/nativeTargetTest.cpp
516+
src/cpp/compiler/codegen/nativeLibraries.cpp
516517
src/cpp/compiler/codegen/nativeTarget.cpp
517518
)
518519
target_include_directories(dynlex_native_target_test PRIVATE src/cpp)

scripts/test.sh

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ normalize_diagnostics() {
109109
printf "%s" "$1" | python3 -B "$SCRIPT_DIR/diagnostic_expectations.py" "${arguments[@]}"
110110
}
111111

112+
render_output_difference() {
113+
diff -u \
114+
--label expected \
115+
--label actual \
116+
<(printf "%s\n" "$1") \
117+
<(printf "%s\n" "$2") | head -n 40 || true
118+
}
119+
112120
run_with_timeout() {
113121
local seconds="$1"
114122
shift
@@ -274,6 +282,8 @@ if process.returncode < 0:
274282
sys.exit(125)
275283
if process.returncode > 255:
276284
status = process.returncode & 0xFFFFFFFF
285+
if status == 0xC0000409:
286+
sys.exit(134)
277287
sys.stderr.write(f"Process terminated with Windows status 0x{status:08X}\n")
278288
sys.exit(125)
279289
sys.exit(process.returncode)
@@ -467,7 +477,7 @@ for test_dir in "$TESTS_DIR"/*/; do
467477
case "$expected_runtime_failure" in
468478
abort)
469479
if [[ "$is_windows" == "true" ]]; then
470-
expected_run_exit=3
480+
expected_run_exit=134
471481
else
472482
expected_run_exit=$(python3 -c 'import signal; print(128 + signal.SIGABRT)')
473483
fi
@@ -519,8 +529,7 @@ for test_dir in "$TESTS_DIR"/*/; do
519529
else
520530
test_elapsed_ms=$(elapsed_ms_since "$test_start_ms")
521531
append_test_result "FAIL" "$RED" "$test_name" "output mismatch" "$test_elapsed_ms"
522-
test_output+=" Expected: $(head -c 200 <<< "$expected_output")\n"
523-
test_output+=" Actual: $(head -c 200 <<< "$actual_output")\n"
532+
test_output+="$(render_output_difference "$expected_output" "$normalized_actual_output")\n"
524533
((failed++))
525534
failures+=("$test_name")
526535
fi

scripts/test_path_host_runtime.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def verify_windows_cache_directory(project: Path) -> None:
7070
missing_source = [text for text in required_source if text not in source]
7171
if missing_source:
7272
raise RuntimeError(f"Windows user cache directory API is incomplete: {missing_source}")
73-
required_libraries = ("shell32", "ole32")
73+
required_libraries = ("shell32", "ole32", "uuid")
7474
missing_libraries = [library for library in required_libraries if library not in cmake]
7575
if missing_libraries:
7676
raise RuntimeError(f"Windows user cache directory libraries are not linked: {missing_libraries}")

src/cpp/compiler/codegen/native.cpp

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "native.h"
2+
#include "nativeLibraries.h"
23
#include "nativeTarget.h"
34
#include "targetOptions.h"
45
#include "llvm/ADT/ArrayRef.h"
@@ -33,11 +34,6 @@
3334

3435
namespace {
3536

36-
struct LibraryNameMapping {
37-
llvm::StringLiteral portableName;
38-
llvm::StringLiteral linkerName;
39-
};
40-
4137
struct ProgramExecutionResult {
4238
int exitCode = 0;
4339
bool executionFailed = false;
@@ -229,32 +225,6 @@ executeProgramAndCapture(llvm::StringRef program, llvm::ArrayRef<llvm::StringRef
229225
return result;
230226
}
231227

232-
std::vector<std::string> nativeLibraryArguments(const llvm::Triple &targetTriple, llvm::StringRef library) {
233-
if (library == "dynlex_runtime") {
234-
std::vector<std::string> arguments = {runtimeLibraryPath().string()};
235-
if (!targetTriple.isOSWindows())
236-
arguments.push_back("-pthread");
237-
return arguments;
238-
}
239-
if (targetTriple.isOSDarwin() && library == "GL")
240-
return {"-framework", "OpenGL"};
241-
242-
if (targetTriple.isOSWindows()) {
243-
static constexpr std::array windowsLibraryNames = {
244-
LibraryNameMapping{"GL", "opengl32"},
245-
LibraryNameMapping{"glfw", "glfw3dll"},
246-
};
247-
for (const LibraryNameMapping &mapping : windowsLibraryNames) {
248-
if (library == mapping.portableName) {
249-
library = mapping.linkerName;
250-
break;
251-
}
252-
}
253-
}
254-
255-
return {"-l" + library.str()};
256-
}
257-
258228
bool requiresExternalRuntimeLibraries(const std::unordered_set<std::string> &libraries) {
259229
return libraries.contains("glfw") || libraries.contains("freetype");
260230
}
@@ -515,7 +485,7 @@ bool emitNativeExecutable(ParseContext &context) {
515485
commandStorage.push_back("-g");
516486

517487
for (const std::string &lib : context.requiredLibraries) {
518-
std::vector<std::string> arguments = nativeLibraryArguments(parsedTargetTriple, lib);
488+
std::vector<std::string> arguments = nativeLibraryArguments(parsedTargetTriple, lib, runtimeLibraryPath().string());
519489
commandStorage.insert(
520490
commandStorage.end(), std::make_move_iterator(arguments.begin()), std::make_move_iterator(arguments.end())
521491
);
@@ -546,7 +516,9 @@ bool emitNativeExecutable(ParseContext &context) {
546516
// Check which libraries are actually missing
547517
std::vector<std::string> missingLibs;
548518
for (const std::string &lib : context.requiredLibraries) {
549-
if (linkerReportsMissingLibrary(linkExecution->output, nativeLibraryArguments(parsedTargetTriple, lib))) {
519+
if (linkerReportsMissingLibrary(
520+
linkExecution->output, nativeLibraryArguments(parsedTargetTriple, lib, runtimeLibraryPath().string())
521+
)) {
550522
missingLibs.push_back(lib);
551523
}
552524
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include "nativeLibraries.h"
2+
#include <array>
3+
4+
namespace {
5+
6+
struct LibraryNameMapping {
7+
llvm::StringLiteral portableName;
8+
llvm::StringLiteral linkerName;
9+
};
10+
11+
} // namespace
12+
13+
std::vector<std::string>
14+
nativeLibraryArguments(const llvm::Triple &targetTriple, llvm::StringRef library, llvm::StringRef runtimeLibraryPath) {
15+
if (library == "dynlex_runtime") {
16+
std::vector<std::string> arguments = {runtimeLibraryPath.str()};
17+
if (targetTriple.isOSWindows()) {
18+
arguments.push_back("-lshell32");
19+
arguments.push_back("-lole32");
20+
arguments.push_back("-luuid");
21+
} else {
22+
arguments.push_back("-pthread");
23+
}
24+
return arguments;
25+
}
26+
if (targetTriple.isOSDarwin() && library == "GL")
27+
return {"-framework", "OpenGL"};
28+
29+
if (targetTriple.isOSWindows()) {
30+
static constexpr std::array windowsLibraryNames = {
31+
LibraryNameMapping{"GL", "opengl32"},
32+
LibraryNameMapping{"glfw", "glfw3dll"},
33+
};
34+
for (const LibraryNameMapping &mapping : windowsLibraryNames) {
35+
if (library == mapping.portableName) {
36+
library = mapping.linkerName;
37+
break;
38+
}
39+
}
40+
}
41+
42+
return {"-l" + library.str()};
43+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#pragma once
2+
3+
#include "llvm/ADT/StringRef.h"
4+
#include "llvm/TargetParser/Triple.h"
5+
#include <string>
6+
#include <vector>
7+
8+
std::vector<std::string>
9+
nativeLibraryArguments(const llvm::Triple &targetTriple, llvm::StringRef library, llvm::StringRef runtimeLibraryPath);

src/runtime/filesystemRuntimeWindows.c

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "runtimeError.h"
44

55
#include <errno.h>
6+
#include <fcntl.h>
67
#include <io.h>
78
#include <limits.h>
89
#include <stdint.h>
@@ -152,20 +153,52 @@ FILE *dynlex_platform_filesystem_open_file(const char *path, size_t path_length,
152153
if (prepared == NULL)
153154
return NULL;
154155
trim_trailing_separators(prepared);
155-
const wchar_t *mode_text = mode == DYNLEX_FILESYSTEM_OPEN_READ ? L"rb"
156-
: mode == DYNLEX_FILESYSTEM_OPEN_WRITE ? L"wb"
157-
: mode == DYNLEX_FILESYSTEM_OPEN_APPEND ? L"ab"
158-
: NULL;
159-
if (mode_text == NULL) {
156+
const char *mode_text = NULL;
157+
DWORD access = 0;
158+
DWORD creation = 0;
159+
int descriptor_flags = O_BINARY;
160+
if (mode == DYNLEX_FILESYSTEM_OPEN_READ) {
161+
mode_text = "rb";
162+
access = GENERIC_READ;
163+
creation = OPEN_EXISTING;
164+
descriptor_flags |= O_RDONLY;
165+
} else if (mode == DYNLEX_FILESYSTEM_OPEN_WRITE) {
166+
mode_text = "wb";
167+
access = GENERIC_WRITE;
168+
creation = CREATE_ALWAYS;
169+
descriptor_flags |= O_WRONLY;
170+
} else if (mode == DYNLEX_FILESYSTEM_OPEN_APPEND) {
171+
mode_text = "ab";
172+
access = GENERIC_WRITE;
173+
creation = OPEN_ALWAYS;
174+
descriptor_flags |= O_WRONLY | O_APPEND;
175+
} else {
160176
free(prepared);
161177
dynlex_runtime_set_error("Invalid filesystem open mode");
162178
return NULL;
163179
}
164-
FILE *file = NULL;
165-
errno_t open_result = _wfopen_s(&file, prepared, mode_text);
180+
HANDLE handle = CreateFileW(
181+
prepared, access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL
182+
);
166183
free(prepared);
167-
if (open_result != 0 || file == NULL)
168-
dynlex_runtime_set_errno_error("Could not open file", open_result == 0 ? EIO : open_result);
184+
if (handle == INVALID_HANDLE_VALUE) {
185+
dynlex_runtime_set_windows_error("Could not open file", GetLastError());
186+
return NULL;
187+
}
188+
int descriptor = _open_osfhandle((intptr_t)handle, descriptor_flags);
189+
if (descriptor == -1) {
190+
int error_number = errno;
191+
CloseHandle(handle);
192+
dynlex_runtime_set_errno_error("Could not create file descriptor", error_number);
193+
return NULL;
194+
}
195+
FILE *file = _fdopen(descriptor, mode_text);
196+
if (file == NULL) {
197+
int error_number = errno;
198+
_close(descriptor);
199+
dynlex_runtime_set_errno_error("Could not create file stream", error_number);
200+
return NULL;
201+
}
169202
return file;
170203
}
171204

tests/cpp/nativeTargetTest.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#include "compiler/codegen/nativeTarget.h"
2+
#include "compiler/codegen/nativeLibraries.h"
23
#include <cstdlib>
34
#include <iostream>
5+
#include <string>
46
#include <string_view>
7+
#include <vector>
58

69
namespace {
710

@@ -25,5 +28,18 @@ int main() {
2528
expectWindowsObjectTarget("x86_64-w64-mingw32", "x86_64-w64-windows-gnu");
2629
expectWindowsObjectTarget("aarch64-w64-mingw32", "aarch64-w64-windows-gnu");
2730
expectWindowsObjectTarget("x86_64-w64-windows-gnu", "x86_64-w64-windows-gnu");
31+
32+
const llvm::Triple windows = normalizedNativeTargetTriple("x86_64-w64-mingw32");
33+
expect(
34+
nativeLibraryArguments(windows, "dynlex_runtime", "runtime.a") ==
35+
std::vector<std::string>({"runtime.a", "-lshell32", "-lole32", "-luuid"}),
36+
"Windows runtime system libraries were not linked after the runtime archive"
37+
);
38+
const llvm::Triple linuxTarget("x86_64-unknown-linux-gnu");
39+
expect(
40+
nativeLibraryArguments(linuxTarget, "dynlex_runtime", "runtime.a") ==
41+
std::vector<std::string>({"runtime.a", "-pthread"}),
42+
"POSIX runtime thread support was not linked"
43+
);
2844
return 0;
2945
}

tests/required/lsp_client_lifecycle/main.dl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ function the LSP lifecycle test interpreter:
44
execute:
55
return "python" if the process platform is Windows, else "python3"
66

7-
set script to "import json,sys\ndef read():\n h={}\n while True:\n line=sys.stdin.buffer.readline()\n if line==b'\\r\\n': break\n n,v=line.decode('ascii').split(':',1);h[n.lower()]=v.strip()\n return json.loads(sys.stdin.buffer.read(int(h['content-length'])))\ndef send(m):\n p=json.dumps(m,separators=(',',':'),ensure_ascii=False).encode();sys.stdout.buffer.write(b'Content-Length: '+str(len(p)).encode()+b'\\r\\n\\r\\n'+p);sys.stdout.buffer.flush()\ninit=read();assert init['method']=='initialize' and init['params']['rootUri']=='file:///workspace';assert init['params']['clientInfo']=={'name':'DynLex'};assert init['params']['capabilities']['general']['positionEncodings']==['utf-16']\nsend({'jsonrpc':'2.0','id':init['id'],'result':{'capabilities':{'positionEncoding':'utf-16','textDocumentSync':{'openClose':True,'change':2,'save':{'includeText':True}},'workspace':{'workspaceFolders':{'supported':True}}},'serverInfo':{'name':'fake'}}})\nassert read()['method']=='initialized'\nopened=read();td=opened['params']['textDocument'];assert opened['method']=='textDocument/didOpen' and td['version']==1 and td['text']=='a😀b\\nline😀'\nchanged=read();assert changed['method']=='textDocument/didChange';assert changed['params']['textDocument']['version']==2;change=changed['params']['contentChanges'][0];assert change['range']=={'start':{'line':0,'character':1},'end':{'line':0,'character':3}} and change['text']=='X'\nreplaced=read();assert replaced['method']=='textDocument/didChange';assert replaced['params']['textDocument']['version']==3;change=replaced['params']['contentChanges'][0];assert change['range']=={'start':{'line':0,'character':0},'end':{'line':1,'character':6}} and change['text']=='final'\nsaved=read();assert saved['method']=='textDocument/didSave' and saved['params']=={'textDocument':{'uri':'file:///workspace/main.dl'},'text':'final'}\nrelated={'location':{'uri':'file:///workspace/other.dl','range':{'start':{'line':1,'character':2},'end':{'line':1,'character':3}}},'message':'related'}\ndiagnostic={'range':{'start':{'line':0,'character':0},'end':{'line':0,'character':1}},'severity':2,'code':7,'codeDescription':{'href':'https://example.invalid/7'},'source':'fake','message':'sample','tags':[1,2],'relatedInformation':[related],'data':{'value':True}}\nsys.stderr.write('first-server-stderr\\n');sys.stderr.flush()\nsend({'jsonrpc':'2.0','method':'textDocument/publishDiagnostics','params':{'uri':'file:///workspace/main.dl','version':3,'diagnostics':[diagnostic]}})\nsend({'jsonrpc':'2.0','id':9007199254740993,'method':'workspace/applyEdit','params':{'edit':{'changes':{'file:///workspace/main.dl':[]}}}})\nreply=read();assert reply['id']==9007199254740993 and reply['error']['code']==-32601\nclosed=read();assert closed['method']=='textDocument/didClose' and closed['params']['textDocument']['uri']=='file:///workspace/main.dl'\nshutdown=read();assert shutdown['method']=='shutdown' and 'params' not in shutdown\nsend({'jsonrpc':'2.0','id':shutdown['id'],'result':None})\nassert read()['method']=='exit'\nsys.stderr.write('final-server-stderr\\n');sys.stderr.flush()"
7+
set script to "import json,sys\ndef read():\n h={}\n while True:\n line=sys.stdin.buffer.readline()\n if line==b'\\r\\n': break\n n,v=line.decode('ascii').split(':',1);h[n.lower()]=v.strip()\n return json.loads(sys.stdin.buffer.read(int(h['content-length'])))\ndef send(m):\n p=json.dumps(m,separators=(',',':'),ensure_ascii=False).encode();sys.stdout.buffer.write(b'Content-Length: '+str(len(p)).encode()+b'\\r\\n\\r\\n'+p);sys.stdout.buffer.flush()\ninit=read();assert init['method']=='initialize' and init['params']['rootUri']=='file:///workspace';assert init['params']['clientInfo']=={'name':'DynLex'};assert init['params']['capabilities']['general']['positionEncodings']==['utf-16']\nsend({'jsonrpc':'2.0','id':init['id'],'result':{'capabilities':{'positionEncoding':'utf-16','textDocumentSync':{'openClose':True,'change':2,'save':{'includeText':True}},'workspace':{'workspaceFolders':{'supported':True}}},'serverInfo':{'name':'fake'}}})\nassert read()['method']=='initialized'\nopened=read();td=opened['params']['textDocument'];assert opened['method']=='textDocument/didOpen' and td['version']==1 and td['text']=='a😀b\\nline😀'\nchanged=read();assert changed['method']=='textDocument/didChange';assert changed['params']['textDocument']['version']==2;change=changed['params']['contentChanges'][0];assert change['range']=={'start':{'line':0,'character':1},'end':{'line':0,'character':3}} and change['text']=='X'\nreplaced=read();assert replaced['method']=='textDocument/didChange';assert replaced['params']['textDocument']['version']==3;change=replaced['params']['contentChanges'][0];assert change['range']=={'start':{'line':0,'character':0},'end':{'line':1,'character':6}} and change['text']=='final'\nsaved=read();assert saved['method']=='textDocument/didSave' and saved['params']=={'textDocument':{'uri':'file:///workspace/main.dl'},'text':'final'}\nrelated={'location':{'uri':'file:///workspace/other.dl','range':{'start':{'line':1,'character':2},'end':{'line':1,'character':3}}},'message':'related'}\ndiagnostic={'range':{'start':{'line':0,'character':0},'end':{'line':0,'character':1}},'severity':2,'code':7,'codeDescription':{'href':'https://example.invalid/7'},'source':'fake','message':'sample','tags':[1,2],'relatedInformation':[related],'data':{'value':True}}\nsys.stderr.buffer.write(b'first-server-stderr\\n');sys.stderr.buffer.flush()\nsend({'jsonrpc':'2.0','method':'textDocument/publishDiagnostics','params':{'uri':'file:///workspace/main.dl','version':3,'diagnostics':[diagnostic]}})\nsend({'jsonrpc':'2.0','id':9007199254740993,'method':'workspace/applyEdit','params':{'edit':{'changes':{'file:///workspace/main.dl':[]}}}})\nreply=read();assert reply['id']==9007199254740993 and reply['error']['code']==-32601\nclosed=read();assert closed['method']=='textDocument/didClose' and closed['params']['textDocument']['uri']=='file:///workspace/main.dl'\nshutdown=read();assert shutdown['method']=='shutdown' and 'params' not in shutdown\nsend({'jsonrpc':'2.0','id':shutdown['id'],'result':None})\nassert read()['method']=='exit'\nsys.stderr.buffer.write(b'final-server-stderr\\n');sys.stderr.buffer.flush()"
88
set command to a process command for the LSP lifecycle test interpreter
99
add "-u" as an argument to command
1010
add "-c" as an argument to command

tests/required/process_communication_error/main.dl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ loop while position < length:
1414
set command to a process command for the communication error test interpreter
1515
add "-u" as an argument to command
1616
add "-c" as an argument to command
17-
add "import os,sys; print('before close'); print('input rejected', file=sys.stderr); os.close(0); sys.exit(9)" as an argument to command
17+
add "import os,sys; sys.stdout.buffer.write(b'before close\\n'); sys.stdout.buffer.flush(); sys.stderr.buffer.write(b'input rejected\\n'); sys.stderr.buffer.flush(); os.close(0); sys.exit(9)" as an argument to command
1818
run command using input and set result to it
1919

2020
print whether result was launched as a line

0 commit comments

Comments
 (0)