Commit 51852d2587

Andrew Kelley <superjoe30@gmail.com>
2018-08-21 22:07:28
fix windows
1 parent bda5539
src-self-hosted/compilation.zig
@@ -302,6 +302,7 @@ pub const Compilation = struct {
         UnsupportedLinkArchitecture,
         UserResourceLimitReached,
         InvalidUtf8,
+        BadPathName,
     };
 
     pub const Event = union(enum) {
src-self-hosted/errmsg.zig
@@ -235,7 +235,7 @@ pub const Msg = struct {
         const allocator = msg.getAllocator();
         const tree = msg.getTree();
 
-        const cwd = try os.getCwd(allocator);
+        const cwd = try os.getCwdAlloc(allocator);
         defer allocator.free(cwd);
 
         const relpath = try os.path.relative(allocator, cwd, msg.realpath);
src-self-hosted/introspect.zig
@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
     const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig");
     defer allocator.free(test_index_file);
 
-    var file = try os.File.openRead(allocator, test_index_file);
+    var file = try os.File.openRead(test_index_file);
     file.close();
 
     return test_zig_dir;
src-self-hosted/libc_installation.zig
@@ -233,7 +233,7 @@ pub const LibCInstallation = struct {
             const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h");
             defer loop.allocator.free(stdlib_path);
 
-            if (try fileExists(loop.allocator, stdlib_path)) {
+            if (try fileExists(stdlib_path)) {
                 self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);
                 return;
             }
@@ -257,7 +257,7 @@ pub const LibCInstallation = struct {
             const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h");
             defer loop.allocator.free(stdlib_path);
 
-            if (try fileExists(loop.allocator, stdlib_path)) {
+            if (try fileExists(stdlib_path)) {
                 self.include_dir = result_buf.toOwnedSlice();
                 return;
             }
@@ -285,7 +285,7 @@ pub const LibCInstallation = struct {
             }
             const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib");
             defer loop.allocator.free(ucrt_lib_path);
-            if (try fileExists(loop.allocator, ucrt_lib_path)) {
+            if (try fileExists(ucrt_lib_path)) {
                 self.lib_dir = result_buf.toOwnedSlice();
                 return;
             }
@@ -360,7 +360,7 @@ pub const LibCInstallation = struct {
             }
             const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib");
             defer loop.allocator.free(kernel32_path);
-            if (try fileExists(loop.allocator, kernel32_path)) {
+            if (try fileExists(kernel32_path)) {
                 self.kernel32_lib_dir = result_buf.toOwnedSlice();
                 return;
             }
@@ -449,12 +449,11 @@ fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
     return search_buf[0..search_end];
 }
 
-fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool {
-    if (std.os.File.access(allocator, path)) |_| {
+fn fileExists(path: []const u8) !bool {
+    if (std.os.File.access(path)) |_| {
         return true;
     } else |err| switch (err) {
-        error.NotFound, error.PermissionDenied => return false,
-        error.OutOfMemory => return error.OutOfMemory,
+        error.FileNotFound, error.PathNotFound, error.PermissionDenied => return false,
         else => return error.FileSystem,
     }
 }
src-self-hosted/test.zig
@@ -94,7 +94,7 @@ pub const TestContext = struct {
         }
 
         // TODO async I/O
-        try std.io.writeFile(allocator, file1_path, source);
+        try std.io.writeFile(file1_path, source);
 
         var comp = try Compilation.create(
             &self.zig_compiler,
@@ -128,7 +128,7 @@ pub const TestContext = struct {
         }
 
         // TODO async I/O
-        try std.io.writeFile(allocator, file1_path, source);
+        try std.io.writeFile(file1_path, source);
 
         var comp = try Compilation.create(
             &self.zig_compiler,
std/event/fs.zig
@@ -382,7 +382,6 @@ pub async fn openRead(loop: *Loop, path: []const u8) os.File.OpenError!os.FileHa
         },
 
         builtin.Os.windows => return os.windowsOpen(
-            loop.allocator,
             path,
             windows.GENERIC_READ,
             windows.FILE_SHARE_READ,
@@ -411,7 +410,6 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: os.File.Mode) os
         },
         builtin.Os.windows,
         => return os.windowsOpen(
-            loop.allocator,
             path,
             windows.GENERIC_WRITE,
             windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@@ -435,7 +433,6 @@ pub async fn openReadWrite(
         },
 
         builtin.Os.windows => return os.windowsOpen(
-            loop.allocator,
             path,
             windows.GENERIC_WRITE|windows.GENERIC_READ,
             windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
@@ -593,7 +590,6 @@ pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8,
 
 async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void {
     const handle = try os.windowsOpen(
-        loop.allocator,
         path,
         windows.GENERIC_WRITE,
         windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
std/os/windows/kernel32.zig
@@ -4,10 +4,8 @@ pub extern "kernel32" stdcallcc fn CancelIoEx(hFile: HANDLE, lpOverlapped: LPOVE
 
 pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
 
-pub extern "kernel32" stdcallcc fn CreateDirectoryA(
-    lpPathName: LPCSTR,
-    lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
-) BOOL;
+pub extern "kernel32" stdcallcc fn CreateDirectoryA( lpPathName: [*]const u8, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
+pub extern "kernel32" stdcallcc fn CreateDirectoryW( lpPathName: [*]const u16, lpSecurityAttributes: ?*SECURITY_ATTRIBUTES) BOOL;
 
 pub extern "kernel32" stdcallcc fn CreateFileA(
     lpFileName: [*]const u8, // TODO null terminated pointer type
@@ -59,7 +57,8 @@ pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, Ex
 
 pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
 
-pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
+pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: [*]const u8) BOOL;
+pub extern "kernel32" stdcallcc fn DeleteFileW(lpFileName: [*]const u16) BOOL;
 
 pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
 
@@ -73,8 +72,8 @@ pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
 
 pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
 
-pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?[*]CHAR) DWORD;
-pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: WORD, lpBuffer: ?[*]WCHAR) DWORD;
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: DWORD, lpBuffer: ?[*]CHAR) DWORD;
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryW(nBufferLength: DWORD, lpBuffer: ?[*]WCHAR) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetCurrentThread() HANDLE;
 pub extern "kernel32" stdcallcc fn GetCurrentThreadId() DWORD;
@@ -87,7 +86,8 @@ pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCo
 
 pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
 
-pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD;
+pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: [*]const CHAR) DWORD;
+pub extern "kernel32" stdcallcc fn GetFileAttributesW(lpFileName: [*]const WCHAR) DWORD;
 
 pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;
 
@@ -131,8 +131,14 @@ pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem
 pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: ?*const c_void) BOOL;
 
 pub extern "kernel32" stdcallcc fn MoveFileExA(
-    lpExistingFileName: LPCSTR,
-    lpNewFileName: LPCSTR,
+    lpExistingFileName: [*]const u8,
+    lpNewFileName: [*]const u8,
+    dwFlags: DWORD,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn MoveFileExW(
+    lpExistingFileName: [*]const u16,
+    lpNewFileName: [*]const u16,
     dwFlags: DWORD,
 ) BOOL;
 
std/os/windows/util.zig
@@ -7,11 +7,17 @@ const mem = std.mem;
 const BufMap = std.BufMap;
 const cstr = std.cstr;
 
-pub const PATH_MAX_UTF16 = 32767;
+// > The maximum path of 32,767 characters is approximate, because the "\\?\"
+// > prefix may be expanded to a longer string by the system at run time, and
+// > this expansion applies to the total length.
+// from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
+pub const PATH_MAX_WIDE = 32767;
 
 pub const WaitError = error{
     WaitAbandoned,
     WaitTimeOut,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -39,6 +45,8 @@ pub const WriteError = error{
     SystemResources,
     OperationAborted,
     BrokenPipe,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -88,13 +96,28 @@ pub fn windowsIsCygwinPty(handle: windows.HANDLE) bool {
 pub const OpenError = error{
     SharingViolation,
     PathAlreadyExists,
+
+    /// When all the path components are found but the file component is not.
     FileNotFound,
+
+    /// When one or more path components are not found.
+    PathNotFound,
+
     AccessDenied,
     PipeBusy,
+    NameTooLong,
+
+    /// On Windows, file paths must be valid Unicode.
+    InvalidUtf8,
+
+    /// On Windows, file paths cannot contain these characters:
+    /// '/', '*', '?', '"', '<', '>', '|'
+    BadPathName,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
-/// `file_path` needs to be copied in memory to add a null terminating byte, hence the allocator.
 pub fn windowsOpen(
     file_path: []const u8,
     desired_access: windows.DWORD,
@@ -102,7 +125,25 @@ pub fn windowsOpen(
     creation_disposition: windows.DWORD,
     flags_and_attrs: windows.DWORD,
 ) OpenError!windows.HANDLE {
-    @compileError("TODO rewrite with CreateFileW and no allocator");
+    const file_path_w = try sliceToPrefixedFileW(file_path);
+
+    const result = windows.CreateFileW(&file_path_w, desired_access, share_mode, null, creation_disposition, flags_and_attrs, null);
+
+    if (result == windows.INVALID_HANDLE_VALUE) {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.SHARING_VIOLATION => return OpenError.SharingViolation,
+            windows.ERROR.ALREADY_EXISTS => return OpenError.PathAlreadyExists,
+            windows.ERROR.FILE_EXISTS => return OpenError.PathAlreadyExists,
+            windows.ERROR.FILE_NOT_FOUND => return OpenError.FileNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return OpenError.PathNotFound,
+            windows.ERROR.ACCESS_DENIED => return OpenError.AccessDenied,
+            windows.ERROR.PIPE_BUSY => return OpenError.PipeBusy,
+            else => return os.unexpectedErrorWindows(err),
+        }
+    }
+
+    return result;
 }
 
 /// Caller must free result.
@@ -242,3 +283,33 @@ pub fn windowsGetQueuedCompletionStatus(completion_port: windows.HANDLE, bytes_t
     }
     return WindowsWaitResult.Normal;
 }
+
+pub fn cStrToPrefixedFileW(s: [*]const u8) ![PATH_MAX_WIDE+1]u16 {
+    return sliceToPrefixedFileW(mem.toSliceConst(u8, s));
+}
+
+pub fn sliceToPrefixedFileW(s: []const u8) ![PATH_MAX_WIDE+1]u16 {
+    // TODO well defined copy elision
+    var result: [PATH_MAX_WIDE+1]u16 = undefined;
+
+    // > File I/O functions in the Windows API convert "/" to "\" as part of
+    // > converting the name to an NT-style name, except when using the "\\?\"
+    // > prefix as detailed in the following sections.
+    // from https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#maximum-path-length-limitation
+    // Because we want the larger maximum path length for absolute paths, we
+    // disallow forward slashes in zig std lib file functions on Windows.
+    for (s) |byte| switch (byte) {
+        '/', '*', '?', '"', '<', '>', '|' => return error.BadPathName,
+        else => {},
+    };
+    const start_index = if (mem.startsWith(u8, s, "\\\\") or !os.path.isAbsolute(s)) 0 else blk: {
+        const prefix = []u16{'\\', '\\', '?', '\\'};
+        mem.copy(u16, result[0..], prefix);
+        break :blk prefix.len;
+    };
+    const end_index = start_index + try std.unicode.utf8ToUtf16Le(result[start_index..], s);
+    assert(end_index <= result.len);
+    if (end_index == result.len) return error.NameTooLong;
+    result[end_index] = 0;
+    return result;
+}
std/os/child_process.zig
@@ -453,10 +453,7 @@ pub const ChildProcess = struct {
         const any_ignore = (self.stdin_behavior == StdIo.Ignore or self.stdout_behavior == StdIo.Ignore or self.stderr_behavior == StdIo.Ignore);
 
         const nul_handle = if (any_ignore) blk: {
-            const nul_file_path = "NUL";
-            var fixed_buffer_mem: [nul_file_path.len + 1]u8 = undefined;
-            var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
-            break :blk try os.windowsOpen(&fixed_allocator.allocator, "NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
+            break :blk try os.windowsOpen("NUL", windows.GENERIC_READ, windows.FILE_SHARE_READ, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL);
         } else blk: {
             break :blk undefined;
         };
std/os/file.zig
@@ -7,6 +7,7 @@ const assert = std.debug.assert;
 const posix = os.posix;
 const windows = os.windows;
 const Os = builtin.Os;
+const windows_util = @import("windows/util.zig");
 
 const is_posix = builtin.os != builtin.Os.windows;
 const is_windows = builtin.os == builtin.Os.windows;
@@ -102,21 +103,42 @@ pub const File = struct {
 
     pub const AccessError = error{
         PermissionDenied,
-        NotFound,
+        PathNotFound,
+        FileNotFound,
         NameTooLong,
         BadMode,
         BadPathName,
         Io,
         SystemResources,
-        OutOfMemory,
+
+        /// On Windows, file paths must be valid Unicode.
+        InvalidUtf8,
 
         Unexpected,
     };
 
+    /// Call from Windows-specific code if you already have a UTF-16LE encoded, null terminated string.
+    /// Otherwise use `access` or `accessC`.
+    pub fn accessW(path: [*]const u16) AccessError!void {
+        if (os.windows.GetFileAttributesW(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
+            return;
+        }
+
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.PATH_NOT_FOUND => return error.PathNotFound,
+            windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
+            else => return os.unexpectedErrorWindows(err),
+        }
+    }
+
+    /// Call if you have a UTF-8 encoded, null-terminated string.
+    /// Otherwise use `access` or `accessW`.
     pub fn accessC(path: [*]const u8) AccessError!void {
         if (is_windows) {
-            // this needs to convert to UTF-16LE and call accessW
-            @compileError("TODO support windows");
+            const path_w = try windows_util.cStrToPrefixedFileW(path);
+            return accessW(&path_w);
         }
         if (is_posix) {
             const result = posix.access(path, posix.F_OK);
@@ -137,28 +159,14 @@ pub const File = struct {
                 posix.ENOMEM => return error.SystemResources,
                 else => return os.unexpectedErrorPosix(err),
             }
-        } else if (is_windows) {
-            if (os.windows.GetFileAttributesA(path) != os.windows.INVALID_FILE_ATTRIBUTES) {
-                return;
-            }
-
-            const err = windows.GetLastError();
-            switch (err) {
-                windows.ERROR.FILE_NOT_FOUND,
-                windows.ERROR.PATH_NOT_FOUND,
-                => return error.NotFound,
-                windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
-                else => return os.unexpectedErrorWindows(err),
-            }
-        } else {
-            @compileError("TODO implement access for this OS");
         }
+        @compileError("Unsupported OS");
     }
 
     pub fn access(path: []const u8) AccessError!void {
         if (is_windows) {
-            // this needs to convert to UTF-16LE and call accessW
-            @compileError("TODO support windows");
+            const path_w = try windows_util.sliceToPrefixedFileW(path);
+            return accessW(&path_w);
         }
         if (is_posix) {
             var path_with_null: [posix.PATH_MAX]u8 = undefined;
@@ -167,7 +175,7 @@ pub const File = struct {
             path_with_null[path.len] = 0;
             return accessC(&path_with_null);
         }
-        @compileError("TODO implement access for this OS");
+        @compileError("Unsupported OS");
     }
 
     /// Upon success, the stream is in an uninitialized state. To continue using it,
std/os/get_app_data_dir.zig
@@ -10,6 +10,7 @@ pub const GetAppDataDirError = error{
 };
 
 /// Caller owns returned memory.
+/// TODO determine if we can remove the allocator requirement
 pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
     switch (builtin.os) {
         builtin.Os.windows => {
@@ -22,7 +23,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD
             )) {
                 os.windows.S_OK => {
                     defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
-                    const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
+                    const global_dir = unicode.utf16leToUtf8Alloc(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
                         error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
                         error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
                         error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
std/os/index.zig
@@ -45,7 +45,7 @@ pub const MAX_PATH_BYTES = switch (builtin.os) {
     // If it would require 4 UTF-8 bytes, then there would be a surrogate
     // pair in the UTF-16LE, and we (over)account 3 bytes for it that way.
     // +1 for the null byte at the end, which can be encoded in 1 byte.
-    Os.windows => 32767 * 3 + 1,
+    Os.windows => windows_util.PATH_MAX_WIDE * 3 + 1,
     else => @compileError("Unsupported OS"),
 };
 
@@ -326,6 +326,8 @@ pub const PosixWriteError = error{
     NoSpaceLeft,
     AccessDenied,
     BrokenPipe,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -439,6 +441,8 @@ pub const PosixOpenError = error{
     NoSpaceLeft,
     NotDir,
     PathAlreadyExists,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -600,6 +604,8 @@ pub const PosixExecveError = error{
     FileNotFound,
     NotDir,
     FileBusy,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -736,20 +742,25 @@ pub fn getCwdAlloc(allocator: *Allocator) ![]u8 {
 pub const GetCwdError = error{Unexpected};
 
 /// The result is a slice of out_buffer.
+/// TODO with well defined copy elision we could make the API of this function better.
 pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
     switch (builtin.os) {
         Os.windows => {
-            var utf16le_buf: [windows_util.PATH_MAX_UTF16]u16 = undefined;
-            const result = windows.GetCurrentDirectoryW(utf16le_buf.len, &utf16le_buf);
+            var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined;
+            const casted_len = @intCast(windows.DWORD, utf16le_buf.len); // TODO shouldn't need this cast
+            const casted_ptr = ([*]u16)(&utf16le_buf); // TODO shouldn't need this cast
+            const result = windows.GetCurrentDirectoryW(casted_len, casted_ptr);
             if (result == 0) {
                 const err = windows.GetLastError();
                 switch (err) {
                     else => return unexpectedErrorWindows(err),
                 }
             }
-            assert(result <= buf.len);
+            assert(result <= utf16le_buf.len);
             const utf16le_slice = utf16le_buf[0..result];
-            return std.unicode.utf16leToUtf8(out_buffer, utf16le_buf);
+            // Trust that Windows gives us valid UTF-16LE.
+            const end_index = std.unicode.utf16leToUtf8(out_buffer, utf16le_slice) catch unreachable;
+            return out_buffer[0..end_index];
         },
         else => {
             const err = posix.getErrno(posix.getcwd(out_buffer, out_buffer.len));
@@ -764,7 +775,9 @@ pub fn getCwd(out_buffer: *[MAX_PATH_BYTES]u8) GetCwdError![]u8 {
 
 test "os.getCwd" {
     // at least call it so it gets compiled
-    _ = getCwd(debug.global_allocator);
+    _ = getCwdAlloc(debug.global_allocator);
+    var buf: [MAX_PATH_BYTES]u8 = undefined;
+    _ = getCwd(&buf);
 }
 
 pub const SymLinkError = PosixSymLinkError || WindowsSymLinkError;
@@ -779,6 +792,8 @@ pub fn symLink(allocator: *Allocator, existing_path: []const u8, new_path: []con
 
 pub const WindowsSymLinkError = error{
     OutOfMemory,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -809,6 +824,8 @@ pub const PosixSymLinkError = error{
     NoSpaceLeft,
     ReadOnlyFileSystem,
     NotDir,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -867,7 +884,7 @@ pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path:
         b64_fs_encoder.encode(tmp_path[dirname.len + 1 ..], rand_buf);
 
         if (symLink(allocator, existing_path, tmp_path)) {
-            return rename(allocator, tmp_path, new_path);
+            return rename(tmp_path, new_path);
         } else |err| switch (err) {
             error.PathAlreadyExists => continue,
             else => return err, // TODO zig should know this set does not include PathAlreadyExists
@@ -886,8 +903,15 @@ pub const DeleteFileError = error{
     NotDir,
     SystemResources,
     ReadOnlyFileSystem,
-    OutOfMemory,
 
+    /// On Windows, file paths must be valid Unicode.
+    InvalidUtf8,
+
+    /// On Windows, file paths cannot contain these characters:
+    /// '/', '*', '?', '"', '<', '>', '|'
+    BadPathName,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -900,7 +924,18 @@ pub fn deleteFile(file_path: []const u8) DeleteFileError!void {
 }
 
 pub fn deleteFileWindows(file_path: []const u8) !void {
-    @compileError("TODO rewrite with DeleteFileW and no allocator");
+    const file_path_w = try windows_util.sliceToPrefixedFileW(file_path);
+
+    if (windows.DeleteFileW(&file_path_w) == 0) {
+        const err = windows.GetLastError();
+        switch (err) {
+            windows.ERROR.FILE_NOT_FOUND => return error.FileNotFound,
+            windows.ERROR.ACCESS_DENIED => return error.AccessDenied,
+            windows.ERROR.FILENAME_EXCED_RANGE => return error.NameTooLong,
+            windows.ERROR.INVALID_PARAMETER => return error.NameTooLong,
+            else => return unexpectedErrorWindows(err),
+        }
+    }
 }
 
 pub fn deleteFilePosixC(file_path: [*]const u8) !void {
@@ -1028,7 +1063,7 @@ pub const AtomicFile = struct {
     pub fn deinit(self: *AtomicFile) void {
         if (!self.finished) {
             self.file.close();
-            deleteFile(self.allocator, self.tmp_path) catch {};
+            deleteFile(self.tmp_path) catch {};
             self.allocator.free(self.tmp_path);
             self.finished = true;
         }
@@ -1037,7 +1072,7 @@ pub const AtomicFile = struct {
     pub fn finish(self: *AtomicFile) !void {
         assert(!self.finished);
         self.file.close();
-        try rename(self.allocator, self.tmp_path, self.dest_path);
+        try rename(self.tmp_path, self.dest_path);
         self.allocator.free(self.tmp_path);
         self.finished = true;
     }
@@ -1075,7 +1110,15 @@ pub fn renameC(old_path: [*]const u8, new_path: [*]const u8) !void {
 
 pub fn rename(old_path: []const u8, new_path: []const u8) !void {
     if (is_windows) {
-        @compileError("TODO rewrite with MoveFileExW and no allocator");
+        const flags = windows.MOVEFILE_REPLACE_EXISTING | windows.MOVEFILE_WRITE_THROUGH;
+        const old_path_w = try windows_util.sliceToPrefixedFileW(old_path);
+        const new_path_w = try windows_util.sliceToPrefixedFileW(new_path);
+        if (windows.MoveFileExW(&old_path_w, &new_path_w, flags) == 0) {
+            const err = windows.GetLastError();
+            switch (err) {
+                else => return unexpectedErrorWindows(err),
+            }
+        }
     } else {
         var old_path_with_null: [posix.PATH_MAX]u8 = undefined;
         if (old_path.len >= posix.PATH_MAX) return error.NameTooLong;
@@ -1099,11 +1142,10 @@ pub fn makeDir(dir_path: []const u8) !void {
     }
 }
 
-pub fn makeDirWindows(allocator: *Allocator, dir_path: []const u8) !void {
-    const path_buf = try cstr.addNullByte(allocator, dir_path);
-    defer allocator.free(path_buf);
+pub fn makeDirWindows(dir_path: []const u8) !void {
+    const dir_path_w = try windows_util.sliceToPrefixedFileW(dir_path);
 
-    if (windows.CreateDirectoryA(path_buf.ptr, null) == 0) {
+    if (windows.CreateDirectoryW(&dir_path_w, null) == 0) {
         const err = windows.GetLastError();
         return switch (err) {
             windows.ERROR.ALREADY_EXISTS => error.PathAlreadyExists,
@@ -1144,13 +1186,14 @@ pub fn makeDirPosix(dir_path: []const u8) !void {
 
 /// Calls makeDir recursively to make an entire path. Returns success if the path
 /// already exists and is a directory.
+/// TODO determine if we can remove the allocator requirement from this function
 pub fn makePath(allocator: *Allocator, full_path: []const u8) !void {
     const resolved_path = try path.resolve(allocator, full_path);
     defer allocator.free(resolved_path);
 
     var end_index: usize = resolved_path.len;
     while (true) {
-        makeDir(allocator, resolved_path[0..end_index]) catch |err| switch (err) {
+        makeDir(resolved_path[0..end_index]) catch |err| switch (err) {
             error.PathAlreadyExists => {
                 // TODO stat the file and return an error if it's not a directory
                 // this is important because otherwise a dangling symlink
@@ -1188,6 +1231,7 @@ pub const DeleteDirError = error{
     ReadOnlyFileSystem,
     OutOfMemory,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -1256,20 +1300,30 @@ const DeleteTreeError = error{
     FileSystem,
     FileBusy,
     DirNotEmpty,
+
+    /// On Windows, file paths must be valid Unicode.
+    InvalidUtf8,
+
+    /// On Windows, file paths cannot contain these characters:
+    /// '/', '*', '?', '"', '<', '>', '|'
+    BadPathName,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
+
+/// TODO determine if we can remove the allocator requirement
 pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!void {
     start_over: while (true) {
         var got_access_denied = false;
         // First, try deleting the item as a file. This way we don't follow sym links.
-        if (deleteFile(allocator, full_path)) {
+        if (deleteFile(full_path)) {
             return;
         } else |err| switch (err) {
             error.FileNotFound => return,
             error.IsDir => {},
             error.AccessDenied => got_access_denied = true,
 
-            error.OutOfMemory,
             error.SymLinkLoop,
             error.NameTooLong,
             error.SystemResources,
@@ -1277,6 +1331,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError!
             error.NotDir,
             error.FileSystem,
             error.FileBusy,
+            error.InvalidUtf8,
+            error.BadPathName,
             error.Unexpected,
             => return err,
         }
@@ -1383,6 +1439,7 @@ pub const Dir = struct {
         PathAlreadyExists,
         OutOfMemory,
 
+        /// See https://github.com/ziglang/zig/issues/1396
         Unexpected,
     };
 
@@ -1685,6 +1742,8 @@ pub fn posix_setregid(rgid: u32, egid: u32) !void {
 
 pub const WindowsGetStdHandleErrs = error{
     NoStdHandles,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2012,7 +2071,7 @@ pub fn unexpectedErrorPosix(errno: usize) UnexpectedError {
 /// Call this when you made a windows DLL call or something that does SetLastError
 /// and you get an unexpected error.
 pub fn unexpectedErrorWindows(err: windows.DWORD) UnexpectedError {
-    if (unexpected_error_tracing) {
+    if (true) {
         debug.warn("unexpected GetLastError(): {}\n", err);
         debug.dumpCurrentStackTrace(null);
     }
@@ -2215,6 +2274,7 @@ pub const PosixBindError = error{
     /// The socket inode would reside on a read-only filesystem.
     ReadOnlyFileSystem,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2258,6 +2318,7 @@ const PosixListenError = error{
     /// The socket is not of a type that supports the listen() operation.
     OperationNotSupported,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2311,6 +2372,7 @@ pub const PosixAcceptError = error{
     /// Firewall rules forbid connection.
     BlockedByFirewall,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2356,6 +2418,7 @@ pub const LinuxEpollCreateError = error{
     /// There was insufficient memory to create the kernel object.
     SystemResources,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2410,6 +2473,7 @@ pub const LinuxEpollCtlError = error{
     /// for example, a regular file or a directory.
     FileDescriptorIncompatibleWithEpoll,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2452,6 +2516,7 @@ pub const LinuxEventFdError = error{
     ProcessFdQuotaExceeded,
     SystemFdQuotaExceeded,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2474,6 +2539,7 @@ pub const PosixGetSockNameError = error{
     /// Insufficient resources were available in the system to perform the operation.
     SystemResources,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2527,6 +2593,7 @@ pub const PosixConnectError = error{
     /// that for IP sockets the timeout may be very long when syncookies are enabled on the server.
     ConnectionTimedOut,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2748,6 +2815,7 @@ pub const SpawnThreadError = error{
     /// Not enough userland memory to spawn the thread.
     OutOfMemory,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -2935,6 +3003,8 @@ pub fn posixFStat(fd: i32) !posix.Stat {
 pub const CpuCountError = error{
     OutOfMemory,
     PermissionDenied,
+
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
@@ -3005,6 +3075,7 @@ pub const BsdKQueueError = error{
     /// The system-wide limit on the total number of open files has been reached.
     SystemFdQuotaExceeded,
 
+    /// See https://github.com/ziglang/zig/issues/1396
     Unexpected,
 };
 
std/os/path.zig
@@ -16,6 +16,8 @@ pub const sep_windows = '\\';
 pub const sep_posix = '/';
 pub const sep = if (is_windows) sep_windows else sep_posix;
 
+pub const sep_str = [1]u8{sep};
+
 pub const delimiter_windows = ';';
 pub const delimiter_posix = ':';
 pub const delimiter = if (is_windows) delimiter_windows else delimiter_posix;
@@ -337,7 +339,7 @@ pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 {
 pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
     if (paths.len == 0) {
         assert(is_windows); // resolveWindows called on non windows can't use getCwd
-        return os.getCwd(allocator);
+        return os.getCwdAlloc(allocator);
     }
 
     // determine which disk designator we will result with, if any
@@ -432,7 +434,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
             },
             WindowsPath.Kind.None => {
                 assert(is_windows); // resolveWindows called on non windows can't use getCwd
-                const cwd = try os.getCwd(allocator);
+                const cwd = try os.getCwdAlloc(allocator);
                 defer allocator.free(cwd);
                 const parsed_cwd = windowsParsePath(cwd);
                 result = try allocator.alloc(u8, max_size + parsed_cwd.disk_designator.len + 1);
@@ -448,7 +450,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
     } else {
         assert(is_windows); // resolveWindows called on non windows can't use getCwd
         // TODO call get cwd for the result_disk_designator instead of the global one
-        const cwd = try os.getCwd(allocator);
+        const cwd = try os.getCwdAlloc(allocator);
         defer allocator.free(cwd);
 
         result = try allocator.alloc(u8, max_size + cwd.len + 1);
@@ -516,7 +518,7 @@ pub fn resolveWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 {
 pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
     if (paths.len == 0) {
         assert(!is_windows); // resolvePosix called on windows can't use getCwd
-        return os.getCwd(allocator);
+        return os.getCwdAlloc(allocator);
     }
 
     var first_index: usize = 0;
@@ -538,7 +540,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
         result = try allocator.alloc(u8, max_size);
     } else {
         assert(!is_windows); // resolvePosix called on windows can't use getCwd
-        const cwd = try os.getCwd(allocator);
+        const cwd = try os.getCwdAlloc(allocator);
         defer allocator.free(cwd);
         result = try allocator.alloc(u8, max_size + cwd.len + 1);
         mem.copy(u8, result, cwd);
@@ -577,7 +579,7 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 {
 }
 
 test "os.path.resolve" {
-    const cwd = try os.getCwd(debug.global_allocator);
+    const cwd = try os.getCwdAlloc(debug.global_allocator);
     if (is_windows) {
         if (windowsParsePath(cwd).kind == WindowsPath.Kind.Drive) {
             cwd[0] = asciiUpper(cwd[0]);
@@ -591,7 +593,7 @@ test "os.path.resolve" {
 
 test "os.path.resolveWindows" {
     if (is_windows) {
-        const cwd = try os.getCwd(debug.global_allocator);
+        const cwd = try os.getCwdAlloc(debug.global_allocator);
         const parsed_cwd = windowsParsePath(cwd);
         {
             const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" });
std/os/test.zig
@@ -10,9 +10,9 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
 const AtomicOrder = builtin.AtomicOrder;
 
 test "makePath, put some files in it, deleteTree" {
-    try os.makePath(a, "os_test_tmp/b/c");
-    try io.writeFile(a, "os_test_tmp/b/c/file.txt", "nonsense");
-    try io.writeFile(a, "os_test_tmp/b/file2.txt", "blah");
+    try os.makePath(a, "os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c");
+    try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "c" ++ os.path.sep_str ++ "file.txt", "nonsense");
+    try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "b" ++ os.path.sep_str ++ "file2.txt", "blah");
     try os.deleteTree(a, "os_test_tmp");
     if (os.Dir.open(a, "os_test_tmp")) |dir| {
         @panic("expected error");
@@ -23,14 +23,14 @@ test "makePath, put some files in it, deleteTree" {
 
 test "access file" {
     try os.makePath(a, "os_test_tmp");
-    if (os.File.access(a, "os_test_tmp/file.txt")) |ok| {
+    if (os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt")) |ok| {
         @panic("expected error");
     } else |err| {
-        assert(err == error.NotFound);
+        assert(err == error.FileNotFound);
     }
 
-    try io.writeFile(a, "os_test_tmp/file.txt", "");
-    try os.File.access(a, "os_test_tmp/file.txt");
+    try io.writeFile("os_test_tmp" ++ os.path.sep_str ++ "file.txt", "");
+    try os.File.access("os_test_tmp" ++ os.path.sep_str ++ "file.txt");
     try os.deleteTree(a, "os_test_tmp");
 }
 
std/build.zig
@@ -267,7 +267,7 @@ pub const Builder = struct {
             if (self.verbose) {
                 warn("rm {}\n", installed_file);
             }
-            _ = os.deleteFile(self.allocator, installed_file);
+            _ = os.deleteFile(installed_file);
         }
 
         // TODO remove empty directories
@@ -1182,7 +1182,7 @@ pub const LibExeObjStep = struct {
 
         if (self.build_options_contents.len() > 0) {
             const build_options_file = try os.path.join(builder.allocator, builder.cache_root, builder.fmt("{}_build_options.zig", self.name));
-            try std.io.writeFile(builder.allocator, build_options_file, self.build_options_contents.toSliceConst());
+            try std.io.writeFile(build_options_file, self.build_options_contents.toSliceConst());
             try zig_args.append("--pkg-begin");
             try zig_args.append("build_options");
             try zig_args.append(builder.pathFromRoot(build_options_file));
@@ -1917,7 +1917,7 @@ pub const WriteFileStep = struct {
             warn("unable to make path {}: {}\n", full_path_dir, @errorName(err));
             return err;
         };
-        io.writeFile(self.builder.allocator, full_path, self.data) catch |err| {
+        io.writeFile(full_path, self.data) catch |err| {
             warn("unable to write {}: {}\n", full_path, @errorName(err));
             return err;
         };
std/cstr.zig
@@ -9,10 +9,9 @@ pub const line_sep = switch (builtin.os) {
     else => "\n",
 };
 
+/// Deprecated, use mem.len
 pub fn len(ptr: [*]const u8) usize {
-    var count: usize = 0;
-    while (ptr[count] != 0) : (count += 1) {}
-    return count;
+    return mem.len(u8, ptr);
 }
 
 pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
@@ -27,12 +26,14 @@ pub fn cmp(a: [*]const u8, b: [*]const u8) i8 {
     }
 }
 
+/// Deprecated, use mem.toSliceConst
 pub fn toSliceConst(str: [*]const u8) []const u8 {
-    return str[0..len(str)];
+    return mem.toSliceConst(u8, str);
 }
 
+/// Deprecated, use mem.toSlice
 pub fn toSlice(str: [*]u8) []u8 {
-    return str[0..len(str)];
+    return mem.toSlice(u8, str);
 }
 
 test "cstr fns" {
std/io.zig
@@ -254,9 +254,8 @@ pub fn OutStream(comptime WriteError: type) type {
     };
 }
 
-/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
-pub fn writeFile(allocator: *mem.Allocator, path: []const u8, data: []const u8) !void {
-    var file = try File.openWrite(allocator, path);
+pub fn writeFile(path: []const u8, data: []const u8) !void {
+    var file = try File.openWrite(path);
     defer file.close();
     try file.write(data);
 }
@@ -268,7 +267,7 @@ pub fn readFileAlloc(allocator: *mem.Allocator, path: []const u8) ![]u8 {
 
 /// On success, caller owns returned buffer.
 pub fn readFileAllocAligned(allocator: *mem.Allocator, path: []const u8, comptime A: u29) ![]align(A) u8 {
-    var file = try File.openRead(allocator, path);
+    var file = try File.openRead(path);
     defer file.close();
 
     const size = try file.getEndPos();
std/io_test.zig
@@ -45,7 +45,7 @@ test "write a file, read it, then delete it" {
         assert(mem.eql(u8, contents["begin".len .. contents.len - "end".len], data));
         assert(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
     }
-    try os.deleteFile(allocator, tmp_file_name);
+    try os.deleteFile(tmp_file_name);
 }
 
 test "BufferOutStream" {
std/mem.zig
@@ -179,8 +179,8 @@ pub fn secureZero(comptime T: type, s: []T) void {
     // NOTE: We do not use a volatile slice cast here since LLVM cannot
     // see that it can be replaced by a memset.
     const ptr = @ptrCast([*]volatile u8, s.ptr);
-    const len = s.len * @sizeOf(T);
-    @memset(ptr, 0, len);
+    const length = s.len * @sizeOf(T);
+    @memset(ptr, 0, length);
 }
 
 test "mem.secureZero" {
@@ -252,6 +252,20 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) bool {
     return true;
 }
 
+pub fn len(comptime T: type, ptr: [*]const T) usize {
+    var count: usize = 0;
+    while (ptr[count] != 0) : (count += 1) {}
+    return count;
+}
+
+pub fn toSliceConst(comptime T: type, ptr: [*]const T) []const T {
+    return ptr[0..len(T, ptr)];
+}
+
+pub fn toSlice(comptime T: type, ptr: [*]T) []T {
+    return ptr[0..len(T, ptr)];
+}
+
 /// Returns true if all elements in a slice are equal to the scalar value provided
 pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
     for (slice) |item| {
@@ -809,3 +823,4 @@ pub fn endianSwap(comptime T: type, x: T) T {
 test "std.mem.endianSwap" {
     assert(endianSwap(u32, 0xDEADBEEF) == 0xEFBEADDE);
 }
+
std/unicode.zig
@@ -247,6 +247,8 @@ pub const Utf16LeIterator = struct {
     }
 
     pub fn nextCodepoint(it: *Utf16LeIterator) !?u32 {
+        assert(it.i <= it.bytes.len);
+        if (it.i == it.bytes.len) return null;
         const c0: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
         if (c0 & ~u32(0x03ff) == 0xd800) {
             // surrogate pair
@@ -254,10 +256,12 @@ pub const Utf16LeIterator = struct {
             if (it.i >= it.bytes.len) return error.DanglingSurrogateHalf;
             const c1: u32 = mem.readIntLE(u16, it.bytes[it.i .. it.i + 2]);
             if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
+            it.i += 2;
             return 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
         } else if (c0 & ~u32(0x03ff) == 0xdc00) {
             return error.UnexpectedSecondSurrogateHalf;
         } else {
+            it.i += 2;
             return c0;
         }
     }
@@ -490,15 +494,15 @@ pub fn utf16leToUtf8Alloc(allocator: *mem.Allocator, utf16le: []const u16) ![]u8
     return result.toOwnedSlice();
 }
 
-pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !void {
-    var out_index: usize = 0;
+/// Asserts that the output buffer is big enough.
+/// Returns end index.
+pub fn utf16leToUtf8(utf8: []u8, utf16le: []const u16) !usize {
+    var end_index: usize = 0;
     var it = Utf16LeIterator.init(utf16le);
     while (try it.nextCodepoint()) |codepoint| {
-        const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
-        try result.resize(result.len + utf8_len);
-        assert((utf8Encode(codepoint, result.items[out_index..]) catch unreachable) == utf8_len);
-        out_index += utf8_len;
+        end_index += try utf8Encode(codepoint, utf8[end_index..]);
     }
+    return end_index;
 }
 
 test "utf16leToUtf8" {
@@ -508,14 +512,14 @@ test "utf16leToUtf8" {
     {
         mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little);
         mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little);
-        const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+        const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
         assert(mem.eql(u8, utf8, "Aa"));
     }
 
     {
         mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little);
         mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little);
-        const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+        const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
         assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
     }
 
@@ -523,7 +527,7 @@ test "utf16leToUtf8" {
         // the values just outside the surrogate half range
         mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little);
         mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little);
-        const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+        const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
         assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
     }
 
@@ -531,7 +535,7 @@ test "utf16leToUtf8" {
         // smallest surrogate pair
         mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little);
         mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
-        const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+        const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
         assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
     }
 
@@ -539,14 +543,14 @@ test "utf16leToUtf8" {
         // largest surrogate pair
         mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
         mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little);
-        const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+        const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
         assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
     }
 
     {
         mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
         mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
-        const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+        const utf8 = try utf16leToUtf8Alloc(std.debug.global_allocator, utf16le);
         assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
     }
 }
@@ -567,3 +571,20 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![]u16
     try result.append(0);
     return result.toOwnedSlice();
 }
+
+/// Returns index of next character. If exact fit, returned index equals output slice length.
+/// If ran out of room, returned index equals output slice length + 1.
+/// TODO support codepoints bigger than 16 bits
+pub fn utf8ToUtf16Le(utf16le: []u16, utf8: []const u8) !usize {
+    const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
+    var end_index: usize = 0;
+
+    var it = (try Utf8View.init(utf8)).iterator();
+    while (it.nextCodepoint()) |codepoint| {
+        if (end_index == utf16le_as_bytes.len) return (end_index / 2) + 1;
+        // TODO surrogate pairs
+        mem.writeInt(utf16le_as_bytes[end_index..], @intCast(u16, codepoint), builtin.Endian.Little);
+        end_index += 2;
+    }
+    return end_index / 2;
+}