From ab93389031b54af4cf62255c3264fe1d86dc87bf Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sun, 18 Jun 2023 22:12:01 +0200 Subject: [PATCH] - Updated posix/win32 stdlib namespacing - Process stdlib - Fix to void expression blocks --- .github/workflows/main.yml | 4 + lib/std/core/env.c3 | 1 + lib/std/io/io_file.c3 | 5 + lib/std/io/os/chdir.c3 | 2 +- lib/std/io/os/file.c3 | 28 +- lib/std/io/os/fileinfo_win32.c3 | 16 +- lib/std/io/os/mkdir.c3 | 4 +- lib/std/io/os/rmdir.c3 | 4 +- lib/std/libc/libc.c3 | 34 ++- lib/std/libc/os/win32.c3 | 10 + lib/std/os/posix/files.c3 | 5 + lib/std/os/posix/general.c3 | 3 + lib/std/os/posix/process.c3 | 49 +++ lib/std/os/subprocess.c3 | 475 +++++++++++++++++++++++++++++ lib/std/os/win32/files.c3 | 48 ++- lib/std/os/win32/general.c3 | 5 +- lib/std/os/win32/process.c3 | 108 ++++++- lib/std/os/win32/types.c3 | 17 ++ lib/std/threads/os/thread_win32.c3 | 179 ++++------- releasenotes.md | 3 + resources/examples/process.c3 | 16 + src/compiler/sema_expr.c | 6 +- 22 files changed, 835 insertions(+), 187 deletions(-) create mode 100644 lib/std/libc/os/win32.c3 create mode 100644 lib/std/os/posix/general.c3 create mode 100644 lib/std/os/posix/process.c3 create mode 100644 lib/std/os/subprocess.c3 create mode 100644 resources/examples/process.c3 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35a0875b8..f021e1a63 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,6 +38,7 @@ jobs: ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\fannkuch-redux.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\contextfree\boolerr.c3 ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\load_world.c3 + ..\build\${{ matrix.build_type }}\c3c.exe compile-run examples\process.c3 - name: Build testproject @@ -254,6 +255,7 @@ jobs: ../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run examples/contextfree/boolerr.c3 ../build/c3c compile-run examples/load_world.c3 + ../build/c3c compile-run examples/process.c3 - name: Compile run unit tests run: | @@ -360,6 +362,7 @@ jobs: ../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run examples/contextfree/boolerr.c3 ../build/c3c compile-run examples/load_world.c3 + ../build/c3c compile-run examples/process.c3 - name: Compile run unit tests run: | @@ -430,6 +433,7 @@ jobs: ../build/c3c compile-run examples/time.c3 ../build/c3c compile-run examples/fannkuch-redux.c3 ../build/c3c compile-run examples/contextfree/boolerr.c3 + ../build/c3c compile-run examples/process.c3 ../build/c3c compile-run examples/load_world.c3 - name: Compile run unit tests diff --git a/lib/std/core/env.c3 b/lib/std/core/env.c3 index c013da824..384d5399e 100644 --- a/lib/std/core/env.c3 +++ b/lib/std/core/env.c3 @@ -138,6 +138,7 @@ const NETBSD_LIBC @builtin = env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == NET const WASI_LIBC @builtin = env::COMPILER_LIBC_AVAILABLE && env::OS_TYPE == WASI; const WASM_NOLIBC @builtin = !env::COMPILER_LIBC_AVAILABLE && env::ARCH_TYPE == ArchType.WASM32 || env::ARCH_TYPE == ArchType.WASM64; const bool NO_LIBC = !env::COMPILER_LIBC_AVAILABLE; +const bool LIBC = env::COMPILER_LIBC_AVAILABLE; const bool WIN32 = OS_TYPE == WIN32; const bool DARWIN = OS_TYPE == IOS || OS_TYPE == MACOS || OS_TYPE == TVOS || OS_TYPE == WATCHOS; const bool LINUX = OS_TYPE == LINUX; diff --git a/lib/std/io/io_file.c3 b/lib/std/io/io_file.c3 index 2298c1930..e3d8227b3 100644 --- a/lib/std/io/io_file.c3 +++ b/lib/std/io/io_file.c3 @@ -11,6 +11,11 @@ fn File! open_path(Path path, String mode) return { .file = os::native_fopen(path.as_str(), mode) }; } +fn File from_libc(CFile file) +{ + return { .file = file }; +} + /** * @require file.file != null **/ diff --git a/lib/std/io/os/chdir.c3 b/lib/std/io/os/chdir.c3 index bee71a546..438ce10dc 100644 --- a/lib/std/io/os/chdir.c3 +++ b/lib/std/io/os/chdir.c3 @@ -21,7 +21,7 @@ macro void! native_chdir(Path path) @pool() { // TODO improve with better error handling. - if (win32::win32_SetCurrentDirectoryW(path.as_str().to_temp_utf16()!!)) return; + if (win32::setCurrentDirectoryW(path.as_str().to_temp_utf16()!!)) return; }; return IoError.GENERAL_ERROR?; $default: diff --git a/lib/std/io/os/file.c3 b/lib/std/io/os/file.c3 index 717a8a332..77876a297 100644 --- a/lib/std/io/os/file.c3 +++ b/lib/std/io/os/file.c3 @@ -29,8 +29,8 @@ fn void*! native_fopen(String filename, String mode) @inline $else @pool() { - $if env::os_is_win32(): - void* file = (CFile)_wfopen(filename.to_temp_utf16(), filename.to_temp_utf16())!; + $if env::WIN32: + void* file = libc::_wfopen(filename.to_temp_utf16(), filename.to_temp_utf16())!; $else void* file = libc::fopen(filename.zstr_tcopy(), mode.zstr_tcopy()); $endif @@ -52,7 +52,7 @@ fn void*! native_freopen(void* file, String filename, String mode) @inline @pool() { $if env::os_is_win32(): - file = _wfreopen(filename.to_temp_utf16(), mode.to_temp_utf16(), file)!; + file = libc::_wfreopen(filename.to_temp_utf16(), mode.to_temp_utf16(), file)!; $else file = libc::freopen(filename.zstr_tcopy(), mode.zstr_tcopy(), file); $endif @@ -67,12 +67,7 @@ fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline if (native_fseek_fn) return native_fseek_fn(file, offset, seek_mode); unreachable("Tried to call fseek without support."); $else - $if env::os_is_win32(): - bool success = _fseeki64(file, (long)offset, (int)seek_mode) == 0; - $else - bool success = libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode) == 0; - $endif - if (!success) return file_seek_errno()?; + if (libc::fseek(file, (SeekIndex)offset, (CInt)seek_mode)) return file_seek_errno()?; $endif } @@ -82,13 +77,8 @@ fn usz! native_ftell(CFile file) @inline if (native_ftell_fn) return native_ftell_fn(file); unreachable("Tried to call ftell without support."); $else - $if env::os_is_win32(): - long index = _ftelli64(file); - return index >= 0 ? index : file_seek_errno()?; - $else - SeekIndex index = libc::ftell(file); - return index >= 0 ? index : file_seek_errno()?; - $endif + long index = libc::ftell(file); + return index >= 0 ? (usz)index : file_seek_errno()?; $endif } @@ -159,11 +149,5 @@ macro anyfault file_seek_errno() @local } } -// Win functions -extern fn void* _wfopen(Char16*, Char16*) @local @if(WIN32_LIBC); -extern fn void* _wfreopen(Char16*, Char16*, CFile) @local @if(WIN32_LIBC); -extern fn int _fseeki64(CFile, long, int) @local @if(WIN32_LIBC); -extern fn long _ftelli64(CFile) @local @if(WIN32_LIBC); - // Posix functions extern fn CInt access(ZString path, CInt mode) @if(POSIX_LIBC); diff --git a/lib/std/io/os/fileinfo_win32.c3 b/lib/std/io/os/fileinfo_win32.c3 index 55d9541ff..81a6c431c 100644 --- a/lib/std/io/os/fileinfo_win32.c3 +++ b/lib/std/io/os/fileinfo_win32.c3 @@ -30,7 +30,7 @@ fn usz! native_file_size(String path) { Char16[] path16 = path.to_temp_utf16()!; Win32_FILE_ATTRIBUTE_DATA data; - win32::win32_GetFileAttributesExW(path16, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data); + win32::getFileAttributesExW(path16, Win32_GET_FILEEX_INFO_LEVELS.STANDARD, &data); Win32_LARGE_INTEGER size; size.lowPart = data.nFileSizeLow; size.highPart = data.nFileSizeHigh; @@ -42,7 +42,7 @@ fn bool native_file_or_dir_exists(String path) { @pool() { - return (bool)win32::win32_PathFileExistsW(path.to_temp_utf16()) ?? false; + return (bool)win32::pathFileExistsW(path.to_temp_utf16()) ?? false; }; } @@ -64,11 +64,11 @@ fn void! native_rmtree(Path path) Win32_WIN32_FIND_DATAW find_data; String s = path.as_str().tconcat("\\*"); - Win32_HANDLE find = win32::win32_FindFirstFileW(s.to_utf16(mem::temp()), &find_data)!; + Win32_HANDLE find = win32::findFirstFileW(s.to_utf16(mem::temp()), &find_data)!; if (find == win32::INVALID_HANDLE_VALUE) return IoError.CANNOT_READ_DIR?; - defer win32::win32_FindClose(find); + defer win32::findClose(find); do { String filename = string::from_zutf16(&find_data.cFileName, mem::temp())!; @@ -80,9 +80,9 @@ fn void! native_rmtree(Path path) } else { - win32::win32_DeleteFileW(file_path.as_str().to_utf16(mem::temp())); + win32::deleteFileW(file_path.as_str().to_utf16(mem::temp())); } - } while (win32::win32_FindNextFileW(find, &find_data) != 0); + } while (win32::findNextFileW(find, &find_data) != 0); os::native_rmdir(path)!; } @@ -90,10 +90,10 @@ fn Path! native_temp_directory(Allocator* using = mem::heap()) { @stack_mem(256; Allocator* mem) { - Win32_DWORD len = win32::win32_GetTempPathW(0, null); + Win32_DWORD len = win32::getTempPathW(0, null); if (!len) return IoError.GENERAL_ERROR?; Char16[] buff = malloc(Char16, len + 1, .using = mem); - if (!win32::win32_GetTempPathW(len, buff)) return IoError.GENERAL_ERROR?; + if (!win32::getTempPathW(len, buff)) return IoError.GENERAL_ERROR?; return path::new(string::from_utf16(buff[:len], .using = mem), using); }; } diff --git a/lib/std/io/os/mkdir.c3 b/lib/std/io/os/mkdir.c3 index 673a79856..1ffa7f05b 100644 --- a/lib/std/io/os/mkdir.c3 +++ b/lib/std/io/os/mkdir.c3 @@ -29,8 +29,8 @@ macro bool! native_mkdir(Path path, MkdirPermissions permissions) @pool() { // TODO security attributes - if (win32::win32_CreateDirectoryW(path.as_str().to_temp_utf16()!!, null)) return true; - switch (win32::win32_GetLastError()) + if (win32::createDirectoryW(path.as_str().to_temp_utf16()!!, null)) return true; + switch (win32::getLastError()) { case win32::ERROR_ACCESS_DENIED: return IoError.NO_PERMISSION?; diff --git a/lib/std/io/os/rmdir.c3 b/lib/std/io/os/rmdir.c3 index f9126a954..76c47d44d 100644 --- a/lib/std/io/os/rmdir.c3 +++ b/lib/std/io/os/rmdir.c3 @@ -26,8 +26,8 @@ macro bool! native_rmdir(Path path) $case WIN32_LIBC: @pool() { - if (win32::win32_RemoveDirectoryW(path.as_str().to_temp_utf16()!!)) return true; - switch (win32::win32_GetLastError()) + if (win32::removeDirectoryW(path.as_str().to_temp_utf16()!!)) return true; + switch (win32::getLastError()) { case win32::ERROR_ACCESS_DENIED: return IoError.NO_PERMISSION?; diff --git a/lib/std/libc/libc.c3 b/lib/std/libc/libc.c3 index 2214851d6..8b0e2e633 100644 --- a/lib/std/libc/libc.c3 +++ b/lib/std/libc/libc.c3 @@ -37,14 +37,18 @@ def JmpBuf = uptr[$$JMP_BUF_SIZE]; module libc @if(env::COMPILER_LIBC_AVAILABLE); +extern fn void abort(); extern fn double atof(char* str); extern fn int atoi(char* str); extern fn CLongLong atoll(char* str); +extern fn CInt close(CInt fd) @if(!env::WIN32); +extern fn CFile fdopen(CInt fd, char* mode) @if(!env::WIN32); +extern fn void atexit(TerminateFunction f); extern fn double strtod(char* str, char** endptr); extern fn CLong strtol(char* str, char** endptr, int base); extern fn CULong stroul(char* str, char** endptr, int base); -extern fn void abort(); -extern fn void atexit(TerminateFunction f); +extern fn long labs(long x); +extern fn LongDivResult ldiv(long number, long denom); extern fn void exit(int status); extern fn ZString getenv(ZString name); extern fn int setenv(ZString name, ZString value, int overwrite); @@ -53,8 +57,6 @@ extern fn int system(char* str); extern fn void bsearch(void* key, void *base, usz items, usz size, CompareFunction compare); extern fn void qsort(void* base, usz items, usz size, CompareFunction compare); extern fn DivResult div(int numer, int denom); -extern fn long labs(long x); -extern fn LongDivResult ldiv(long number, long denom); extern fn int rand(); extern fn void srand(uint seed); @@ -106,9 +108,9 @@ extern fn CFile fopen(ZString filename, ZString mode); extern fn usz fread(void* ptr, usz size, usz nmemb, CFile stream); extern fn CFile freopen(ZString filename, ZString mode, CFile stream); extern fn CFile fmemopen(void* ptr, usz size, ZString mode); -extern fn int fseek(CFile stream, SeekIndex offset, int whence); +extern fn int fseek(CFile stream, SeekIndex offset, int whence) @if(!env::WIN32); extern fn int fsetpos(CFile stream, Fpos* pos); -extern fn SeekIndex ftell(CFile stream); +extern fn SeekIndex ftell(CFile stream) @if(!env::WIN32); extern fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream); extern fn int remove(char* filename); extern fn int rename(char* old_name, char* new_name); @@ -134,10 +136,10 @@ extern fn int puts(char* str); extern fn int ungetc(int c, CFile stream); extern fn void perror(char* str); extern fn isz getline(char** linep, usz* linecapp, CFile stream); - +extern fn CInt fileno(CFile strem) @if(!env::WIN32); extern fn int timespec_get(TimeSpec* ts, int base); extern fn int nanosleep(TimeSpec* req, TimeSpec* remaining); - +extern fn isz read(CInt fd, void* buf, usz nbyte); extern fn ZString asctime(Tm *timeptr); extern fn Clock_t clock(); extern fn ZString ctime(Time_t *timer); @@ -152,6 +154,10 @@ def SignalFunction = fn void(int); extern fn SignalFunction signal(int sig, SignalFunction function); // Incomplete +const CInt STDIN_FD = 0; +const CInt STDOUT_FD = 1; +const CInt STDERR_FD = 2; + module libc @if(LINUX_LIBC); extern CFile __stdin @extern("stdin"); extern CFile __stdout @extern("stdout"); @@ -177,9 +183,9 @@ module libc @if(WIN32_LIBC); extern fn CFile __acrt_iob_func(CInt c); extern fn usz _msize(void* ptr); macro usz malloc_size(void* ptr) => _msize(ptr); -macro CFile stdin() => __acrt_iob_func(0); -macro CFile stdout() => __acrt_iob_func(1); -macro CFile stderr() => __acrt_iob_func(2); +macro CFile stdin() => __acrt_iob_func(STDIN_FD); +macro CFile stdout() => __acrt_iob_func(STDOUT_FD); +macro CFile stderr() => __acrt_iob_func(STDERR_FD); extern fn Tm* _gmtime64_s(Tm* buf, Time_t *timer); extern fn Tm* _localtime64_s(Tm* buf, Time_t *timer); extern fn void _get_timezone(CLong *timezone); @@ -189,9 +195,9 @@ extern fn Time_t mktime(Tm *timeptr) @extern("_mktime64"); extern fn Time_t timegm(Tm *timeptr) @extern("_mkgmtime64"); module libc @if(env::COMPILER_LIBC_AVAILABLE && !env::WIN32 && !env::LINUX && !env::DARWIN); -macro CFile stdin() { return (CFile*)(uptr)0; } -macro CFile stdout() { return (CFile*)(uptr)1; } -macro CFile stderr() { return (CFile*)(uptr)2; } +macro CFile stdin() { return (CFile*)(uptr)STDIN_FD; } +macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; } +macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; } module libc @if(env::COMPILER_LIBC_AVAILABLE && !env::WIN32); extern fn Tm* gmtime_r(Time_t *timer, Tm* buf); diff --git a/lib/std/libc/os/win32.c3 b/lib/std/libc/os/win32.c3 new file mode 100644 index 000000000..2040201d8 --- /dev/null +++ b/lib/std/libc/os/win32.c3 @@ -0,0 +1,10 @@ +module libc @if(env::WIN32); + +extern fn int _fseeki64(CFile, long, int); def fseek = _fseeki64 @if(env::WIN32); +extern fn long _ftelli64(CFile); def ftell = _ftelli64 @if(env::WIN32); +extern fn CFile _wfopen(Char16*, Char16*); +extern fn CFile _wfreopen(Char16*, Char16*, CFile); +extern fn int _close(CInt fd); def close = _close; +extern fn CFile _fdopen(CInt fd, char* mode); def fdopen = _fdopen; +extern fn CInt _fileno(CFile stream); def fileno = _fileno; + diff --git a/lib/std/os/posix/files.c3 b/lib/std/os/posix/files.c3 index 6fc5a86a5..f2c994087 100644 --- a/lib/std/os/posix/files.c3 +++ b/lib/std/os/posix/files.c3 @@ -1,6 +1,11 @@ module std::os::posix @if(POSIX_LIBC); +import libc; extern fn int rmdir(ZString); extern fn int mkdir(ZString, ushort mode_t); extern fn int chdir(ZString); extern fn ZString getcwd(char* pwd, usz len); +def Mode_t = uint; +def Pid_t = int; +extern fn CInt pipe(CInt[2]* pipes); +extern fn CFile fdopen(CInt fd, ZString mode); diff --git a/lib/std/os/posix/general.c3 b/lib/std/os/posix/general.c3 new file mode 100644 index 000000000..571ac9f94 --- /dev/null +++ b/lib/std/os/posix/general.c3 @@ -0,0 +1,3 @@ +module std::os::posix; + +extern ZString* environ; \ No newline at end of file diff --git a/lib/std/os/posix/process.c3 b/lib/std/os/posix/process.c3 new file mode 100644 index 000000000..487dedaae --- /dev/null +++ b/lib/std/os/posix/process.c3 @@ -0,0 +1,49 @@ +module std::os::posix @if(POSIX_LIBC); + +struct Posix_spawn_file_actions_t +{ + int __allocated; + int __used; + void* __actions; + int[16] __pad; +} + +struct Posix_spawnattr_t +{ + void*[42] opaque; +} + +extern fn CInt posix_spawn_file_actions_init(Posix_spawn_file_actions_t *file_actions); +extern fn CInt posix_spawn_file_actions_destroy(Posix_spawn_file_actions_t *file_actions); +extern fn CInt posix_spawn_file_actions_addclose(Posix_spawn_file_actions_t *file_actions, CInt fd); +extern fn CInt posix_spawn_file_actions_adddup2(Posix_spawn_file_actions_t *file_actions, CInt fd, CInt newfd); + +def spawn_file_actions_init = posix_spawn_file_actions_init; +def spawn_file_actions_destroy = posix_spawn_file_actions_destroy; +def spawn_file_actions_addclose = posix_spawn_file_actions_addclose; +def spawn_file_actions_adddup2 = posix_spawn_file_actions_adddup2; + +extern fn CInt posix_spawnp(Pid_t* pid, char* file, Posix_spawn_file_actions_t* file_actions, + Posix_spawnattr_t* attrp, ZString* argv, ZString* envp); +extern fn CInt posix_spawn(Pid_t* pid, char* file, Posix_spawn_file_actions_t* file_actions, + Posix_spawnattr_t* attrp, ZString* argv, ZString* envp); +def spawnp = posix_spawnp; +def spawn = posix_spawn; + +extern fn CInt kill(Pid_t pid, CInt sig); +extern fn Pid_t waitpid(Pid_t pid, CInt* stat_loc, int options); + +macro CInt wEXITSTATUS(CInt status) => (status & 0xff00) >> 8; +macro CInt wTERMSIG(CInt status) => status & 0x7f; +macro CInt wSTOPSIG(CInt status) => wEXITSTATUS(status); +macro bool wIFEXITED(CInt status) => wTERMSIG(status) == 0; +macro bool wIFSIGNALED(CInt status) => ((ichar) ((status & 0x7f) + 1) >> 1) > 0; +macro bool wIFSTOPPED(CInt status) => (status & 0xff) == 0x7f; +macro bool wIFCONTINUED(CInt status) => status == __W_CONTINUED; +macro CInt wWCOREDUMP(CInt status) => status & __WCOREFLAG; +macro CInt w_EXITCODE(CInt ret, CInt sig) => (ret << 8) | sig; +macro CInt w_STOPCODE(CInt sig) => (sig << 8) | 0x7f; +const CInt __WCOREFLAG = 0x80; +const CInt __W_CONTINUED = 0xffff; +const CInt WNOHANG = 1; +const CInt WUNTRACES = 2; diff --git a/lib/std/os/subprocess.c3 b/lib/std/os/subprocess.c3 new file mode 100644 index 000000000..376b529ce --- /dev/null +++ b/lib/std/os/subprocess.c3 @@ -0,0 +1,475 @@ +module std::os::process @if(env::WIN32 || env::POSIX); +import std::io::file; +import libc; + +// This code is based on https://github.com/sheredom/subprocess.h + +fault SubProcessResult +{ + FAILED_TO_CREATE_PIPE, + FAILED_TO_OPEN_STDIN, + FAILED_TO_OPEN_STDOUT, + FAILED_TO_OPEN_STDERR, + FAILED_TO_START_PROCESS, + FAILED_TO_INITIALIZE_ACTIONS, + PROCESS_JOIN_FAILED, + PROCESS_TERMINATION_FAILED, + READ_FAILED, +} + +struct SubProcess +{ + CFile stdin_file; + CFile stdout_file; + CFile stderr_file; + + Win32_HANDLE hProcess @if(env::WIN32); + Win32_HANDLE hStdInput @if(env::WIN32); + Win32_HANDLE hEventOutput @if(env::WIN32); + Win32_HANDLE hEventError @if(env::WIN32); + + Pid_t child @if(!env::WIN32); + int return_status @if(!env::WIN32); + + bool is_alive; +} + +bitstruct SubProcessOptions : int +{ + // Combine stdout and stderr to the same file + bool combined_stdout_stderr; + // Child process should inhert env variables of parent process + bool inherit_environment; + // Enable async reading of stdout/stderr before completion + bool read_async; + // Spawn child process without window if supported + bool no_window; + // Search for program names in the PATH variable. Always enabled on Windows. + // Note: this will **not** search for paths in any provided custom environment + // and instead uses the PATH of the spawning process. + bool search_user_path; +} + +fn void! create_named_pipe_helper(void** rd, void **wr) @local @if(env::WIN32) +{ + Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES.sizeof, null, 1 }; + + tlocal long index = 0; + long unique = index++; + @pool() + { + String s = string::tprintf(`\\.\pipe\c3_subprocess.%08x.%08x.%d`, win32::getCurrentProcessId(), win32::getCurrentThreadId(), unique); + Win32_LPCSTR str = (Win32_LPCSTR)s.ptr; + *rd = win32::createNamedPipeA( + str, + win32::PIPE_ACCESS_INBOUND | win32::FILE_FLAG_OVERLAPPED, + win32::PIPE_TYPE_BYTE | win32::PIPE_WAIT, + 1, 4096, 4096, 0, &sa_attr); + if (win32::INVALID_HANDLE_VALUE == *rd) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + *wr = win32::createFileA( + str, win32::GENERIC_WRITE, 0, &sa_attr, + win32::OPEN_EXISTING, win32::FILE_ATTRIBUTE_NORMAL, null); + if (win32::INVALID_HANDLE_VALUE == *wr) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + }; +} + +fn Char16* convert_command_line_win32(String[] command_line) @inline @if(env::WIN32) @local +{ + DString str; + str.tinit(512); + foreach (i, s : command_line) + { + if (i != 0) str.append(' '); + bool needs_escape = {| + foreach (c : s) + { + switch (c) + { + case '\t': + case ' ': + case '\v': + return true; + } + } + return false; + |}; + if (!needs_escape) + { + str.append(s); + continue; + } + str.append('"'); + foreach (j, c : s) + { + switch (c) + { + case '\\': + if (j != s.len - 1 && s[j + 1] == '"') str.append('\\'); + case '"': + str.append('\\'); + } + str.append(c); + } + str.append('"'); + } + str.append('\0'); + return str.str().to_utf16(.using = mem::temp())!!; +} + +/** + * @require !environment || !options.inherit_environment + **/ +fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::WIN32) +{ + void* rd, wr; + Win32_DWORD flags = win32::CREATE_UNICODE_ENVIRONMENT; + Win32_PROCESS_INFORMATION process_info; + Win32_SECURITY_ATTRIBUTES sa_attr = { Win32_SECURITY_ATTRIBUTES.sizeof, null, 1 }; + Win32_STARTUPINFOW start_info = { + .cb = Win32_STARTUPINFOW.sizeof, + .dwFlags = win32::STARTF_USESTDHANDLES + }; + if (options.no_window) flags |= win32::CREATE_NO_WINDOW; + if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + // TODO defer catch + if (!win32::setHandleInformation(wr, win32::HANDLE_FLAG_INHERIT, 0)) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + + CFile stdin; + CFile stdout; + CFile stderr; + @pool() + { + Char16* used_environment = null; + if (!options.inherit_environment) + { + DString env; + env.tinit(); + if (!environment.len) + { + env.append("\0"); + } + foreach (String s : environment) + { + env.append(s); + env.append("\0"); + } + env.append("\0"); + used_environment = env.str().to_utf16(.using = mem::temp()).ptr!; + } + int fd = win32::_open_osfhandle((iptr)wr, 0); + if (fd != -1) + { + stdin = win32::_fdopen(fd, "wb"); + if (!stdin) return SubProcessResult.FAILED_TO_OPEN_STDIN?; + } + start_info.hStdInput = rd; + if (options.read_async) + { + create_named_pipe_helper(&rd, &wr)!; + } + else + { + if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + } + if (!win32::setHandleInformation(rd, win32::HANDLE_FLAG_INHERIT, 0)) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + fd = win32::_open_osfhandle((iptr)rd, 0); + if (fd != -1) + { + stdout = win32::_fdopen(fd, "rb"); + if (!stdout) return SubProcessResult.FAILED_TO_OPEN_STDOUT?; + } + + start_info.hStdOutput = wr; + + {| + if (options.combined_stdout_stderr) + { + stderr = stdout; + start_info.hStdError = start_info.hStdOutput; + return; + } + if (options.read_async) + { + create_named_pipe_helper(&rd, &wr)!; + } + else + { + if (!win32::createPipe(&rd, &wr, &sa_attr, 0)) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + } + if (!win32::setHandleInformation(rd, win32::HANDLE_FLAG_INHERIT, 0)) return SubProcessResult.FAILED_TO_CREATE_PIPE?; + + fd = win32::_open_osfhandle((iptr)rd, 0); + if (fd != -1) + { + stderr = win32::_fdopen(fd, "rb"); + if (!stderr) return SubProcessResult.FAILED_TO_OPEN_STDERR?; + } + start_info.hStdError = wr; + + |}; + void *event_output; + void *event_error; + if (options.read_async) + { + event_output = win32::createEventA(&sa_attr, 1, 1, null); + event_error = win32::createEventA(&sa_attr, 1, 1, null); + } + if (!win32::createProcessW( + null, + convert_command_line_win32(command_line), + null, // process security attributes + null, // primary thread security attributes + 1, // handles are inherited + flags, // creation flags + used_environment, // environment + null, // use parent dir + &start_info, // startup info ptr + &process_info)) return SubProcessResult.FAILED_TO_START_PROCESS?; + }; + // We don't need the handle of the primary thread in the called process. + win32::closeHandle(process_info.hThread); + if (start_info.hStdOutput) + { + win32::closeHandle(start_info.hStdOutput); + if (start_info.hStdOutput != start_info.hStdError) win32::closeHandle(start_info.hStdError); + } + + return { + .hProcess = process_info.hProcess, + .hStdInput = start_info.hStdInput, + .stdin_file = stdin, + .stdout_file = stdout, + .stderr_file = stderr, + .is_alive = true, + }; +} + +/** + * @require command_line.len > 0 + **/ +fn ZString* tcopy_command_line(String[] command_line) @local @inline @if(env::POSIX) +{ + ZString* copy = tmalloc(ZString, command_line.len + 1); + foreach (i, str : command_line) + { + copy[i] = str.zstr_tcopy(); + } + copy[command_line.len] = null; + return copy; +} + +const ZString[1] EMPTY_ENVIRONMENT @if(env::POSIX) = { null }; +fn ZString* tcopy_env(String[] environment) @local @inline @if(env::POSIX) +{ + if (!environment.len) return &EMPTY_ENVIRONMENT; + ZString* copy = tmalloc(ZString, environment.len + 1); + copy[environment.len] = null; + foreach (i, str : environment) + { + copy[i] = str.zstr_tcopy(); + } + return copy; +} + + +/** + * @require !environment || !options.inherit_environment + **/ +fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::POSIX) +{ + CInt[2] stdinfd; + CInt[2] stdoutfd; + CInt[2] stderrfd; + + if (posix::pipe(&stdinfd)) return SubProcessResult.FAILED_TO_OPEN_STDIN?; + if (posix::pipe(&stdoutfd)) return SubProcessResult.FAILED_TO_OPEN_STDOUT?; + if (!options.combined_stdout_stderr && posix::pipe(&stderrfd)) return SubProcessResult.FAILED_TO_OPEN_STDERR?; + + Posix_spawn_file_actions_t actions; + if (posix::spawn_file_actions_init(&actions)) return SubProcessResult.FAILED_TO_INITIALIZE_ACTIONS?; + defer posix::spawn_file_actions_destroy(&actions); + if (posix::spawn_file_actions_addclose(&actions, stdinfd[1])) return SubProcessResult.FAILED_TO_OPEN_STDIN?; + if (posix::spawn_file_actions_adddup2(&actions, stdinfd[0], libc::STDIN_FD)) return SubProcessResult.FAILED_TO_OPEN_STDIN?; + if (posix::spawn_file_actions_addclose(&actions, stdoutfd[0])) return SubProcessResult.FAILED_TO_OPEN_STDOUT?; + if (posix::spawn_file_actions_adddup2(&actions, stdoutfd[1], libc::STDOUT_FD)) return SubProcessResult.FAILED_TO_OPEN_STDOUT?; + if (options.combined_stdout_stderr) + { + if (posix::spawn_file_actions_adddup2(&actions, libc::STDOUT_FD, libc::STDERR_FD)) return SubProcessResult.FAILED_TO_OPEN_STDERR?; + } + else + { + if (posix::spawn_file_actions_addclose(&actions, stderrfd[0])) return SubProcessResult.FAILED_TO_OPEN_STDERR?; + if (posix::spawn_file_actions_adddup2(&actions, stderrfd[1], libc::STDERR_FD)) return SubProcessResult.FAILED_TO_OPEN_STDERR?; + } + Pid_t child; + @pool() + { + ZString* command_line_copy = tcopy_command_line(command_line); + ZString* used_environment = options.inherit_environment ? posix::environ : tcopy_env(environment); + if (options.search_user_path) + { + if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return SubProcessResult.FAILED_TO_START_PROCESS?; + } + else + { + if (posix::spawnp(&child, command_line_copy[0], &actions, null, command_line_copy, used_environment)) return SubProcessResult.FAILED_TO_START_PROCESS?; + } + }; + libc::close(stdinfd[0]); + CFile stdin = libc::fdopen(stdinfd[1], "wb"); + libc::close(stdoutfd[1]); + CFile stdout = libc::fdopen(stdoutfd[0], "rb"); + CFile stderr = {| + if (options.combined_stdout_stderr) return stdout; + libc::close(stderrfd[1]); + return libc::fdopen(stderrfd[0], "rb"); + |}; + return { + .stdin_file = stdin, + .stdout_file = stdout, + .stderr_file = stderr, + .child = child, + .is_alive = true, + }; +} + +fn CInt! SubProcess.join(SubProcess *this) @if(env::POSIX) +{ + if (this.stdin_file) + { + libc::fclose(this.stdin_file); + this.stdin_file = null; + } + CInt status; + if (this.child && this.child != posix::waitpid(this.child, &status, 0)) return SubProcessResult.PROCESS_JOIN_FAILED?; + + this.child = 0; + this.is_alive = false; + + return this.return_status = posix::wIFEXITED(status) ? posix::wEXITSTATUS(status) : libc::EXIT_FAILURE; +} + +fn File SubProcess.stdout(SubProcess* this) +{ + return file::from_libc(this.stdout_file); +} + +fn CInt! SubProcess.join(SubProcess *this) @if(env::WIN32) +{ + if (this.stdin_file) + { + libc::fclose(this.stdin_file); + this.stdin_file = null; + } + if (this.hStdInput) + { + win32::closeHandle(this.hStdInput); + this.hStdInput = null; + } + win32::waitForSingleObject(this.hProcess, win32::INFINITE); + Win32_DWORD return_code @noinit; + if (!win32::getExitCodeProcess(this.hProcess, &return_code)) return SubProcessResult.PROCESS_JOIN_FAILED?; + this.is_alive = false; + return return_code; +} + +fn bool SubProcess.destroy(SubProcess* this) +{ + if (this.stdin_file) libc::fclose(this.stdin_file); + if (this.stdout_file) + { + libc::fclose(this.stdout_file); + if (this.stdout_file != this.stderr_file) libc::fclose(this.stderr_file); + } + this.stdin_file = this.stdout_file = this.stderr_file = null; + $if env::WIN32: + if (this.hProcess) win32::closeHandle(this.hProcess); + if (this.hStdInput) win32::closeHandle(this.hStdInput); + if (this.hEventOutput) win32::closeHandle(this.hEventOutput); + if (this.hEventError) win32::closeHandle(this.hEventError); + this.hProcess = this.hStdInput = this.hEventOutput = this.hEventError = null; + $endif; + return true; +} + +fn void! SubProcess.terminate(SubProcess* this) +{ + $if env::WIN32: + if (!win32::terminateProcess(this.hProcess, 99)) return SubProcessResult.PROCESS_TERMINATION_FAILED?; + $else + if (posix::kill(this.child, 9)) return SubProcessResult.PROCESS_TERMINATION_FAILED?; + $endif +} + +/** + * @require size <= Win32_DWORD.max + **/ +fn usz! read_from_file_win32(CFile file, Win32_HANDLE event_handle, char* buffer, usz size) @if(env::WIN32) @local +{ + CInt fd = libc::fileno(file); + Win32_DWORD bytes_read = 0; + Win32_OVERLAPPED overlapped = { .hEvent = event_handle }; + Win32_HANDLE handle = (Win32_HANDLE)win32::_get_osfhandle(fd); + if (!win32::readFile(handle, buffer, (Win32_DWORD)size, &bytes_read, &overlapped)) + { + // Means we've got an async read! + if (win32::getLastError() == win32::ERROR_IO_PENDING) + { + if (!win32::getOverlappedResult(handle, &overlapped, &bytes_read, 1)) + { + switch (win32::getLastError()) + { + case win32::ERROR_IO_INCOMPLETE: + case win32::ERROR_HANDLE_EOF: + break; + default: + return SubProcessResult.READ_FAILED?; + } + } + } + } + return bytes_read; +} +fn usz! read_from_file_posix(CFile file, char* buffer, usz size) @if(env::POSIX) @local +{ + isz bytes_read = libc::read(libc::fileno(file), buffer, size); + if (bytes_read < 0) return SubProcessResult.READ_FAILED?; + return bytes_read; +} + +fn usz! SubProcess.read_stdout(SubProcess* this, char* buffer, usz size) +{ + $if env::WIN32: + return read_from_file_win32(this.stdout_file, this.hEventOutput, buffer, size); + $else + return read_from_file_posix(this.stdout_file, buffer, size); + $endif +} + +fn usz! SubProcess.read_stderr(SubProcess* this, char* buffer, usz size) +{ + $if env::WIN32: + return read_from_file_win32(this.stderr_file, this.hEventError, buffer, size); + $else + return read_from_file_posix(this.stderr_file, buffer, size); + $endif +} + +fn bool! SubProcess.is_alive(SubProcess* this) +{ + if (!this.is_alive) return false; + $if env::WIN32: + bool is_alive = win32::waitForSingleObject(this.hProcess, 0) != win32::WAIT_OBJECT_0; + if (!is_alive) this.is_alive = false; + return is_alive; + $else + CInt status; + bool is_alive = posix::waitpid(this.child, &status, posix::WNOHANG) == 0; + if (is_alive) return true; + this.is_alive = false; + this.return_status = posix::wIFEXITED(status) ? posix::wEXITSTATUS(status) : libc::EXIT_FAILURE; + this.child = 0; + this.join()!; + return false; + $endif +} \ No newline at end of file diff --git a/lib/std/os/win32/files.c3 b/lib/std/os/win32/files.c3 index 5e3698d58..f60e9ab76 100644 --- a/lib/std/os/win32/files.c3 +++ b/lib/std/os/win32/files.c3 @@ -1,5 +1,5 @@ module std::os::win32 @if(WIN32_LIBC); - +import libc; enum Win32_GET_FILEEX_INFO_LEVELS { STANDARD, @@ -39,22 +39,44 @@ struct Win32_WIN32_FIND_DATAW def Win32_LPWIN32_FIND_DATAW = Win32_WIN32_FIND_DATAW*; -extern fn Win32_BOOL win32_CloseHandle(Win32_HANDLE) @extern("CloseHandle"); -extern fn Win32_BOOL win32_CreatePipe(Win32_PHANDLE hReadPipe, Win32_PHANDLE hWritePipe, Win32_LPSECURITY_ATTRIBUTES lpPipeAttributes, Win32_DWORD nSize) @extern("CreatePipe"); -extern fn Win32_BOOL win32_GetFileAttributesExW(Win32_LPCWSTR, Win32_GET_FILEEX_INFO_LEVELS, Win32_LPVOID) @extern("GetFileAttributesExW"); -extern fn Win32_BOOL win32_PathFileExistsW(Win32_LPCWSTR) @extern("PathFileExistsW"); -extern fn Win32_DWORD win32_GetTempPathW(Win32_DWORD nBufferLength, Win32_LPWSTR lpBuffer) @extern("GetTempPathW"); -extern fn Win32_BOOL win32_SetCurrentDirectoryW(Win32_LPCTSTR buffer) @extern("SetCurrentDirectoryW"); -extern fn Win32_BOOL win32_RemoveDirectoryW(Win32_LPCWSTR lpPathName) @extern("RemoveDirectoryW"); -extern fn Win32_BOOL win32_CreateDirectoryW(Win32_LPCWSTR lpPathName, Win32_LPSECURITY_ATTRIBUTES lpPipeAttributes) @extern("CreateDirectoryW"); -extern fn Win32_BOOL win32_DeleteFileW(Win32_LPCWSTR lpFileName) @extern("DeleteFileW"); -extern fn Win32_HANDLE win32_FindFirstFileW(Win32_LPCWSTR lpFileName, Win32_LPWIN32_FIND_DATAW lpFindFileData) @extern("FindFirstFileW"); -extern fn Win32_BOOL win32_FindNextFileW(Win32_HANDLE hFindFile, Win32_LPWIN32_FIND_DATAW lpFindFileData) @extern("FindNextFileW"); -extern fn Win32_BOOL win32_FindClose(Win32_HANDLE hFindFile) @extern("FindClose"); +extern fn Win32_BOOL closeHandle(Win32_HANDLE) @extern("CloseHandle"); +extern fn Win32_BOOL createPipe(Win32_PHANDLE hReadPipe, Win32_PHANDLE hWritePipe, Win32_LPSECURITY_ATTRIBUTES lpPipeAttributes, Win32_DWORD nSize) @extern("CreatePipe"); +extern fn Win32_BOOL getFileAttributesExW(Win32_LPCWSTR, Win32_GET_FILEEX_INFO_LEVELS, Win32_LPVOID) @extern("GetFileAttributesExW"); +extern fn Win32_BOOL pathFileExistsW(Win32_LPCWSTR) @extern("PathFileExistsW"); +extern fn Win32_DWORD getTempPathW(Win32_DWORD nBufferLength, Win32_LPWSTR lpBuffer) @extern("GetTempPathW"); +extern fn Win32_BOOL setCurrentDirectoryW(Win32_LPCTSTR buffer) @extern("SetCurrentDirectoryW"); +extern fn Win32_BOOL removeDirectoryW(Win32_LPCWSTR lpPathName) @extern("RemoveDirectoryW"); +extern fn Win32_BOOL createDirectoryW(Win32_LPCWSTR lpPathName, Win32_LPSECURITY_ATTRIBUTES lpPipeAttributes) @extern("CreateDirectoryW"); +extern fn Win32_BOOL deleteFileW(Win32_LPCWSTR lpFileName) @extern("DeleteFileW"); +extern fn Win32_HANDLE findFirstFileW(Win32_LPCWSTR lpFileName, Win32_LPWIN32_FIND_DATAW lpFindFileData) @extern("FindFirstFileW"); +extern fn Win32_BOOL findNextFileW(Win32_HANDLE hFindFile, Win32_LPWIN32_FIND_DATAW lpFindFileData) @extern("FindNextFileW"); +extern fn Win32_BOOL findClose(Win32_HANDLE hFindFile) @extern("FindClose"); + +const Win32_DWORD GENERIC_WRITE = 0x40000000; +const Win32_DWORD OPEN_EXISTING = 3; +const Win32_DWORD FILE_ATTRIBUTE_NORMAL = 0x00000080; + +extern fn Win32_HANDLE createFileA( + Win32_LPCSTR lpFileName, + Win32_DWORD dwDesiredAccess, + Win32_DWORD dwShareMode, + Win32_LPSECURITY_ATTRIBUTES lpSecurityAttributes, + Win32_DWORD dwCreationDisposition, + Win32_DWORD dwFlagsAndAttributes, + Win32_HANDLE hTemplateFile +) @extern("CreateFileA"); +extern fn Win32_BOOL readFile(Win32_HANDLE hFile, Win32_LPVOID lpBuffer, Win32_DWORD nNumberOfBytesToRead, + Win32_LPDWORD lpNumberOfBytesRead, Win32_LPOVERLAPPED lpOverlapped +) @extern("ReadFile"); extern fn Char16* _wgetcwd(Char16* buffer, int maxlen); extern fn usz wcslen(Char16* str); +extern fn int _open_osfhandle(iptr osfhandle, int flags); +extern fn iptr _get_osfhandle(int fd); +extern fn CFile _fdopen(int fd, ZString mode); + + /* extern ulong _win32_GetCurrentDirectoryW(ulong, Char16* buffer) @extern("GetCurrentDirectoryW"); extern bool _win32_CreateSymbolicLinkW(Char16* symlink_file, Char16* target_file, ulong flags) @extern("CreateSymbolicLinkW"); diff --git a/lib/std/os/win32/general.c3 b/lib/std/os/win32/general.c3 index c71fbf9ad..7f1a636cd 100644 --- a/lib/std/os/win32/general.c3 +++ b/lib/std/os/win32/general.c3 @@ -1,6 +1,6 @@ module std::os::win32 @if(WIN32_LIBC); -extern fn Win32_DWORD win32_GetLastError() @extern("GetLastError"); +extern fn Win32_DWORD getLastError() @extern("GetLastError"); const Win32_DWORD ERROR_INVALID_FUNCTION = 0x1; const Win32_DWORD ERROR_FILE_NOT_FOUND = 0x2; @@ -221,4 +221,5 @@ const Win32_DWORD ERROR_DEVICE_FEATURE_NOT_SUPPORTED = 0x13C; const Win32_DWORD ERROR_MR_MID_NOT_FOUND = 0x13D; const Win32_DWORD ERROR_SCOPE_NOT_FOUND = 0x13E; const Win32_DWORD ERROR_UNDEFINED_SCOPE = 0x13F; - +const Win32_DWORD ERROR_IO_INCOMPLETE = 0x3E4; +const Win32_DWORD ERROR_IO_PENDING = 0x3E5; \ No newline at end of file diff --git a/lib/std/os/win32/process.c3 b/lib/std/os/win32/process.c3 index 28e9c5e42..6db272116 100644 --- a/lib/std/os/win32/process.c3 +++ b/lib/std/os/win32/process.c3 @@ -1,6 +1,100 @@ module std::os::win32 @if(WIN32_LIBC); -extern fn bool win32_CreateProcessW( +const Win32_DWORD STARTF_USESTDHANDLES = 0x00000100; +const Win32_DWORD CREATE_NO_WINDOW = 0x08000000; +const Win32_DWORD CREATE_PROTECTED_PROCESS = 0x00040000; +const Win32_DWORD CREATE_UNICODE_ENVIRONMENT = 0x00000400; +const uint WAIT_OBJECT_0 = 0; +const uint WAIT_ABANDONED = 128; +const uint WAIT_IO_COMPLETION = 192; +const uint WAIT_FAILED = (uint)-1; +const Win32_DWORD HANDLE_FLAG_INHERIT = 1; +const Win32_DWORD HANDLE_FLAG_PROTECT_FROM_CLOSE = 2; +const uint INFINITE = (uint)-1; +const Win32_DWORD PIPE_ACCESS_DUPLEX = 0x00000003; +const Win32_DWORD PIPE_ACCESS_INBOUND = 0x00000001; +const Win32_DWORD PIPE_ACCESS_OUTBOUND = 0x00000002; +const Win32_DWORD FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000; +const Win32_DWORD FILE_FLAG_WRITE_THROUGH = 0x80000000; +const Win32_DWORD FILE_FLAG_OVERLAPPED = 0x40000000; +const Win32_DWORD WRITE_DAC = 0x00040000; +const Win32_DWORD WRITE_OWNER = 0x00080000; +const Win32_DWORD ACCESS_SYSTEM_SECURITY = 0x01000000; +const Win32_DWORD PIPE_TYPE_BYTE = 0; +const Win32_DWORD PIPE_TYPE_MESSAGE = 4; +const Win32_DWORD PIPE_READMODE_BYTE = 0; +const Win32_DWORD PIPE_READMODE_MESSAGE = 2; +const Win32_DWORD PIPE_WAIT = 0; +const Win32_DWORD PIPE_NOWAIT = 1; +const Win32_DWORD PIPE_ACCEPT_REMOTE_CLIENTS = 0; +const Win32_DWORD PIPE_REJECT_REMOTE_CLIENTS = 8; + +def NativeThread = Win32_HANDLE; + +struct NativeMutex +{ + union + { + Win32_CRITICAL_SECTION critical_section; + Win32_HANDLE handle; + } + bool already_locked; + bool recursive; + bool timed; +} + +struct NativeOnceFlag +{ + int status; + Win32_CRITICAL_SECTION lock; +} + +struct NativeConditionVariable +{ + union + { + struct + { + Win32_HANDLE event_one; + Win32_HANDLE event_all; + } + Win32_HANDLE[2] events; + } + uint waiters_count; + Win32_CRITICAL_SECTION waiters_count_lock; +} + +extern fn void initializeCriticalSection(Win32_CRITICAL_SECTION* section) @extern("InitializeCriticalSection"); +extern fn void deleteCriticalSection(Win32_CRITICAL_SECTION* section) @extern("DeleteCriticalSection"); +extern fn Win32_HANDLE createMutex(void*, bool, void*) @extern("CreateMutexA"); +extern fn Win32_BOOL releaseMutex(Win32_HANDLE) @extern("ReleaseMutex"); +extern fn void enterCriticalSection(Win32_CRITICAL_SECTION* section) @extern("EnterCriticalSection"); +extern fn void leaveCriticalSection(Win32_CRITICAL_SECTION* section) @extern("LeaveCriticalSection"); +extern fn Win32_BOOL tryEnterCriticalSection(Win32_CRITICAL_SECTION* section) @extern("TryEnterCriticalSection"); +extern fn uint waitForSingleObject(Win32_HANDLE, uint milliseconds) @extern("WaitForSingleObject"); +extern fn void sleep(uint ms) @extern("Sleep"); +extern fn uint waitForMultipleObjects(uint count, Win32_HANDLE* handles, bool wait_all, uint ms) @extern("WaitForMultipleObjects"); +extern fn Win32_BOOL resetEvent(Win32_HANDLE event) @extern("ResetEvent"); +extern fn Win32_BOOL setEvent(Win32_HANDLE handle) @extern("SetEvent"); +extern fn long interlockedCompareExchange(int* dest, int exchange, int comperand) @extern("InterlockedCompareExchange"); +extern fn uint sleepEx(uint ms, bool alertable) @extern("SleepEx"); +extern fn Win32_HANDLE createThread(void* attributes, usz stack, ThreadFn func, void* arg, uint flags, uint* thread_id) @extern("CreateThread"); +extern fn Win32_BOOL getExitCodeThread(Win32_HANDLE handle, uint* exit_code) @extern("GetExitCodeThread"); +extern fn Win32_BOOL getExitCodeProcess(Win32_HANDLE hProcess, Win32_LPDWORD lpExitCode) @extern("GetExitCodeProcess"); +extern fn uint getThreadId(Win32_HANDLE) @extern("GetThreadId"); +extern fn void exitThread(uint res) @noreturn @extern("ExitThread"); +extern fn Win32_HANDLE getCurrentThread() @extern("GetCurrentThread"); +extern fn Win32_BOOL terminateProcess(Win32_HANDLE hProcess, Win32_UINT uExitCode) @extern("TerminateProcess"); +extern fn Win32_DWORD getCurrentProcessId() @extern("GetCurrentProcessId"); +extern fn Win32_DWORD getCurrentThreadId() @extern("GetCurrentThreadId"); +extern fn Win32_BOOL setHandleInformation(Win32_HANDLE hObject, Win32_DWORD dwMask, Win32_DWORD dwFlags) @extern("SetHandleInformation"); +extern fn Win32_HANDLE createEventA( + Win32_LPSECURITY_ATTRIBUTES lpEventAttributes, + Win32_BOOL bManualReset, + Win32_BOOL bInitialState, + Win32_LPCSTR lpName +) @extern("CreateEventA"); +extern fn Win32_BOOL createProcessW( Win32_LPCWSTR lpApplicationName, Win32_LPWSTR lpCommandLine, Win32_LPSECURITY_ATTRIBUTES lpProcessAttributes, @@ -12,3 +106,15 @@ extern fn bool win32_CreateProcessW( Win32_LPSTARTUPINFOW lpStartupInfo, Win32_LPPROCESS_INFORMATION lpProcessInformation ) @extern("CreateProcessW"); +extern fn Win32_HANDLE createNamedPipeA( + Win32_LPCSTR lpName, Win32_DWORD dwOpenMode, Win32_DWORD dwPipeMode, + Win32_DWORD nMaxInstances, Win32_DWORD nOutBufferSize, Win32_DWORD nInBufferSize, + Win32_DWORD nDefaultTimeOut, Win32_LPSECURITY_ATTRIBUTES lpSecurityAttributes +) @extern("CreateNamedPipeA"); +extern fn Win32_BOOL getOverlappedResult( + Win32_HANDLE hFile, + Win32_LPOVERLAPPED lpOverlapped, + Win32_LPDWORD lpNumberOfBytesTransferred, + Win32_BOOL bWait +) @extern("GetOverlappedResult"); + diff --git a/lib/std/os/win32/types.c3 b/lib/std/os/win32/types.c3 index 342c79611..459d5a334 100644 --- a/lib/std/os/win32/types.c3 +++ b/lib/std/os/win32/types.c3 @@ -196,6 +196,8 @@ struct Win32_SECURITY_ATTRIBUTES Win32_BOOL bInheritHandle; } + + def Win32_LPSECURITY_ATTRIBUTES = Win32_SECURITY_ATTRIBUTES*; def Win32_PSECURITY_ATTRIBUTES = Win32_SECURITY_ATTRIBUTES*; @@ -221,6 +223,21 @@ struct Win32_STARTUPINFOW Win32_HANDLE hStdError; } +struct Win32_OVERLAPPED +{ + Win32_ULONG_PTR internal; + Win32_ULONG_PTR internalHigh; + union { + struct { + Win32_DWORD offset; + Win32_DWORD offsetHigh; + } + Win32_PVOID pointer; + } + Win32_HANDLE hEvent; +} + +def Win32_LPOVERLAPPED = Win32_OVERLAPPED*; def Win32_LPSTARTUPINFOW = Win32_STARTUPINFOW*; struct Win32_STARTUPINFOEXW diff --git a/lib/std/threads/os/thread_win32.c3 b/lib/std/threads/os/thread_win32.c3 index 84548a932..be763f181 100644 --- a/lib/std/threads/os/thread_win32.c3 +++ b/lib/std/threads/os/thread_win32.c3 @@ -1,68 +1,5 @@ -module std::thread::os @if(thread::THREAD_MODEL == ThreadModel.WIN32); - -def NativeThread = Win32_HANDLE; - -struct NativeMutex -{ - union - { - Win32_CRITICAL_SECTION critical_section; - Win32_HANDLE handle; - } - bool already_locked; - bool recursive; - bool timed; -} - -struct NativeOnceFlag -{ - int status; - Win32_CRITICAL_SECTION lock; -} - -struct NativeConditionVariable -{ - union - { - struct - { - Win32_HANDLE event_one; - Win32_HANDLE event_all; - } - Win32_HANDLE[2] events; - } - uint waiters_count; - Win32_CRITICAL_SECTION waiters_count_lock; -} - -extern fn void win32_InitializeCriticalSection(Win32_CRITICAL_SECTION* section) @extern("InitializeCriticalSection"); -extern fn void win32_DeleteCriticalSection(Win32_CRITICAL_SECTION* section) @extern("DeleteCriticalSection"); -extern fn Win32_HANDLE win32_CreateMutex(void*, bool, void*) @extern("CreateMutexA"); -extern fn bool win32_CloseHandle(Win32_HANDLE) @extern("CloseHandle"); -extern fn bool win32_ReleaseMutex(Win32_HANDLE) @extern("ReleaseMutex"); -extern fn void win32_EnterCriticalSection(Win32_CRITICAL_SECTION* section) @extern("EnterCriticalSection"); -extern fn void win32_LeaveCriticalSection(Win32_CRITICAL_SECTION* section) @extern("LeaveCriticalSection"); -extern fn bool win32_TryEnterCriticalSection(Win32_CRITICAL_SECTION* section) @extern("TryEnterCriticalSection"); -extern fn uint win32_WaitForSingleObject(Win32_HANDLE, uint milliseconds) @extern("WaitForSingleObject"); -extern fn void win32_Sleep(uint ms) @extern("Sleep"); -extern fn uint win32_WaitForMultipleObjects(uint count, Win32_HANDLE* handles, bool wait_all, uint ms) @extern("WaitForMultipleObjects"); -extern fn Win32_HANDLE win32_CreateEventA(void* attributes, bool manual_reset, bool initial_state, char* name) @extern("CreateEventA"); -extern fn bool win32_ResetEvent(Win32_HANDLE event) @extern("ResetEvent"); -extern fn bool win32_SetEvent(Win32_HANDLE handle) @extern("SetEvent"); -extern fn long win32_InterlockedCompareExchange(int* dest, int exchange, int comperand) @extern("InterlockedCompareExchange"); -extern fn uint win32_SleepEx(uint ms, bool alertable) @extern("SleepEx"); -extern fn Win32_HANDLE win32_CreateThread(void* attributes, usz stack, ThreadFn func, void* arg, uint flags, uint* thread_id) @extern("CreateThread"); -extern fn bool win32_GetExitCodeThread(Win32_HANDLE handle, uint* exit_code) @extern("GetExitCodeThread"); -extern fn uint win32_GetThreadId(Win32_HANDLE) @extern("GetThreadId"); -extern fn void win32_ExitThread(uint res) @noreturn @extern("ExitThread"); -extern fn Win32_HANDLE win32_GetCurrentThread() @extern("GetCurrentThread"); - -const uint WAIT_OBJECT_0 = 0; -const uint WAIT_ABANDONED = 128; -const uint WAIT_TIMEOUT = 258; -const uint WAIT_IO_COMPLETION = 192; -const uint WAIT_FAILED = (uint)-1; -const uint INFINITE = (uint)-1; +module std::thread::os @if(env::WIN32); +import std::os::win32; fn void! NativeMutex.init(NativeMutex* mtx, MutexType type) { @@ -71,35 +8,35 @@ fn void! NativeMutex.init(NativeMutex* mtx, MutexType type) mtx.timed = (bool)(type & thread::MUTEX_TIMED); if (!mtx.timed) { - win32_InitializeCriticalSection(&(mtx.critical_section)); + win32::initializeCriticalSection(&(mtx.critical_section)); return; } - if (!(mtx.handle = win32_CreateMutex(null, false, null))) return ThreadFault.INIT_FAILED?; + if (!(mtx.handle = win32::createMutex(null, false, null))) return ThreadFault.INIT_FAILED?; } fn void! NativeMutex.destroy(NativeMutex* mtx) { if (!mtx.timed) { - win32_DeleteCriticalSection(&mtx.critical_section); + win32::deleteCriticalSection(&mtx.critical_section); return; } - if (!win32_CloseHandle(mtx.handle)) return ThreadFault.DESTROY_FAILED?; + if (!win32::closeHandle(mtx.handle)) return ThreadFault.DESTROY_FAILED?; } fn void! NativeMutex.lock(NativeMutex* mtx) { if (!mtx.timed) { - win32_EnterCriticalSection(&mtx.critical_section); + win32::enterCriticalSection(&mtx.critical_section); } else { - switch (win32_WaitForSingleObject(mtx.handle, INFINITE)) + switch (win32::waitForSingleObject(mtx.handle, win32::INFINITE)) { - case WAIT_OBJECT_0: + case win32::WAIT_OBJECT_0: break; - case WAIT_ABANDONED: + case win32::WAIT_ABANDONED: default: return ThreadFault.LOCK_FAILED?; @@ -107,7 +44,7 @@ fn void! NativeMutex.lock(NativeMutex* mtx) } if (!mtx.recursive) { - while (mtx.already_locked) win32_Sleep(1); + while (mtx.already_locked) win32::sleep(1); } mtx.already_locked = true; } @@ -118,19 +55,19 @@ fn void! NativeMutex.lock(NativeMutex* mtx) **/ fn void! NativeMutex.lock_timeout(NativeMutex* mtx, uint ms) { - switch (win32_WaitForSingleObject(mtx.handle, ms)) + switch (win32::waitForSingleObject(mtx.handle, ms)) { - case WAIT_OBJECT_0: + case win32::WAIT_OBJECT_0: break; - case WAIT_TIMEOUT: + case win32::WAIT_TIMEOUT: return ThreadFault.LOCK_TIMEOUT?; - case WAIT_ABANDONED: + case win32::WAIT_ABANDONED: default: return ThreadFault.LOCK_FAILED?; } if (!mtx.recursive) { - while (mtx.already_locked) win32_Sleep(1); + while (mtx.already_locked) win32::sleep(1); mtx.already_locked = true; } } @@ -138,8 +75,8 @@ fn void! NativeMutex.lock_timeout(NativeMutex* mtx, uint ms) fn bool NativeMutex.try_lock(NativeMutex* mtx) { bool success = mtx.timed - ? win32_WaitForSingleObject(mtx.handle, 0) == WAIT_OBJECT_0 - : win32_TryEnterCriticalSection(&mtx.critical_section); + ? win32::waitForSingleObject(mtx.handle, 0) == win32::WAIT_OBJECT_0 + : (bool)win32::tryEnterCriticalSection(&mtx.critical_section); if (!success) return false; if (!mtx.recursive) @@ -147,7 +84,7 @@ fn bool NativeMutex.try_lock(NativeMutex* mtx) if (mtx.already_locked) { assert(!mtx.timed); - win32_LeaveCriticalSection(&mtx.critical_section); + win32::leaveCriticalSection(&mtx.critical_section); return false; } mtx.already_locked = true; @@ -160,10 +97,10 @@ fn void! NativeMutex.unlock(NativeMutex* mtx) mtx.already_locked = false; if (!mtx.timed) { - win32_LeaveCriticalSection(&mtx.critical_section); + win32::leaveCriticalSection(&mtx.critical_section); return; } - if (!win32_ReleaseMutex(mtx.handle)) return ThreadFault.UNLOCK_FAILED?; + if (!win32::releaseMutex(mtx.handle)) return ThreadFault.UNLOCK_FAILED?; } const int CONDITION_EVENT_ONE = 0; @@ -172,17 +109,17 @@ const int CONDITION_EVENT_ALL = 1; fn void! NativeConditionVariable.init(NativeConditionVariable* cond) { cond.waiters_count = 0; - win32_InitializeCriticalSection(&cond.waiters_count_lock); - cond.event_one = win32_CreateEventA(null, false, false, null); + win32::initializeCriticalSection(&cond.waiters_count_lock); + cond.event_one = win32::createEventA(null, 0, 0, null); if (!cond.event_one) { cond.event_all = (Win32_HANDLE)0; return ThreadFault.INIT_FAILED?; } - cond.event_all = win32_CreateEventA(null, true, false, null); + cond.event_all = win32::createEventA(null, 1, 0, null); if (!cond.event_all) { - win32_CloseHandle(cond.event_one); + win32::closeHandle(cond.event_one); cond.event_one = (Win32_HANDLE)0; return ThreadFault.INIT_FAILED?; } @@ -190,57 +127,57 @@ fn void! NativeConditionVariable.init(NativeConditionVariable* cond) fn void! NativeConditionVariable.destroy(NativeConditionVariable* cond) @maydiscard { - if (cond.event_one) win32_CloseHandle(cond.event_one); - if (cond.event_all) win32_CloseHandle(cond.event_all); - win32_DeleteCriticalSection(&cond.waiters_count_lock); + if (cond.event_one) win32::closeHandle(cond.event_one); + if (cond.event_all) win32::closeHandle(cond.event_all); + win32::deleteCriticalSection(&cond.waiters_count_lock); } fn void! NativeConditionVariable.signal(NativeConditionVariable* cond) { - win32_EnterCriticalSection(&cond.waiters_count_lock); + win32::enterCriticalSection(&cond.waiters_count_lock); bool have_waiters = cond.waiters_count > 0; - win32_LeaveCriticalSection(&cond.waiters_count_lock); - if (have_waiters && !win32_SetEvent(cond.event_one)) return ThreadFault.SIGNAL_FAILED?; + win32::leaveCriticalSection(&cond.waiters_count_lock); + if (have_waiters && !win32::setEvent(cond.event_one)) return ThreadFault.SIGNAL_FAILED?; } fn void! NativeConditionVariable.broadcast(NativeConditionVariable* cond) { - win32_EnterCriticalSection(&cond.waiters_count_lock); + win32::enterCriticalSection(&cond.waiters_count_lock); bool have_waiters = cond.waiters_count > 0; - win32_LeaveCriticalSection(&cond.waiters_count_lock); - if (have_waiters && !win32_SetEvent(cond.event_all)) return ThreadFault.SIGNAL_FAILED?; + win32::leaveCriticalSection(&cond.waiters_count_lock); + if (have_waiters && !win32::setEvent(cond.event_all)) return ThreadFault.SIGNAL_FAILED?; } fn void! timedwait(NativeConditionVariable* cond, NativeMutex* mtx, uint timeout) @private { - win32_EnterCriticalSection(&cond.waiters_count_lock); + win32::enterCriticalSection(&cond.waiters_count_lock); cond.waiters_count++; - win32_LeaveCriticalSection(&cond.waiters_count_lock); + win32::leaveCriticalSection(&cond.waiters_count_lock); mtx.unlock()!; - uint result = win32_WaitForMultipleObjects(2, &cond.events, false, timeout); + uint result = win32::waitForMultipleObjects(2, &cond.events, false, timeout); switch (result) { - case WAIT_TIMEOUT: + case win32::WAIT_TIMEOUT: mtx.lock()!; return ThreadFault.WAIT_TIMEOUT?; - case WAIT_FAILED: + case win32::WAIT_FAILED: mtx.lock()!; return ThreadFault.WAIT_FAILED?; default: break; } - win32_EnterCriticalSection(&cond.waiters_count_lock); + win32::enterCriticalSection(&cond.waiters_count_lock); cond.waiters_count--; // If event all && no waiters bool last_waiter = result == 1 && !cond.waiters_count; - win32_LeaveCriticalSection(&cond.waiters_count_lock); + win32::leaveCriticalSection(&cond.waiters_count_lock); if (last_waiter) { - if (!win32_ResetEvent(cond.event_all)) + if (!win32::resetEvent(cond.event_all)) { mtx.lock()!; return ThreadFault.WAIT_FAILED?; @@ -252,7 +189,7 @@ fn void! timedwait(NativeConditionVariable* cond, NativeMutex* mtx, uint timeout fn void! NativeConditionVariable.wait(NativeConditionVariable* cond, NativeMutex* mtx) @inline { - return timedwait(cond, mtx, INFINITE) @inline; + return timedwait(cond, mtx, win32::INFINITE) @inline; } fn void! NativeConditionVariable.wait_timeout(NativeConditionVariable* cond, NativeMutex* mtx, uint time) @inline @@ -262,23 +199,23 @@ fn void! NativeConditionVariable.wait_timeout(NativeConditionVariable* cond, Nat fn void! NativeThread.create(NativeThread* thread, ThreadFn func, void* args) { - if (!(*thread = win32_CreateThread(null, 0, func, args, 0, null))) return ThreadFault.INIT_FAILED?; + if (!(*thread = win32::createThread(null, 0, func, args, 0, null))) return ThreadFault.INIT_FAILED?; } fn void! NativeThread.detach(NativeThread thread) @inline { - if (!win32_CloseHandle(thread)) return ThreadFault.DETACH_FAILED?; + if (!win32::closeHandle(thread)) return ThreadFault.DETACH_FAILED?; } fn void native_thread_exit(int result) @inline { - win32_ExitThread((uint)result); + win32::exitThread((uint)result); } fn void native_thread_yield() { - win32_Sleep(0); + win32::sleep(0); } fn void NativeOnceFlag.call_once(NativeOnceFlag* flag, OnceFn func) @@ -290,20 +227,20 @@ fn void NativeOnceFlag.call_once(NativeOnceFlag* flag, OnceFn func) case 0: if (mem::compare_exchange_volatile(&flag.status, 1, 0, AtomicOrdering.SEQ_CONSISTENT, AtomicOrdering.SEQ_CONSISTENT) == 0) { - win32_InitializeCriticalSection(&flag.lock); - win32_EnterCriticalSection(&flag.lock); + win32::initializeCriticalSection(&flag.lock); + win32::enterCriticalSection(&flag.lock); @volatile_store(flag.status, 2); func(); @volatile_store(flag.status, 3); - win32_LeaveCriticalSection(&flag.lock); + win32::leaveCriticalSection(&flag.lock); return; } break; case 1: break; case 2: - win32_EnterCriticalSection(&flag.lock); - win32_LeaveCriticalSection(&flag.lock); + win32::enterCriticalSection(&flag.lock); + win32::leaveCriticalSection(&flag.lock); break; } } @@ -311,19 +248,19 @@ fn void NativeOnceFlag.call_once(NativeOnceFlag* flag, OnceFn func) fn void! NativeThread.join(NativeThread thread, int *res) { - if (win32_WaitForSingleObject(thread, INFINITE) == WAIT_FAILED) return ThreadFault.JOIN_FAILED?; - if (!win32_GetExitCodeThread(thread, (uint*)res)) return ThreadFault.JOIN_FAILED?; - defer win32_CloseHandle(thread); + if (win32::waitForSingleObject(thread, win32::INFINITE) == win32::WAIT_FAILED) return ThreadFault.JOIN_FAILED?; + if (!win32::getExitCodeThread(thread, (uint*)res)) return ThreadFault.JOIN_FAILED?; + defer win32::closeHandle(thread); } fn NativeThread native_thread_current() { - return win32_GetCurrentThread(); + return win32::getCurrentThread(); } fn bool NativeThread.equals(NativeThread this, NativeThread other) { - return win32_GetThreadId(this) == win32_GetThreadId(other); + return win32::getThreadId(this) == win32::getThreadId(other); } /** @@ -331,7 +268,7 @@ fn bool NativeThread.equals(NativeThread this, NativeThread other) **/ fn void! native_sleep_ms(ulong ms) { - if (win32_SleepEx((uint)ms, true) == WAIT_IO_COMPLETION) return ThreadFault.INTERRUPTED?; + if (win32::sleepEx((uint)ms, true) == win32::WAIT_IO_COMPLETION) return ThreadFault.INTERRUPTED?; } fn void! native_sleep(double s) diff --git a/releasenotes.md b/releasenotes.md index af7c0d902..7418a0d3b 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -3,6 +3,7 @@ ## 0.5.0 Change List ### Changes / improvements +- Fix to void expression blocks - Temporary objects may now invoke methods using ref parameters. - Delete object files after successful linking. - `@if` introduced, other top level conditional compilation removed. @@ -86,6 +87,8 @@ - `@ensure` checks only non-optional results. ### Stdlib changes +- Updated posix/win32 stdlib namespacing +- `process` stdlib - Stdlib updates to string. - Many additions to `List`: `remove`, `array_view`, `add_all`, `compact` etc - Added dstringwriter. diff --git a/resources/examples/process.c3 b/resources/examples/process.c3 new file mode 100644 index 000000000..7e09a1ca0 --- /dev/null +++ b/resources/examples/process.c3 @@ -0,0 +1,16 @@ +module test; +import std::os::process; +import std::io; + +fn void! main() +{ + String command = env::WIN32 ? "dir" : "ls"; + SubProcess x = process::create({ command }, { .search_user_path = true })!!; + x.join()!; + Stream stream = x.stdout().as_stream(); + while (try char b = stream.read_byte()) + { + io::printf("%c", b); + } + io::printn("...Done"); +} diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 9de81892a..b15194b3e 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -1712,7 +1712,7 @@ static inline Type *context_unify_returns(SemaContext *context) } // 3. Same type -> we're done. - if (common_type == rtype) continue; + if (common_type == rtype || (common_type == type_void && rtype == type_wildcard)) continue; // 4. Find the max of the old and new. Type *max = type_find_max_type(common_type, rtype); @@ -1744,6 +1744,10 @@ static inline Type *context_unify_returns(SemaContext *context) Ast *return_stmt = context->returns[i]; if (!return_stmt) continue; Expr *ret_expr = return_stmt->return_stmt.expr; + if (!ret_expr) + { + context_unify_returns(context); + } // 8. All casts should work. if (!cast_implicit(context, ret_expr, common_type)) {