diff --git a/CMakeLists.txt b/CMakeLists.txt index 4dd6a1dcfa..d6bd8a6c2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -596,6 +596,7 @@ set(ZIG_STD_FILES "os/windows/ntdll.zig" "os/windows/ole32.zig" "os/windows/shell32.zig" + "os/windows/tls.zig" "os/windows/util.zig" "os/zen.zig" "pdb.zig" diff --git a/build.zig b/build.zig index a41a5f808b..5c7c5b8a18 100644 --- a/build.zig +++ b/build.zig @@ -16,7 +16,10 @@ pub fn build(b: *Builder) !void { var docgen_exe = b.addExecutable("docgen", "doc/docgen.zig"); const rel_zig_exe = try os.path.relative(b.allocator, b.build_root, b.zig_exe); - const langref_out_path = os.path.join(b.allocator, b.cache_root, "langref.html") catch unreachable; + const langref_out_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, "langref.html" }, + ) catch unreachable; var docgen_cmd = b.addCommand(null, b.env_map, [][]const u8{ docgen_exe.getOutputPath(), rel_zig_exe, @@ -125,13 +128,19 @@ fn dependOnLib(b: *Builder, lib_exe_obj: var, dep: LibraryDep) void { for (dep.libdirs.toSliceConst()) |lib_dir| { lib_exe_obj.addLibPath(lib_dir); } - const lib_dir = os.path.join(b.allocator, dep.prefix, "lib") catch unreachable; + const lib_dir = os.path.join( + b.allocator, + [][]const u8{ dep.prefix, "lib" }, + ) catch unreachable; for (dep.system_libs.toSliceConst()) |lib| { const static_bare_name = if (mem.eql(u8, lib, "curses")) ([]const u8)("libncurses.a") else b.fmt("lib{}.a", lib); - const static_lib_name = os.path.join(b.allocator, lib_dir, static_bare_name) catch unreachable; + const static_lib_name = os.path.join( + b.allocator, + [][]const u8{ lib_dir, static_bare_name }, + ) catch unreachable; const have_static = fileExists(static_lib_name) catch unreachable; if (have_static) { lib_exe_obj.addObjectFile(static_lib_name); @@ -159,7 +168,11 @@ fn fileExists(filename: []const u8) !bool { fn addCppLib(b: *Builder, lib_exe_obj: var, cmake_binary_dir: []const u8, lib_name: []const u8) void { const lib_prefix = if (lib_exe_obj.target.isWindows()) "" else "lib"; - lib_exe_obj.addObjectFile(os.path.join(b.allocator, cmake_binary_dir, "zig_cpp", b.fmt("{}{}{}", lib_prefix, lib_name, lib_exe_obj.target.libFileExt())) catch unreachable); + lib_exe_obj.addObjectFile(os.path.join(b.allocator, [][]const u8{ + cmake_binary_dir, + "zig_cpp", + b.fmt("{}{}{}", lib_prefix, lib_name, lib_exe_obj.target.libFileExt()), + }) catch unreachable); } const LibraryDep = struct { @@ -235,8 +248,11 @@ fn findLLVM(b: *Builder, llvm_config_exe: []const u8) !LibraryDep { pub fn installStdLib(b: *Builder, stdlib_files: []const u8) void { var it = mem.tokenize(stdlib_files, ";"); while (it.next()) |stdlib_file| { - const src_path = os.path.join(b.allocator, "std", stdlib_file) catch unreachable; - const dest_path = os.path.join(b.allocator, "lib", "zig", "std", stdlib_file) catch unreachable; + const src_path = os.path.join(b.allocator, [][]const u8{ "std", stdlib_file }) catch unreachable; + const dest_path = os.path.join( + b.allocator, + [][]const u8{ "lib", "zig", "std", stdlib_file }, + ) catch unreachable; b.installFile(src_path, dest_path); } } @@ -244,8 +260,11 @@ pub fn installStdLib(b: *Builder, stdlib_files: []const u8) void { pub fn installCHeaders(b: *Builder, c_header_files: []const u8) void { var it = mem.tokenize(c_header_files, ";"); while (it.next()) |c_header_file| { - const src_path = os.path.join(b.allocator, "c_headers", c_header_file) catch unreachable; - const dest_path = os.path.join(b.allocator, "lib", "zig", "include", c_header_file) catch unreachable; + const src_path = os.path.join(b.allocator, [][]const u8{ "c_headers", c_header_file }) catch unreachable; + const dest_path = os.path.join( + b.allocator, + [][]const u8{ "lib", "zig", "include", c_header_file }, + ) catch unreachable; b.installFile(src_path, dest_path); } } diff --git a/doc/docgen.zig b/doc/docgen.zig index 2489e034bc..14e4700553 100644 --- a/doc/docgen.zig +++ b/doc/docgen.zig @@ -990,13 +990,19 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var try tokenizeAndPrint(tokenizer, out, code.source_token); try out.write(""); const name_plus_ext = try std.fmt.allocPrint(allocator, "{}.zig", code.name); - const tmp_source_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_ext); + const tmp_source_file_name = try os.path.join( + allocator, + [][]const u8{ tmp_dir_name, name_plus_ext }, + ); try io.writeFile(tmp_source_file_name, trimmed_raw_source); switch (code.id) { Code.Id.Exe => |expected_outcome| { const name_plus_bin_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, exe_ext); - const tmp_bin_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_bin_ext); + const tmp_bin_file_name = try os.path.join( + allocator, + [][]const u8{ tmp_dir_name, name_plus_bin_ext }, + ); var build_args = std.ArrayList([]const u8).init(allocator); defer build_args.deinit(); try build_args.appendSlice([][]const u8{ @@ -1024,7 +1030,10 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var } for (code.link_objects) |link_object| { const name_with_ext = try std.fmt.allocPrint(allocator, "{}{}", link_object, obj_ext); - const full_path_object = try os.path.join(allocator, tmp_dir_name, name_with_ext); + const full_path_object = try os.path.join( + allocator, + [][]const u8{ tmp_dir_name, name_with_ext }, + ); try build_args.append("--object"); try build_args.append(full_path_object); try out.print(" --object {}", name_with_ext); @@ -1216,12 +1225,18 @@ fn genHtml(allocator: *mem.Allocator, tokenizer: *Tokenizer, toc: *Toc, out: var }, Code.Id.Obj => |maybe_error_match| { const name_plus_obj_ext = try std.fmt.allocPrint(allocator, "{}{}", code.name, obj_ext); - const tmp_obj_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_obj_ext); + const tmp_obj_file_name = try os.path.join( + allocator, + [][]const u8{ tmp_dir_name, name_plus_obj_ext }, + ); var build_args = std.ArrayList([]const u8).init(allocator); defer build_args.deinit(); const name_plus_h_ext = try std.fmt.allocPrint(allocator, "{}.h", code.name); - const output_h_file_name = try os.path.join(allocator, tmp_dir_name, name_plus_h_ext); + const output_h_file_name = try os.path.join( + allocator, + [][]const u8{ tmp_dir_name, name_plus_h_ext }, + ); try build_args.appendSlice([][]const u8{ zig_exe, diff --git a/doc/langref.html.in b/doc/langref.html.in index 144c8571c4..e3ba0e3956 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -3192,7 +3192,16 @@ fn foo() void { } {#code_end#} {#header_open|Pass-by-value Parameters#}

- In Zig, structs, unions, and enums with payloads can be passed directly to a function: + Primitive types such as {#link|Integers#} and {#link|Floats#} passed as parameters + are copied, and then the copy is available in the function body. This is called "passing by value". + Copying a primitive type is essentially free and typically involves nothing more than + setting a register. +

+

+ Structs, unions, and arrays can sometimes be more efficiently passed as a reference, since a copy + could be arbitrarily expensive depending on the size. When these types are passed + as parameters, Zig may choose to copy and pass by value, or pass by reference, whichever way + Zig decides will be faster. This is made possible, in part, by the fact that parameters are immutable.

{#code_begin|test#} const Point = struct { @@ -3201,20 +3210,20 @@ const Point = struct { }; fn foo(point: Point) i32 { + // Here, `point` could be a reference, or a copy. The function body + // can ignore the difference and treat it as a value. Be very careful + // taking the address of the parameter - it should be treated as if + // the address will become invalid when the function returns. return point.x + point.y; } const assert = @import("std").debug.assert; -test "pass aggregate type by non-copy value to function" { +test "pass struct to function" { assert(foo(Point{ .x = 1, .y = 2 }) == 3); } {#code_end#}

- In this case, the value may be passed by reference, or by value, whichever way - Zig decides will be faster. -

-

For extern functions, Zig follows the C ABI for passing structs and unions by value.

{#header_close#} diff --git a/doc/targets.md b/doc/targets.md deleted file mode 100644 index e5c6ab5534..0000000000 --- a/doc/targets.md +++ /dev/null @@ -1,15 +0,0 @@ -# How to Add Support For More Targets - -Create bootstrap code in std/bootstrap.zig and add conditional compilation -logic. This code is responsible for the real executable entry point, calling -main() and making the exit syscall when main returns. - -How to pass a byvalue struct parameter in the C calling convention is -target-specific. Add logic for how to do function prototypes and function calls -for the target when an exported or external function has a byvalue struct. - -Write the target-specific code in the standard library. - -Update the C integer types to be the correct size for the target. - -Make sure that `c_longdouble` codegens the correct floating point value. diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig index d60892432e..e55d8ccda6 100644 --- a/src-self-hosted/compilation.zig +++ b/src-self-hosted/compilation.zig @@ -487,7 +487,7 @@ pub const Compilation = struct { comp.name = try Buffer.init(comp.arena(), name); comp.llvm_triple = try target.getTriple(comp.arena()); comp.llvm_target = try Target.llvmTargetFromTriple(comp.llvm_triple); - comp.zig_std_dir = try std.os.path.join(comp.arena(), zig_lib_dir, "std"); + comp.zig_std_dir = try std.os.path.join(comp.arena(), [][]const u8{ zig_lib_dir, "std" }); const opt_level = switch (build_mode) { builtin.Mode.Debug => llvm.CodeGenLevelNone, @@ -1198,7 +1198,7 @@ pub const Compilation = struct { const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", file_prefix[0..], suffix); defer self.gpa().free(file_name); - const full_path = try os.path.join(self.gpa(), tmp_dir, file_name[0..]); + const full_path = try os.path.join(self.gpa(), [][]const u8{ tmp_dir, file_name[0..] }); errdefer self.gpa().free(full_path); return Buffer.fromOwnedSlice(self.gpa(), full_path); @@ -1219,7 +1219,7 @@ pub const Compilation = struct { const zig_dir_path = try getZigDir(self.gpa()); defer self.gpa().free(zig_dir_path); - const tmp_dir = try os.path.join(self.arena(), zig_dir_path, comp_dir_name[0..]); + const tmp_dir = try os.path.join(self.arena(), [][]const u8{ zig_dir_path, comp_dir_name[0..] }); try os.makePath(self.gpa(), tmp_dir); return tmp_dir; } diff --git a/src-self-hosted/introspect.zig b/src-self-hosted/introspect.zig index d41f82f755..0a7f63b4f1 100644 --- a/src-self-hosted/introspect.zig +++ b/src-self-hosted/introspect.zig @@ -8,10 +8,10 @@ const warn = std.debug.warn; /// Caller must free result pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![]u8 { - const test_zig_dir = try os.path.join(allocator, test_path, "lib", "zig"); + const test_zig_dir = try os.path.join(allocator, [][]const u8{ test_path, "lib", "zig" }); errdefer allocator.free(test_zig_dir); - const test_index_file = try os.path.join(allocator, test_zig_dir, "std", "index.zig"); + const test_index_file = try os.path.join(allocator, [][]const u8{ test_zig_dir, "std", "index.zig" }); defer allocator.free(test_index_file); var file = try os.File.openRead(test_index_file); diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig index 18d2daf0c2..edcb9dc579 100644 --- a/src-self-hosted/libc_installation.zig +++ b/src-self-hosted/libc_installation.zig @@ -230,7 +230,7 @@ pub const LibCInstallation = struct { while (path_i < search_paths.len) : (path_i += 1) { const search_path_untrimmed = search_paths.at(search_paths.len - path_i - 1); const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " "); - const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h"); + const stdlib_path = try std.os.path.join(loop.allocator, [][]const u8{ search_path, "stdlib.h" }); defer loop.allocator.free(stdlib_path); if (try fileExists(stdlib_path)) { @@ -254,7 +254,10 @@ pub const LibCInstallation = struct { const stream = &std.io.BufferOutStream.init(&result_buf).stream; try stream.print("{}\\Include\\{}\\ucrt", search.path, search.version); - const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h"); + const stdlib_path = try std.os.path.join( + loop.allocator, + [][]const u8{ result_buf.toSliceConst(), "stdlib.h" }, + ); defer loop.allocator.free(stdlib_path); if (try fileExists(stdlib_path)) { @@ -283,7 +286,10 @@ pub const LibCInstallation = struct { builtin.Arch.aarch64v8 => try stream.write("arm"), else => return error.UnsupportedArchitecture, } - const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib"); + const ucrt_lib_path = try std.os.path.join( + loop.allocator, + [][]const u8{ result_buf.toSliceConst(), "ucrt.lib" }, + ); defer loop.allocator.free(ucrt_lib_path); if (try fileExists(ucrt_lib_path)) { self.lib_dir = result_buf.toOwnedSlice(); @@ -358,7 +364,10 @@ pub const LibCInstallation = struct { builtin.Arch.aarch64v8 => try stream.write("arm\\"), else => return error.UnsupportedArchitecture, } - const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib"); + const kernel32_path = try std.os.path.join( + loop.allocator, + [][]const u8{ result_buf.toSliceConst(), "kernel32.lib" }, + ); defer loop.allocator.free(kernel32_path); if (try fileExists(kernel32_path)) { self.kernel32_lib_dir = result_buf.toOwnedSlice(); diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 1b32533ebc..fadc9b0189 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -315,7 +315,7 @@ fn constructLinkerArgsElf(ctx: *Context) !void { } fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void { - const full_path = try std.os.path.join(&ctx.arena.allocator, dirname, basename); + const full_path = try std.os.path.join(&ctx.arena.allocator, [][]const u8{ dirname, basename }); const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path); try ctx.args.append(full_path_with_null.ptr); } diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig index f6ee9a0513..64aa729469 100644 --- a/src-self-hosted/main.zig +++ b/src-self-hosted/main.zig @@ -757,7 +757,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro var group = event.Group(FmtError!void).init(fmt.loop); while (try dir.next()) |entry| { if (entry.kind == std.os.Dir.Entry.Kind.Directory or mem.endsWith(u8, entry.name, ".zig")) { - const full_path = try os.path.join(fmt.loop.allocator, file_path, entry.name); + const full_path = try os.path.join(fmt.loop.allocator, [][]const u8{ file_path, entry.name }); try group.call(fmtPath, fmt, full_path, check_mode); } } diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig index ff5b96df84..de551cf7f7 100644 --- a/src-self-hosted/test.zig +++ b/src-self-hosted/test.zig @@ -87,7 +87,7 @@ pub const TestContext = struct { ) !void { var file_index_buf: [20]u8 = undefined; const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); - const file1_path = try std.os.path.join(allocator, tmp_dir_name, file_index, file1); + const file1_path = try std.os.path.join(allocator, [][]const u8{ tmp_dir_name, file_index, file1 }); if (std.os.path.dirname(file1_path)) |dirname| { try std.os.makePath(allocator, dirname); @@ -120,7 +120,7 @@ pub const TestContext = struct { ) !void { var file_index_buf: [20]u8 = undefined; const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr()); - const file1_path = try std.os.path.join(allocator, tmp_dir_name, file_index, file1); + const file1_path = try std.os.path.join(allocator, [][]const u8{ tmp_dir_name, file_index, file1 }); const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", file1_path, Target(Target.Native).exeFileExt()); if (std.os.path.dirname(file1_path)) |dirname| { diff --git a/src/all_types.hpp b/src/all_types.hpp index c4c9e13cfb..5af4e71157 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -544,12 +544,7 @@ struct AstNodeDefer { }; struct AstNodeVariableDeclaration { - VisibMod visib_mod; Buf *symbol; - bool is_const; - bool is_comptime; - bool is_export; - bool is_extern; // one or both of type and expr will be non null AstNode *type; AstNode *expr; @@ -559,6 +554,13 @@ struct AstNodeVariableDeclaration { AstNode *align_expr; // populated if the "section(S)" is present AstNode *section_expr; + Token *threadlocal_tok; + + VisibMod visib_mod; + bool is_const; + bool is_comptime; + bool is_export; + bool is_extern; }; struct AstNodeTestDecl { @@ -1873,6 +1875,7 @@ struct ZigVar { bool shadowable; bool src_is_const; bool gen_is_const; + bool is_thread_local; }; struct ErrorTableEntry { diff --git a/src/analyze.cpp b/src/analyze.cpp index d92d337c69..83a576554a 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -28,28 +28,10 @@ static Error ATTRIBUTE_MUST_USE resolve_enum_zero_bits(CodeGen *g, ZigType *enum static Error ATTRIBUTE_MUST_USE resolve_union_zero_bits(CodeGen *g, ZigType *union_type); static void analyze_fn_body(CodeGen *g, ZigFn *fn_table_entry); -ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg) { - if (node->owner->c_import_node != nullptr) { - // if this happens, then translate_c generated code that - // failed semantic analysis, which isn't supposed to happen - ErrorMsg *err = add_node_error(g, node->owner->c_import_node, - buf_sprintf("compiler bug: @cImport generated invalid zig code")); - - add_error_note(g, err, node, msg); - - g->errors.append(err); - return err; - } - - ErrorMsg *err = err_msg_create_with_line(node->owner->path, node->line, node->column, - node->owner->source_code, node->owner->line_offsets, msg); - - g->errors.append(err); - return err; -} - -ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg) { - if (node->owner->c_import_node != nullptr) { +static ErrorMsg *add_error_note_token(CodeGen *g, ErrorMsg *parent_msg, ImportTableEntry *owner, Token *token, + Buf *msg) +{ + if (owner->c_import_node != nullptr) { // if this happens, then translate_c generated code that // failed semantic analysis, which isn't supposed to happen @@ -64,13 +46,46 @@ ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *m return note; } - ErrorMsg *err = err_msg_create_with_line(node->owner->path, node->line, node->column, - node->owner->source_code, node->owner->line_offsets, msg); + ErrorMsg *err = err_msg_create_with_line(owner->path, token->start_line, token->start_column, + owner->source_code, owner->line_offsets, msg); err_msg_add_note(parent_msg, err); return err; } +ErrorMsg *add_token_error(CodeGen *g, ImportTableEntry *owner, Token *token, Buf *msg) { + if (owner->c_import_node != nullptr) { + // if this happens, then translate_c generated code that + // failed semantic analysis, which isn't supposed to happen + ErrorMsg *err = add_node_error(g, owner->c_import_node, + buf_sprintf("compiler bug: @cImport generated invalid zig code")); + + add_error_note_token(g, err, owner, token, msg); + + g->errors.append(err); + return err; + } + ErrorMsg *err = err_msg_create_with_line(owner->path, token->start_line, token->start_column, + owner->source_code, owner->line_offsets, msg); + + g->errors.append(err); + return err; +} + +ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg) { + Token fake_token; + fake_token.start_line = node->line; + fake_token.start_column = node->column; + return add_token_error(g, node->owner, &fake_token, msg); +} + +ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg) { + Token fake_token; + fake_token.start_line = node->line; + fake_token.start_column = node->column; + return add_error_note_token(g, parent_msg, node->owner, &fake_token, msg); +} + ZigType *new_type_table_entry(ZigTypeId id) { ZigType *entry = allocate(1); entry->id = id; @@ -3668,6 +3683,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { bool is_const = var_decl->is_const; bool is_extern = var_decl->is_extern; bool is_export = var_decl->is_export; + bool is_thread_local = var_decl->threadlocal_tok != nullptr; ZigType *explicit_type = nullptr; if (var_decl->type) { @@ -3727,6 +3743,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { tld_var->var = add_variable(g, source_node, tld_var->base.parent_scope, var_decl->symbol, is_const, init_val, &tld_var->base, type); tld_var->var->linkage = linkage; + tld_var->var->is_thread_local = is_thread_local; if (implicit_type != nullptr && type_is_invalid(implicit_type)) { tld_var->var->var_type = g->builtin_types.entry_invalid; @@ -3747,6 +3764,10 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) { } } + if (is_thread_local && is_const) { + add_node_error(g, source_node, buf_sprintf("threadlocal variable cannot be constant")); + } + g->global_vars.append(tld_var); } diff --git a/src/analyze.hpp b/src/analyze.hpp index f558fa44b0..9773782510 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -12,6 +12,7 @@ void semantic_analyze(CodeGen *g); ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg); +ErrorMsg *add_token_error(CodeGen *g, ImportTableEntry *owner, Token *token, Buf *msg); ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg); ZigType *new_type_table_entry(ZigTypeId id); ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const); diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 994ba5f5b1..34a7faa2a5 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -132,6 +132,10 @@ static const char *const_or_var_string(bool is_const) { return is_const ? "const" : "var"; } +static const char *thread_local_string(Token *tok) { + return (tok == nullptr) ? "" : "threadlocal "; +} + const char *container_string(ContainerKind kind) { switch (kind) { case ContainerKindEnum: return "enum"; @@ -554,8 +558,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { { const char *pub_str = visib_mod_string(node->data.variable_declaration.visib_mod); const char *extern_str = extern_string(node->data.variable_declaration.is_extern); + const char *thread_local_str = thread_local_string(node->data.variable_declaration.threadlocal_tok); const char *const_or_var = const_or_var_string(node->data.variable_declaration.is_const); - fprintf(ar->f, "%s%s%s ", pub_str, extern_str, const_or_var); + fprintf(ar->f, "%s%s%s%s ", pub_str, extern_str, thread_local_str, const_or_var); print_symbol(ar, node->data.variable_declaration.symbol); if (node->data.variable_declaration.type) { diff --git a/src/codegen.cpp b/src/codegen.cpp index 1a70901cf0..f7adbf6bdc 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -88,7 +88,7 @@ static const char *symbols_that_llvm_depends_on[] = { }; CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode, - Buf *zig_lib_dir) + Buf *zig_lib_dir, Buf *override_std_dir) { CodeGen *g = allocate(1); @@ -96,8 +96,12 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out g->zig_lib_dir = zig_lib_dir; - g->zig_std_dir = buf_alloc(); - os_path_join(zig_lib_dir, buf_create_from_str("std"), g->zig_std_dir); + if (override_std_dir == nullptr) { + g->zig_std_dir = buf_alloc(); + os_path_join(zig_lib_dir, buf_create_from_str("std"), g->zig_std_dir); + } else { + g->zig_std_dir = override_std_dir; + } g->zig_c_headers_dir = buf_alloc(); os_path_join(zig_lib_dir, buf_create_from_str("include"), g->zig_c_headers_dir); @@ -2582,6 +2586,8 @@ static LLVMValueRef gen_rem(CodeGen *g, bool want_runtime_safety, bool want_fast } +typedef LLVMValueRef (*BuildBinOpFunc)(LLVMBuilderRef, LLVMValueRef, LLVMValueRef, const char *); + static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, IrInstructionBinOp *bin_op_instruction) { @@ -2640,50 +2646,71 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, } else { zig_unreachable(); } + case IrBinOpMult: + case IrBinOpMultWrap: case IrBinOpAdd: case IrBinOpAddWrap: + case IrBinOpSub: + case IrBinOpSubWrap: { + // These are lookup table using the AddSubMul enum as the lookup. + // If AddSubMul ever changes, then these tables will be out of + // date. + static const BuildBinOpFunc float_op[3] = { LLVMBuildFAdd, LLVMBuildFSub, LLVMBuildFMul }; + static const BuildBinOpFunc wrap_op[3] = { LLVMBuildAdd, LLVMBuildSub, LLVMBuildMul }; + static const BuildBinOpFunc signed_op[3] = { LLVMBuildNSWAdd, LLVMBuildNSWSub, LLVMBuildNSWMul }; + static const BuildBinOpFunc unsigned_op[3] = { LLVMBuildNUWAdd, LLVMBuildNUWSub, LLVMBuildNUWMul }; + + bool is_vector = type_entry->id == ZigTypeIdVector; + bool is_wrapping = (op_id == IrBinOpSubWrap || op_id == IrBinOpAddWrap || op_id == IrBinOpMultWrap); + AddSubMul add_sub_mul = + op_id == IrBinOpAdd || op_id == IrBinOpAddWrap ? AddSubMulAdd : + op_id == IrBinOpSub || op_id == IrBinOpSubWrap ? AddSubMulSub : + AddSubMulMul; + + // The code that is generated for vectors and scalars are the same, + // so we can just set type_entry to the vectors elem_type an avoid + // a lot of repeated code. + if (is_vector) + type_entry = type_entry->data.vector.elem_type; + if (type_entry->id == ZigTypeIdPointer) { assert(type_entry->data.pointer.ptr_len == PtrLenUnknown); + LLVMValueRef subscript_value; + if (is_vector) + zig_panic("TODO: Implement vector operations on pointers."); + + switch (add_sub_mul) { + case AddSubMulAdd: + subscript_value = op2_value; + break; + case AddSubMulSub: + subscript_value = LLVMBuildNeg(g->builder, op2_value, ""); + break; + case AddSubMulMul: + zig_unreachable(); + } + // TODO runtime safety - return LLVMBuildInBoundsGEP(g->builder, op1_value, &op2_value, 1, ""); + return LLVMBuildInBoundsGEP(g->builder, op1_value, &subscript_value, 1, ""); } else if (type_entry->id == ZigTypeIdFloat) { ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); - return LLVMBuildFAdd(g->builder, op1_value, op2_value, ""); + return float_op[add_sub_mul](g->builder, op1_value, op2_value, ""); } else if (type_entry->id == ZigTypeIdInt) { - bool is_wrapping = (op_id == IrBinOpAddWrap); if (is_wrapping) { - return LLVMBuildAdd(g->builder, op1_value, op2_value, ""); + return wrap_op[add_sub_mul](g->builder, op1_value, op2_value, ""); } else if (want_runtime_safety) { - return gen_overflow_op(g, type_entry, AddSubMulAdd, op1_value, op2_value); + if (is_vector) + zig_panic("TODO: Implement runtime safety vector operations."); + return gen_overflow_op(g, type_entry, add_sub_mul, op1_value, op2_value); } else if (type_entry->data.integral.is_signed) { - return LLVMBuildNSWAdd(g->builder, op1_value, op2_value, ""); + return signed_op[add_sub_mul](g->builder, op1_value, op2_value, ""); } else { - return LLVMBuildNUWAdd(g->builder, op1_value, op2_value, ""); - } - } else if (type_entry->id == ZigTypeIdVector) { - ZigType *elem_type = type_entry->data.vector.elem_type; - if (elem_type->id == ZigTypeIdFloat) { - ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); - return LLVMBuildFAdd(g->builder, op1_value, op2_value, ""); - } else if (elem_type->id == ZigTypeIdPointer) { - zig_panic("TODO codegen for pointers in vectors"); - } else if (elem_type->id == ZigTypeIdInt) { - bool is_wrapping = (op_id == IrBinOpAddWrap); - if (is_wrapping) { - return LLVMBuildAdd(g->builder, op1_value, op2_value, ""); - } else if (want_runtime_safety) { - zig_panic("TODO runtime safety for vector integer addition"); - } else if (elem_type->data.integral.is_signed) { - return LLVMBuildNSWAdd(g->builder, op1_value, op2_value, ""); - } else { - return LLVMBuildNUWAdd(g->builder, op1_value, op2_value, ""); - } - } else { - zig_unreachable(); + return unsigned_op[add_sub_mul](g->builder, op1_value, op2_value, ""); } } else { zig_unreachable(); } + } case IrBinOpBinOr: return LLVMBuildOr(g->builder, op1_value, op2_value, ""); case IrBinOpBinXor: @@ -2728,49 +2755,6 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, return ZigLLVMBuildLShrExact(g->builder, op1_value, op2_casted, ""); } } - case IrBinOpSub: - case IrBinOpSubWrap: - if (type_entry->id == ZigTypeIdPointer) { - assert(type_entry->data.pointer.ptr_len == PtrLenUnknown); - // TODO runtime safety - LLVMValueRef subscript_value = LLVMBuildNeg(g->builder, op2_value, ""); - return LLVMBuildInBoundsGEP(g->builder, op1_value, &subscript_value, 1, ""); - } else if (type_entry->id == ZigTypeIdFloat) { - ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); - return LLVMBuildFSub(g->builder, op1_value, op2_value, ""); - } else if (type_entry->id == ZigTypeIdInt) { - bool is_wrapping = (op_id == IrBinOpSubWrap); - if (is_wrapping) { - return LLVMBuildSub(g->builder, op1_value, op2_value, ""); - } else if (want_runtime_safety) { - return gen_overflow_op(g, type_entry, AddSubMulSub, op1_value, op2_value); - } else if (type_entry->data.integral.is_signed) { - return LLVMBuildNSWSub(g->builder, op1_value, op2_value, ""); - } else { - return LLVMBuildNUWSub(g->builder, op1_value, op2_value, ""); - } - } else { - zig_unreachable(); - } - case IrBinOpMult: - case IrBinOpMultWrap: - if (type_entry->id == ZigTypeIdFloat) { - ZigLLVMSetFastMath(g->builder, ir_want_fast_math(g, &bin_op_instruction->base)); - return LLVMBuildFMul(g->builder, op1_value, op2_value, ""); - } else if (type_entry->id == ZigTypeIdInt) { - bool is_wrapping = (op_id == IrBinOpMultWrap); - if (is_wrapping) { - return LLVMBuildMul(g->builder, op1_value, op2_value, ""); - } else if (want_runtime_safety) { - return gen_overflow_op(g, type_entry, AddSubMulMul, op1_value, op2_value); - } else if (type_entry->data.integral.is_signed) { - return LLVMBuildNSWMul(g->builder, op1_value, op2_value, ""); - } else { - return LLVMBuildNUWMul(g->builder, op1_value, op2_value, ""); - } - } else { - zig_unreachable(); - } case IrBinOpDivUnspecified: return gen_div(g, want_runtime_safety, ir_want_fast_math(g, &bin_op_instruction->base), op1_value, op2_value, type_entry, DivKindFloat); @@ -6361,6 +6345,12 @@ static void validate_inline_fns(CodeGen *g) { report_errors_and_maybe_exit(g); } +static void set_global_tls(CodeGen *g, ZigVar *var, LLVMValueRef global_value) { + if (var->is_thread_local && !g->is_single_threaded) { + LLVMSetThreadLocalMode(global_value, LLVMGeneralDynamicTLSModel); + } +} + static void do_code_gen(CodeGen *g) { assert(!g->errors.length); @@ -6445,6 +6435,7 @@ static void do_code_gen(CodeGen *g) { maybe_import_dll(g, global_value, GlobalLinkageIdStrong); LLVMSetAlignment(global_value, var->align_bytes); LLVMSetGlobalConstant(global_value, var->gen_is_const); + set_global_tls(g, var, global_value); } } else { bool exported = (var->linkage == VarLinkageExport); @@ -6470,6 +6461,7 @@ static void do_code_gen(CodeGen *g) { } LLVMSetGlobalConstant(global_value, var->gen_is_const); + set_global_tls(g, var, global_value); } var->value_ref = global_value; @@ -7520,6 +7512,7 @@ static Error define_builtin_compile_vars(CodeGen *g) { g->compile_var_package = new_package(buf_ptr(this_dir), builtin_zig_basename); g->root_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package); g->std_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package); + g->std_package->package_table.put(buf_create_from_str("std"), g->std_package); g->compile_var_import = add_source_file(g, g->compile_var_package, builtin_zig_path, contents); scan_import(g, g->compile_var_import); @@ -7560,7 +7553,13 @@ static void init(CodeGen *g) { LLVMTargetRef target_ref; char *err_msg = nullptr; if (LLVMGetTargetFromTriple(buf_ptr(&g->triple_str), &target_ref, &err_msg)) { - zig_panic("unable to create target based on: %s", buf_ptr(&g->triple_str)); + fprintf(stderr, + "Zig is expecting LLVM to understand this target: '%s'\n" + "However LLVM responded with: \"%s\"\n" + "Zig is unable to continue. This is a bug in Zig:\n" + "https://github.com/ziglang/zig/issues/438\n" + , buf_ptr(&g->triple_str), err_msg); + exit(1); } bool is_optimized = g->build_mode != BuildModeDebug; @@ -8349,8 +8348,12 @@ static void add_cache_pkg(CodeGen *g, CacheHash *ch, PackageTableEntry *pkg) { if (!entry) break; - cache_buf(ch, entry->key); - add_cache_pkg(g, ch, entry->value); + // TODO: I think we need a more sophisticated detection of + // packages we have already seen + if (entry->value != pkg) { + cache_buf(ch, entry->key); + add_cache_pkg(g, ch, entry->value); + } } } diff --git a/src/codegen.hpp b/src/codegen.hpp index 6f1cdfb677..4bd8f2dcca 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -15,7 +15,7 @@ #include CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode, - Buf *zig_lib_dir); + Buf *zig_lib_dir, Buf *override_std_dir); void codegen_set_clang_argv(CodeGen *codegen, const char **args, size_t len); void codegen_set_llvm_argv(CodeGen *codegen, const char **args, size_t len); diff --git a/src/ir.cpp b/src/ir.cpp index 3cbbdc8103..02b2b12230 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -5204,6 +5204,10 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod add_node_error(irb->codegen, variable_declaration->section_expr, buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol))); } + if (variable_declaration->threadlocal_tok != nullptr) { + add_token_error(irb->codegen, node->owner, variable_declaration->threadlocal_tok, + buf_sprintf("function-local variable '%s' cannot be threadlocal", buf_ptr(variable_declaration->symbol))); + } // Temporarily set the name of the IrExecutable to the VariableDeclaration // so that the struct or enum from the init expression inherits the name. diff --git a/src/link.cpp b/src/link.cpp index 593f7f309f..58221a99ea 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -42,7 +42,7 @@ static Buf *build_a_raw(CodeGen *parent_gen, const char *aname, Buf *full_path) } CodeGen *child_gen = codegen_create(full_path, child_target, child_out_type, - parent_gen->build_mode, parent_gen->zig_lib_dir); + parent_gen->build_mode, parent_gen->zig_lib_dir, parent_gen->zig_std_dir); child_gen->out_h_path = nullptr; child_gen->verbose_tokenize = parent_gen->verbose_tokenize; diff --git a/src/main.cpp b/src/main.cpp index 81f49089be..73a1d75d42 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -74,6 +74,7 @@ static int print_full_usage(const char *arg0) { " -dirafter [dir] same as -isystem but do it last\n" " -isystem [dir] add additional search path for other .h files\n" " -mllvm [arg] forward an arg to LLVM's option processing\n" + " --override-std-dir [arg] use an alternate Zig standard library\n" "\n" "Link Options:\n" " --dynamic-linker [path] set the path to ld.so\n" @@ -395,6 +396,7 @@ int main(int argc, char **argv) { bool system_linker_hack = false; TargetSubsystem subsystem = TargetSubsystemAuto; bool is_single_threaded = false; + Buf *override_std_dir = nullptr; if (argc >= 2 && strcmp(argv[1], "build") == 0) { Buf zig_exe_path_buf = BUF_INIT; @@ -430,7 +432,8 @@ int main(int argc, char **argv) { Buf *build_runner_path = buf_alloc(); os_path_join(get_zig_special_dir(), buf_create_from_str("build_runner.zig"), build_runner_path); - CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir()); + CodeGen *g = codegen_create(build_runner_path, nullptr, OutTypeExe, BuildModeDebug, get_zig_lib_dir(), + override_std_dir); g->enable_time_report = timing_info; buf_init_from_str(&g->cache_dir, cache_dir ? cache_dir : default_zig_cache_name); codegen_set_out_name(g, buf_create_from_str("build")); @@ -645,6 +648,8 @@ int main(int argc, char **argv) { clang_argv.append(argv[i]); llvm_argv.append(argv[i]); + } else if (strcmp(arg, "--override-std-dir") == 0) { + override_std_dir = buf_create_from_str(argv[i]); } else if (strcmp(arg, "--library-path") == 0 || strcmp(arg, "-L") == 0) { lib_dirs.append(argv[i]); } else if (strcmp(arg, "--library") == 0) { @@ -819,7 +824,7 @@ int main(int argc, char **argv) { switch (cmd) { case CmdBuiltin: { - CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir()); + CodeGen *g = codegen_create(nullptr, target, out_type, build_mode, get_zig_lib_dir(), override_std_dir); g->is_single_threaded = is_single_threaded; Buf *builtin_source = codegen_generate_builtin_source(g); if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) { @@ -878,7 +883,8 @@ int main(int argc, char **argv) { if (cmd == CmdRun && buf_out_name == nullptr) { buf_out_name = buf_create_from_str("run"); } - CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, get_zig_lib_dir()); + CodeGen *g = codegen_create(zig_root_source_file, target, out_type, build_mode, get_zig_lib_dir(), + override_std_dir); g->subsystem = subsystem; if (disable_pic) { diff --git a/src/parser.cpp b/src/parser.cpp index 81bd469d1c..1c1af87c51 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -844,12 +844,17 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc) { // VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON static AstNode *ast_parse_var_decl(ParseContext *pc) { - Token *first = eat_token_if(pc, TokenIdKeywordConst); - if (first == nullptr) - first = eat_token_if(pc, TokenIdKeywordVar); - if (first == nullptr) - return nullptr; - + Token *thread_local_kw = eat_token_if(pc, TokenIdKeywordThreadLocal); + Token *mut_kw = eat_token_if(pc, TokenIdKeywordConst); + if (mut_kw == nullptr) + mut_kw = eat_token_if(pc, TokenIdKeywordVar); + if (mut_kw == nullptr) { + if (thread_local_kw == nullptr) { + return nullptr; + } else { + ast_invalid_token_error(pc, peek_token(pc)); + } + } Token *identifier = expect_token(pc, TokenIdSymbol); AstNode *type_expr = nullptr; if (eat_token_if(pc, TokenIdColon) != nullptr) @@ -863,8 +868,9 @@ static AstNode *ast_parse_var_decl(ParseContext *pc) { expect_token(pc, TokenIdSemicolon); - AstNode *res = ast_create_node(pc, NodeTypeVariableDeclaration, first); - res->data.variable_declaration.is_const = first->id == TokenIdKeywordConst; + AstNode *res = ast_create_node(pc, NodeTypeVariableDeclaration, mut_kw); + res->data.variable_declaration.threadlocal_tok = thread_local_kw; + res->data.variable_declaration.is_const = mut_kw->id == TokenIdKeywordConst; res->data.variable_declaration.symbol = token_buf(identifier); res->data.variable_declaration.type = type_expr; res->data.variable_declaration.align_expr = align_expr; diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index d43bfabf6d..3acd605748 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -146,6 +146,7 @@ static const struct ZigKeyword zig_keywords[] = { {"suspend", TokenIdKeywordSuspend}, {"switch", TokenIdKeywordSwitch}, {"test", TokenIdKeywordTest}, + {"threadlocal", TokenIdKeywordThreadLocal}, {"true", TokenIdKeywordTrue}, {"try", TokenIdKeywordTry}, {"undefined", TokenIdKeywordUndefined}, @@ -1586,6 +1587,7 @@ const char * token_name(TokenId id) { case TokenIdKeywordStruct: return "struct"; case TokenIdKeywordSwitch: return "switch"; case TokenIdKeywordTest: return "test"; + case TokenIdKeywordThreadLocal: return "threadlocal"; case TokenIdKeywordTrue: return "true"; case TokenIdKeywordTry: return "try"; case TokenIdKeywordUndefined: return "undefined"; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index 1574e95571..17f36699b3 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -88,6 +88,7 @@ enum TokenId { TokenIdKeywordSuspend, TokenIdKeywordSwitch, TokenIdKeywordTest, + TokenIdKeywordThreadLocal, TokenIdKeywordTrue, TokenIdKeywordTry, TokenIdKeywordUndefined, diff --git a/std/build.zig b/std/build.zig index 5246d97339..0dbbded802 100644 --- a/std/build.zig +++ b/std/build.zig @@ -145,8 +145,8 @@ pub const Builder = struct { pub fn setInstallPrefix(self: *Builder, maybe_prefix: ?[]const u8) void { self.prefix = maybe_prefix orelse "/usr/local"; // TODO better default - self.lib_dir = os.path.join(self.allocator, self.prefix, "lib") catch unreachable; - self.exe_dir = os.path.join(self.allocator, self.prefix, "bin") catch unreachable; + self.lib_dir = os.path.join(self.allocator, [][]const u8{ self.prefix, "lib" }) catch unreachable; + self.exe_dir = os.path.join(self.allocator, [][]const u8{ self.prefix, "bin" }) catch unreachable; } pub fn addExecutable(self: *Builder, name: []const u8, root_src: ?[]const u8) *LibExeObjStep { @@ -618,7 +618,10 @@ pub const Builder = struct { ///::dest_rel_path is relative to prefix path or it can be an absolute path pub fn addInstallFile(self: *Builder, src_path: []const u8, dest_rel_path: []const u8) *InstallFileStep { - const full_dest_path = os.path.resolve(self.allocator, self.prefix, dest_rel_path) catch unreachable; + const full_dest_path = os.path.resolve( + self.allocator, + [][]const u8{ self.prefix, dest_rel_path }, + ) catch unreachable; self.pushInstalledFile(full_dest_path); const install_step = self.allocator.create(InstallFileStep) catch unreachable; @@ -653,7 +656,7 @@ pub const Builder = struct { } fn pathFromRoot(self: *Builder, rel_path: []const u8) []u8 { - return os.path.resolve(self.allocator, self.build_root, rel_path) catch unreachable; + return os.path.resolve(self.allocator, [][]const u8{ self.build_root, rel_path }) catch unreachable; } pub fn fmt(self: *Builder, comptime format: []const u8, args: ...) []u8 { @@ -676,7 +679,7 @@ pub const Builder = struct { if (os.path.isAbsolute(name)) { return name; } - const full_path = try os.path.join(self.allocator, search_prefix, "bin", self.fmt("{}{}", name, exe_extension)); + const full_path = try os.path.join(self.allocator, [][]const u8{ search_prefix, "bin", self.fmt("{}{}", name, exe_extension) }); if (os.path.real(self.allocator, full_path)) |real_path| { return real_path; } else |_| { @@ -691,7 +694,7 @@ pub const Builder = struct { } var it = mem.tokenize(PATH, []u8{os.path.delimiter}); while (it.next()) |path| { - const full_path = try os.path.join(self.allocator, path, self.fmt("{}{}", name, exe_extension)); + const full_path = try os.path.join(self.allocator, [][]const u8{ path, self.fmt("{}{}", name, exe_extension) }); if (os.path.real(self.allocator, full_path)) |real_path| { return real_path; } else |_| { @@ -705,7 +708,7 @@ pub const Builder = struct { return name; } for (paths) |path| { - const full_path = try os.path.join(self.allocator, path, self.fmt("{}{}", name, exe_extension)); + const full_path = try os.path.join(self.allocator, [][]const u8{ path, self.fmt("{}{}", name, exe_extension) }); if (os.path.real(self.allocator, full_path)) |real_path| { return real_path; } else |_| { @@ -1113,7 +1116,10 @@ pub const LibExeObjStep = struct { } pub fn getOutputPath(self: *LibExeObjStep) []const u8 { - return if (self.output_path) |output_path| output_path else os.path.join(self.builder.allocator, self.builder.cache_root, self.out_filename) catch unreachable; + return if (self.output_path) |output_path| output_path else os.path.join( + self.builder.allocator, + [][]const u8{ self.builder.cache_root, self.out_filename }, + ) catch unreachable; } pub fn setOutputHPath(self: *LibExeObjStep, file_path: []const u8) void { @@ -1126,7 +1132,10 @@ pub const LibExeObjStep = struct { } pub fn getOutputHPath(self: *LibExeObjStep) []const u8 { - return if (self.output_h_path) |output_h_path| output_h_path else os.path.join(self.builder.allocator, self.builder.cache_root, self.out_h_filename) catch unreachable; + return if (self.output_h_path) |output_h_path| output_h_path else os.path.join( + self.builder.allocator, + [][]const u8{ self.builder.cache_root, self.out_h_filename }, + ) catch unreachable; } pub fn addAssemblyFile(self: *LibExeObjStep, path: []const u8) void { @@ -1226,7 +1235,10 @@ 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)); + const build_options_file = try os.path.join( + builder.allocator, + [][]const u8{ builder.cache_root, builder.fmt("{}_build_options.zig", self.name) }, + ); try std.io.writeFile(build_options_file, self.build_options_contents.toSliceConst()); try zig_args.append("--pkg-begin"); try zig_args.append("build_options"); @@ -1476,7 +1488,10 @@ pub const LibExeObjStep = struct { cc_args.append("-c") catch unreachable; cc_args.append(abs_source_file) catch unreachable; - const cache_o_src = os.path.join(builder.allocator, builder.cache_root, source_file) catch unreachable; + const cache_o_src = os.path.join( + builder.allocator, + [][]const u8{ builder.cache_root, source_file }, + ) catch unreachable; if (os.path.dirname(cache_o_src)) |cache_o_dir| { try builder.makePath(cache_o_dir); } @@ -1528,7 +1543,10 @@ pub const LibExeObjStep = struct { cc_args.append("-current_version") catch unreachable; cc_args.append(builder.fmt("{}.{}.{}", self.version.major, self.version.minor, self.version.patch)) catch unreachable; - const install_name = builder.pathFromRoot(os.path.join(builder.allocator, builder.cache_root, self.major_only_filename) catch unreachable); + const install_name = builder.pathFromRoot(os.path.join( + builder.allocator, + [][]const u8{ builder.cache_root, self.major_only_filename }, + ) catch unreachable); cc_args.append("-install_name") catch unreachable; cc_args.append(install_name) catch unreachable; } else { @@ -1594,7 +1612,10 @@ pub const LibExeObjStep = struct { cc_args.append("-c") catch unreachable; cc_args.append(abs_source_file) catch unreachable; - const cache_o_src = os.path.join(builder.allocator, builder.cache_root, source_file) catch unreachable; + const cache_o_src = os.path.join( + builder.allocator, + [][]const u8{ builder.cache_root, source_file }, + ) catch unreachable; if (os.path.dirname(cache_o_src)) |cache_o_dir| { try builder.makePath(cache_o_dir); } @@ -1686,6 +1707,7 @@ pub const TestStep = struct { no_rosegment: bool, output_path: ?[]const u8, system_linker_hack: bool, + override_std_dir: ?[]const u8, pub fn init(builder: *Builder, root_src: []const u8) TestStep { const step_name = builder.fmt("test {}", root_src); @@ -1707,6 +1729,7 @@ pub const TestStep = struct { .no_rosegment = false, .output_path = null, .system_linker_hack = false, + .override_std_dir = null, }; } @@ -1737,6 +1760,10 @@ pub const TestStep = struct { self.build_mode = mode; } + pub fn overrideStdDir(self: *TestStep, dir_path: []const u8) void { + self.override_std_dir = dir_path; + } + pub fn setOutputPath(self: *TestStep, file_path: []const u8) void { self.output_path = file_path; @@ -1751,7 +1778,10 @@ pub const TestStep = struct { return output_path; } else { const basename = self.builder.fmt("test{}", self.target.exeFileExt()); - return os.path.join(self.builder.allocator, self.builder.cache_root, basename) catch unreachable; + return os.path.join( + self.builder.allocator, + [][]const u8{ self.builder.cache_root, basename }, + ) catch unreachable; } } @@ -1914,6 +1944,10 @@ pub const TestStep = struct { if (self.system_linker_hack) { try zig_args.append("--system-linker-hack"); } + if (self.override_std_dir) |dir| { + try zig_args.append("--override-std-dir"); + try zig_args.append(builder.pathFromRoot(dir)); + } try builder.spawnChild(zig_args.toSliceConst()); } @@ -1969,13 +2003,22 @@ const InstallArtifactStep = struct { .builder = builder, .step = Step.init(builder.fmt("install {}", artifact.step.name), builder.allocator, make), .artifact = artifact, - .dest_file = os.path.join(builder.allocator, dest_dir, artifact.out_filename) catch unreachable, + .dest_file = os.path.join( + builder.allocator, + [][]const u8{ dest_dir, artifact.out_filename }, + ) catch unreachable, }; self.step.dependOn(&artifact.step); builder.pushInstalledFile(self.dest_file); if (self.artifact.kind == LibExeObjStep.Kind.Lib and !self.artifact.static) { - builder.pushInstalledFile(os.path.join(builder.allocator, builder.lib_dir, artifact.major_only_filename) catch unreachable); - builder.pushInstalledFile(os.path.join(builder.allocator, builder.lib_dir, artifact.name_only_filename) catch unreachable); + builder.pushInstalledFile(os.path.join( + builder.allocator, + [][]const u8{ builder.lib_dir, artifact.major_only_filename }, + ) catch unreachable); + builder.pushInstalledFile(os.path.join( + builder.allocator, + [][]const u8{ builder.lib_dir, artifact.name_only_filename }, + ) catch unreachable); } return self; } @@ -2131,13 +2174,19 @@ fn doAtomicSymLinks(allocator: *Allocator, output_path: []const u8, filename_maj const out_dir = os.path.dirname(output_path) orelse "."; const out_basename = os.path.basename(output_path); // sym link for libfoo.so.1 to libfoo.so.1.2.3 - const major_only_path = os.path.join(allocator, out_dir, filename_major_only) catch unreachable; + const major_only_path = os.path.join( + allocator, + [][]const u8{ out_dir, filename_major_only }, + ) catch unreachable; os.atomicSymLink(allocator, out_basename, major_only_path) catch |err| { warn("Unable to symlink {} -> {}\n", major_only_path, out_basename); return err; }; // sym link for libfoo.so to libfoo.so.1 - const name_only_path = os.path.join(allocator, out_dir, filename_name_only) catch unreachable; + const name_only_path = os.path.join( + allocator, + [][]const u8{ out_dir, filename_name_only }, + ) catch unreachable; os.atomicSymLink(allocator, filename_major_only, name_only_path) catch |err| { warn("Unable to symlink {} -> {}\n", name_only_path, filename_major_only); return err; diff --git a/std/debug/index.zig b/std/debug/index.zig index 838bd0c166..a1e2747df5 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -37,7 +37,6 @@ const Module = struct { var stderr_file: os.File = undefined; var stderr_file_out_stream: os.File.OutStream = undefined; -/// TODO multithreaded awareness var stderr_stream: ?*io.OutStream(os.File.WriteError) = null; var stderr_mutex = std.Mutex.init(); pub fn warn(comptime fmt: []const u8, args: ...) void { @@ -775,7 +774,7 @@ fn openSelfDebugInfoWindows(allocator: *mem.Allocator) !DebugInfo { const len = try di.coff.getPdbPath(path_buf[0..]); const raw_path = path_buf[0..len]; - const path = try os.path.resolve(allocator, raw_path); + const path = try os.path.resolve(allocator, [][]const u8{raw_path}); try di.pdb.openFile(di.coff, path); @@ -1353,7 +1352,7 @@ const LineNumberProgram = struct { return error.InvalidDebugInfo; } else self.include_dirs[file_entry.dir_index]; - const file_name = try os.path.join(self.file_entries.allocator, dir_name, file_entry.file_name); + const file_name = try os.path.join(self.file_entries.allocator, [][]const u8{ dir_name, file_entry.file_name }); errdefer self.file_entries.allocator.free(file_name); return LineInfo{ .line = if (self.prev_line >= 0) @intCast(usize, self.prev_line) else 0, diff --git a/std/event/fs.zig b/std/event/fs.zig index 097f2beddc..fd0fe434cb 100644 --- a/std/event/fs.zig +++ b/std/event/fs.zig @@ -871,7 +871,7 @@ pub fn Watch(comptime V: type) type { } async fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { - const resolved_path = try os.path.resolve(self.channel.loop.allocator, file_path); + const resolved_path = try os.path.resolve(self.channel.loop.allocator, [][]const u8{file_path}); var resolved_path_consumed = false; defer if (!resolved_path_consumed) self.channel.loop.allocator.free(resolved_path); @@ -1336,7 +1336,7 @@ async fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void { } async fn testFsWatch(loop: *Loop) !void { - const file_path = try os.path.join(loop.allocator, test_tmp_dir, "file.txt"); + const file_path = try os.path.join(loop.allocator, [][]const u8{ test_tmp_dir, "file.txt" }); defer loop.allocator.free(file_path); const contents = diff --git a/std/heap.zig b/std/heap.zig index fd2ce1e965..1403f8e831 100644 --- a/std/heap.zig +++ b/std/heap.zig @@ -106,9 +106,7 @@ pub const DirectAllocator = struct { }; const ptr = os.windows.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory; const root_addr = @ptrToInt(ptr); - const rem = @rem(root_addr, alignment); - const march_forward_bytes = if (rem == 0) 0 else (alignment - rem); - const adjusted_addr = root_addr + march_forward_bytes; + const adjusted_addr = mem.alignForward(root_addr, alignment); const record_addr = adjusted_addr + n; @intToPtr(*align(1) usize, record_addr).* = root_addr; return @intToPtr([*]u8, adjusted_addr)[0..n]; @@ -126,8 +124,7 @@ pub const DirectAllocator = struct { const base_addr = @ptrToInt(old_mem.ptr); const old_addr_end = base_addr + old_mem.len; const new_addr_end = base_addr + new_size; - const rem = @rem(new_addr_end, os.page_size); - const new_addr_end_rounded = new_addr_end + if (rem == 0) 0 else (os.page_size - rem); + const new_addr_end_rounded = mem.alignForward(new_addr_end, os.page_size); if (old_addr_end > new_addr_end_rounded) { _ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded); } diff --git a/std/index.zig b/std/index.zig index 80d1e46bb6..2a63244004 100644 --- a/std/index.zig +++ b/std/index.zig @@ -33,8 +33,8 @@ pub const io = @import("io.zig"); pub const json = @import("json.zig"); pub const macho = @import("macho.zig"); pub const math = @import("math/index.zig"); -pub const meta = @import("meta/index.zig"); pub const mem = @import("mem.zig"); +pub const meta = @import("meta/index.zig"); pub const net = @import("net.zig"); pub const os = @import("os/index.zig"); pub const pdb = @import("pdb.zig"); diff --git a/std/io.zig b/std/io.zig index c8701aeda6..81d90def6e 100644 --- a/std/io.zig +++ b/std/io.zig @@ -912,7 +912,7 @@ pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type { } /// Flush any remaining bits to the stream. - pub fn flushBits(self: *Self) !void { + pub fn flushBits(self: *Self) Error!void { if (self.bit_count == 0) return; try self.out_stream.writeByte(self.bit_buffer); self.bit_buffer = 0; @@ -1079,7 +1079,7 @@ pub fn Deserializer(comptime endian: builtin.Endian, is_packed: bool, comptime E } //@BUG: inferred error issue. See: #1386 - fn deserializeInt(self: *Self, comptime T: type) (Stream.Error || error{EndOfStream})!T { + fn deserializeInt(self: *Self, comptime T: type) (Error || error{EndOfStream})!T { comptime assert(trait.is(builtin.TypeId.Int)(T) or trait.is(builtin.TypeId.Float)(T)); const u8_bit_count = 8; @@ -1287,11 +1287,11 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime is_packed: bool, com } /// Flushes any unwritten bits to the stream - pub fn flush(self: *Self) Stream.Error!void { + pub fn flush(self: *Self) Error!void { if (is_packed) return self.out_stream.flushBits(); } - fn serializeInt(self: *Self, value: var) !void { + fn serializeInt(self: *Self, value: var) Error!void { const T = @typeOf(value); comptime assert(trait.is(builtin.TypeId.Int)(T) or trait.is(builtin.TypeId.Float)(T)); @@ -1323,7 +1323,7 @@ pub fn Serializer(comptime endian: builtin.Endian, comptime is_packed: bool, com } /// Serializes the passed value into the stream - pub fn serialize(self: *Self, value: var) !void { + pub fn serialize(self: *Self, value: var) Error!void { const T = comptime @typeOf(value); if (comptime trait.isIndexable(T)) { diff --git a/std/io_test.zig b/std/io_test.zig index 0bee0ddaf0..9a0687ec69 100644 --- a/std/io_test.zig +++ b/std/io_test.zig @@ -357,6 +357,15 @@ fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime is_pa const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte; assert(in.pos == if (is_packed) total_packed_bytes else total_bytes); + + //Verify that empty error set works with serializer. + //deserializer is covered by SliceInStream + const NullError = io.NullOutStream.Error; + var null_out = io.NullOutStream.init(); + var null_out_stream = &null_out.stream; + var null_serializer = io.Serializer(endian, is_packed, NullError).init(null_out_stream); + try null_serializer.serialize(data_mem[0..]); + try null_serializer.flush(); } test "Serializer/Deserializer Int" { @@ -568,4 +577,4 @@ test "Deserializer bad data" { try testBadData(builtin.Endian.Little, false); try testBadData(builtin.Endian.Big, true); try testBadData(builtin.Endian.Little, true); -} \ No newline at end of file +} diff --git a/std/mem.zig b/std/mem.zig index 26ae4ef089..a6cbae744f 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -882,42 +882,40 @@ pub const SplitIterator = struct { } }; -/// Naively combines a series of strings with a separator. +/// Naively combines a series of slices with a separator. /// Allocates memory for the result, which must be freed by the caller. -pub fn join(allocator: *Allocator, sep: u8, strings: ...) ![]u8 { - comptime assert(strings.len >= 1); - var total_strings_len: usize = strings.len; // 1 sep per string - { - comptime var string_i = 0; - inline while (string_i < strings.len) : (string_i += 1) { - const arg = ([]const u8)(strings[string_i]); - total_strings_len += arg.len; - } - } +pub fn join(allocator: *Allocator, separator: []const u8, slices: []const []const u8) ![]u8 { + if (slices.len == 0) return (([*]u8)(undefined))[0..0]; - const buf = try allocator.alloc(u8, total_strings_len); + const total_len = blk: { + var sum: usize = separator.len * (slices.len - 1); + for (slices) |slice| + sum += slice.len; + break :blk sum; + }; + + const buf = try allocator.alloc(u8, total_len); errdefer allocator.free(buf); - var buf_index: usize = 0; - comptime var string_i = 0; - inline while (true) { - const arg = ([]const u8)(strings[string_i]); - string_i += 1; - copy(u8, buf[buf_index..], arg); - buf_index += arg.len; - if (string_i >= strings.len) break; - if (buf[buf_index - 1] != sep) { - buf[buf_index] = sep; - buf_index += 1; - } + copy(u8, buf, slices[0]); + var buf_index: usize = slices[0].len; + for (slices[1..]) |slice| { + copy(u8, buf[buf_index..], separator); + buf_index += separator.len; + copy(u8, buf[buf_index..], slice); + buf_index += slice.len; } - return allocator.shrink(u8, buf, buf_index); + // No need for shrink since buf is exactly the correct size. + return buf; } test "mem.join" { - assert(eql(u8, try join(debug.global_allocator, ',', "a", "b", "c"), "a,b,c")); - assert(eql(u8, try join(debug.global_allocator, ',', "a"), "a")); + var buf: [1024]u8 = undefined; + const a = &std.heap.FixedBufferAllocator.init(&buf).allocator; + assert(eql(u8, try join(a, ",", [][]const u8{ "a", "b", "c" }), "a,b,c")); + assert(eql(u8, try join(a, ",", [][]const u8{"a"}), "a")); + assert(eql(u8, try join(a, ",", [][]const u8{ "a", "", "b", "", "c" }), "a,,b,,c")); } test "testStringEquality" { @@ -1366,3 +1364,23 @@ test "std.mem.subArrayPtr" { sub2[1] = 'X'; debug.assert(std.mem.eql(u8, a2, "abcXef")); } + +/// Round an address up to the nearest aligned address +pub fn alignForward(addr: usize, alignment: usize) usize { + return (addr + alignment - 1) & ~(alignment - 1); +} + +test "std.mem.alignForward" { + debug.assertOrPanic(alignForward(1, 1) == 1); + debug.assertOrPanic(alignForward(2, 1) == 2); + debug.assertOrPanic(alignForward(1, 2) == 2); + debug.assertOrPanic(alignForward(2, 2) == 2); + debug.assertOrPanic(alignForward(3, 2) == 4); + debug.assertOrPanic(alignForward(4, 2) == 4); + debug.assertOrPanic(alignForward(7, 8) == 8); + debug.assertOrPanic(alignForward(8, 8) == 8); + debug.assertOrPanic(alignForward(9, 8) == 16); + debug.assertOrPanic(alignForward(15, 8) == 16); + debug.assertOrPanic(alignForward(16, 8) == 16); + debug.assertOrPanic(alignForward(17, 8) == 24); +} diff --git a/std/os/child_process.zig b/std/os/child_process.zig index 7aa8582369..6635b76976 100644 --- a/std/os/child_process.zig +++ b/std/os/child_process.zig @@ -574,7 +574,7 @@ pub const ChildProcess = struct { // to match posix semantics const app_name = x: { if (self.cwd) |cwd| { - const resolved = try os.path.resolve(self.allocator, cwd, self.argv[0]); + const resolved = try os.path.resolve(self.allocator, [][]const u8{ cwd, self.argv[0] }); defer self.allocator.free(resolved); break :x try cstr.addNullByte(self.allocator, resolved); } else { @@ -597,10 +597,10 @@ pub const ChildProcess = struct { var it = mem.tokenize(PATH, ";"); while (it.next()) |search_path| { - const joined_path = try os.path.join(self.allocator, search_path, app_name); + const joined_path = try os.path.join(self.allocator, [][]const u8{ search_path, app_name }); defer self.allocator.free(joined_path); - const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, app_name); + const joined_path_w = try unicode.utf8ToUtf16LeWithNull(self.allocator, joined_path); defer self.allocator.free(joined_path_w); if (windowsCreateProcess(joined_path_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_w_ptr, &siStartInfo, &piProcInfo)) |_| { @@ -610,6 +610,9 @@ pub const ChildProcess = struct { } else { return err; } + } else { + // Every other error would have been returned earlier. + return error.FileNotFound; } }; diff --git a/std/os/get_app_data_dir.zig b/std/os/get_app_data_dir.zig index ae133bb4b1..f5e0b78eec 100644 --- a/std/os/get_app_data_dir.zig +++ b/std/os/get_app_data_dir.zig @@ -30,7 +30,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD error.OutOfMemory => return error.OutOfMemory, }; defer allocator.free(global_dir); - return os.path.join(allocator, global_dir, appname); + return os.path.join(allocator, [][]const u8{ global_dir, appname }); }, os.windows.E_OUTOFMEMORY => return error.OutOfMemory, else => return error.AppDataDirUnavailable, @@ -41,14 +41,14 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD // TODO look in /etc/passwd return error.AppDataDirUnavailable; }; - return os.path.join(allocator, home_dir, "Library", "Application Support", appname); + return os.path.join(allocator, [][]const u8{ home_dir, "Library", "Application Support", appname }); }, builtin.Os.linux, builtin.Os.freebsd => { const home_dir = os.getEnvPosix("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; }; - return os.path.join(allocator, home_dir, ".local", "share", appname); + return os.path.join(allocator, [][]const u8{ home_dir, ".local", "share", appname }); }, else => @compileError("Unsupported OS"), } @@ -67,4 +67,3 @@ test "std.os.getAppDataDir" { // We can't actually validate the result _ = getAppDataDir(allocator, "zig") catch return; } - diff --git a/std/os/index.zig b/std/os/index.zig index 451c0a3436..8e9876c36b 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -8,6 +8,10 @@ const is_posix = switch (builtin.os) { }; const os = @This(); +comptime { + assert(@import("std") == std); // You have to run the std lib tests with --override-std-dir +} + test "std.os" { _ = @import("child_process.zig"); _ = @import("darwin.zig"); @@ -692,12 +696,7 @@ pub fn getBaseAddress() usize { return base; } const phdr = linuxGetAuxVal(std.elf.AT_PHDR); - const ElfHeader = switch (@sizeOf(usize)) { - 4 => std.elf.Elf32_Ehdr, - 8 => std.elf.Elf64_Ehdr, - else => @compileError("Unsupported architecture"), - }; - return phdr - @sizeOf(ElfHeader); + return phdr - @sizeOf(std.elf.Ehdr); }, builtin.Os.macosx, builtin.Os.freebsd => return @ptrToInt(&std.c._mh_execute_header), builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)), @@ -1285,7 +1284,7 @@ pub fn makeDirPosix(dir_path: []const u8) !void { /// 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); + const resolved_path = try path.resolve(allocator, [][]const u8{full_path}); defer allocator.free(resolved_path); var end_index: usize = resolved_path.len; @@ -2305,18 +2304,17 @@ pub fn selfExePath(out_buffer: *[MAX_PATH_BYTES]u8) ![]u8 { switch (builtin.os) { Os.linux => return readLink(out_buffer, "/proc/self/exe"), Os.freebsd => { - var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1}; + var mib = [4]c_int{ posix.CTL_KERN, posix.KERN_PROC, posix.KERN_PROC_PATHNAME, -1 }; var out_len: usize = out_buffer.len; const err = posix.getErrno(posix.sysctl(&mib, 4, out_buffer, &out_len, null, 0)); - if (err == 0 ) return mem.toSlice(u8, out_buffer); + if (err == 0) return mem.toSlice(u8, out_buffer); return switch (err) { posix.EFAULT => error.BadAdress, posix.EPERM => error.PermissionDenied, else => unexpectedErrorPosix(err), }; - }, Os.windows => { var utf16le_buf: [windows_util.PATH_MAX_WIDE]u16 = undefined; @@ -2908,14 +2906,15 @@ pub const Thread = struct { pub const Data = if (use_pthreads) struct { handle: Thread.Handle, - stack_addr: usize, - stack_len: usize, + mmap_addr: usize, + mmap_len: usize, } else switch (builtin.os) { builtin.Os.linux => struct { handle: Thread.Handle, - stack_addr: usize, - stack_len: usize, + mmap_addr: usize, + mmap_len: usize, + tls_end_addr: usize, }, builtin.Os.windows => struct { handle: Thread.Handle, @@ -2955,7 +2954,7 @@ pub const Thread = struct { posix.EDEADLK => unreachable, else => unreachable, } - assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0); + assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); } else switch (builtin.os) { builtin.Os.linux => { while (true) { @@ -2969,7 +2968,7 @@ pub const Thread = struct { else => unreachable, } } - assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0); + assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0); }, builtin.Os.windows => { assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0); @@ -3008,6 +3007,9 @@ pub const SpawnThreadError = error{ Unexpected, }; +pub var linux_tls_phdr: ?*std.elf.Phdr = null; +pub var linux_tls_img_src: [*]const u8 = undefined; // defined if linux_tls_phdr is + /// caller must call wait on the returned thread /// fn startFn(@typeOf(context)) T /// where T is u8, noreturn, void, or !void @@ -3097,42 +3099,56 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0; - const mmap_len = default_stack_size; - const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); - if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory; - errdefer assert(posix.munmap(stack_addr, mmap_len) == 0); + var stack_end_offset: usize = undefined; + var thread_start_offset: usize = undefined; + var context_start_offset: usize = undefined; + var tls_start_offset: usize = undefined; + const mmap_len = blk: { + // First in memory will be the stack, which grows downwards. + var l: usize = mem.alignForward(default_stack_size, os.page_size); + stack_end_offset = l; + // Above the stack, so that it can be in the same mmap call, put the Thread object. + l = mem.alignForward(l, @alignOf(Thread)); + thread_start_offset = l; + l += @sizeOf(Thread); + // Next, the Context object. + if (@sizeOf(Context) != 0) { + l = mem.alignForward(l, @alignOf(Context)); + context_start_offset = l; + l += @sizeOf(Context); + } + // Finally, the Thread Local Storage, if any. + if (!Thread.use_pthreads) { + if (linux_tls_phdr) |tls_phdr| { + l = mem.alignForward(l, tls_phdr.p_align); + tls_start_offset = l; + l += tls_phdr.p_memsz; + } + } + break :blk l; + }; + const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0); + if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory; + errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0); + + const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset)); + thread_ptr.data.mmap_addr = mmap_addr; + thread_ptr.data.mmap_len = mmap_len; - var stack_end: usize = stack_addr + mmap_len; var arg: usize = undefined; if (@sizeOf(Context) != 0) { - stack_end -= @sizeOf(Context); - stack_end -= stack_end % @alignOf(Context); - assert(stack_end >= stack_addr); - const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, stack_end)); + arg = mmap_addr + context_start_offset; + const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg)); context_ptr.* = context; - arg = stack_end; } - stack_end -= @sizeOf(Thread); - stack_end -= stack_end % @alignOf(Thread); - assert(stack_end >= stack_addr); - const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, stack_end)); - - thread_ptr.data.stack_addr = stack_addr; - thread_ptr.data.stack_len = mmap_len; - - if (builtin.os == builtin.Os.windows) { - // use windows API directly - @compileError("TODO support spawnThread for Windows"); - } else if (Thread.use_pthreads) { + if (Thread.use_pthreads) { // use pthreads var attr: c.pthread_attr_t = undefined; if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources; defer assert(c.pthread_attr_destroy(&attr) == 0); - // align to page - stack_end -= stack_end % os.page_size; - assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, stack_addr), stack_end - stack_addr) == 0); + assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0); const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg)); switch (err) { @@ -3143,10 +3159,17 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread else => return unexpectedErrorPosix(@intCast(usize, err)), } } else if (builtin.os == builtin.Os.linux) { - // use linux API directly. TODO use posix.CLONE_SETTLS and initialize thread local storage correctly - const flags = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | posix.CLONE_DETACHED; - const newtls: usize = 0; - const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); + var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND | + posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID | + posix.CLONE_DETACHED; + var newtls: usize = undefined; + if (linux_tls_phdr) |tls_phdr| { + @memcpy(@intToPtr([*]u8, mmap_addr + tls_start_offset), linux_tls_img_src, tls_phdr.p_filesz); + thread_ptr.data.tls_end_addr = mmap_addr + mmap_len; + newtls = @ptrToInt(&thread_ptr.data.tls_end_addr); + flags |= posix.CLONE_SETTLS; + } + const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle); const err = posix.getErrno(rc); switch (err) { 0 => return thread_ptr, diff --git a/std/os/path.zig b/std/os/path.zig index 0b960fa2da..266a77b97c 100644 --- a/std/os/path.zig +++ b/std/os/path.zig @@ -33,40 +33,103 @@ pub fn isSep(byte: u8) bool { } } +/// This is different from mem.join in that the separator will not be repeated if +/// it is found at the end or beginning of a pair of consecutive paths. +fn joinSep(allocator: *Allocator, separator: u8, paths: []const []const u8) ![]u8 { + if (paths.len == 0) return (([*]u8)(undefined))[0..0]; + + const total_len = blk: { + var sum: usize = paths[0].len; + var i: usize = 1; + while (i < paths.len) : (i += 1) { + const prev_path = paths[i - 1]; + const this_path = paths[i]; + const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator); + const this_sep = (this_path.len != 0 and this_path[0] == separator); + sum += @boolToInt(!prev_sep and !this_sep); + sum += if (prev_sep and this_sep) this_path.len - 1 else this_path.len; + } + break :blk sum; + }; + + const buf = try allocator.alloc(u8, total_len); + errdefer allocator.free(buf); + + mem.copy(u8, buf, paths[0]); + var buf_index: usize = paths[0].len; + var i: usize = 1; + while (i < paths.len) : (i += 1) { + const prev_path = paths[i - 1]; + const this_path = paths[i]; + const prev_sep = (prev_path.len != 0 and prev_path[prev_path.len - 1] == separator); + const this_sep = (this_path.len != 0 and this_path[0] == separator); + if (!prev_sep and !this_sep) { + buf[buf_index] = separator; + buf_index += 1; + } + const adjusted_path = if (prev_sep and this_sep) this_path[1..] else this_path; + mem.copy(u8, buf[buf_index..], adjusted_path); + buf_index += adjusted_path.len; + } + + // No need for shrink since buf is exactly the correct size. + return buf; +} + +pub const join = if (is_windows) joinWindows else joinPosix; + /// Naively combines a series of paths with the native path seperator. /// Allocates memory for the result, which must be freed by the caller. -pub fn join(allocator: *Allocator, paths: ...) ![]u8 { - if (is_windows) { - return joinWindows(allocator, paths); - } else { - return joinPosix(allocator, paths); - } +pub fn joinWindows(allocator: *Allocator, paths: []const []const u8) ![]u8 { + return joinSep(allocator, sep_windows, paths); } -pub fn joinWindows(allocator: *Allocator, paths: ...) ![]u8 { - return mem.join(allocator, sep_windows, paths); +/// Naively combines a series of paths with the native path seperator. +/// Allocates memory for the result, which must be freed by the caller. +pub fn joinPosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { + return joinSep(allocator, sep_posix, paths); } -pub fn joinPosix(allocator: *Allocator, paths: ...) ![]u8 { - return mem.join(allocator, sep_posix, paths); +fn testJoinWindows(paths: []const []const u8, expected: []const u8) void { + var buf: [1024]u8 = undefined; + const a = &std.heap.FixedBufferAllocator.init(&buf).allocator; + const actual = joinWindows(a, paths) catch @panic("fail"); + debug.assertOrPanic(mem.eql(u8, actual, expected)); +} + +fn testJoinPosix(paths: []const []const u8, expected: []const u8) void { + var buf: [1024]u8 = undefined; + const a = &std.heap.FixedBufferAllocator.init(&buf).allocator; + const actual = joinPosix(a, paths) catch @panic("fail"); + debug.assertOrPanic(mem.eql(u8, actual, expected)); } test "os.path.join" { - assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\b", "c"), "c:\\a\\b\\c")); - assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\b\\", "c"), "c:\\a\\b\\c")); + testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c"); + testJoinWindows([][]const u8{ "c:\\a\\b", "c" }, "c:\\a\\b\\c"); + testJoinWindows([][]const u8{ "c:\\a\\b\\", "c" }, "c:\\a\\b\\c"); - assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\", "a", "b\\", "c"), "c:\\a\\b\\c")); - assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\a\\", "b\\", "c"), "c:\\a\\b\\c")); + testJoinWindows([][]const u8{ "c:\\", "a", "b\\", "c" }, "c:\\a\\b\\c"); + testJoinWindows([][]const u8{ "c:\\a\\", "b\\", "c" }, "c:\\a\\b\\c"); - assert(mem.eql(u8, try joinWindows(debug.global_allocator, "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig"), "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig")); + testJoinWindows( + [][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig" }, + "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig", + ); - assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/b", "c"), "/a/b/c")); - assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/b/", "c"), "/a/b/c")); + testJoinPosix([][]const u8{ "/a/b", "c" }, "/a/b/c"); + testJoinPosix([][]const u8{ "/a/b/", "c" }, "/a/b/c"); - assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c")); - assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/a/", "b/", "c"), "/a/b/c")); + testJoinPosix([][]const u8{ "/", "a", "b/", "c" }, "/a/b/c"); + testJoinPosix([][]const u8{ "/a/", "b/", "c" }, "/a/b/c"); - assert(mem.eql(u8, try joinPosix(debug.global_allocator, "/home/andy/dev/zig/build/lib/zig/std", "io.zig"), "/home/andy/dev/zig/build/lib/zig/std/io.zig")); + testJoinPosix( + [][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "io.zig" }, + "/home/andy/dev/zig/build/lib/zig/std/io.zig", + ); + + testJoinPosix([][]const u8{ "a", "/c" }, "a/c"); + testJoinPosix([][]const u8{ "a/", "/c" }, "a/c"); } pub fn isAbsolute(path: []const u8) bool { @@ -312,18 +375,8 @@ fn asciiEqlIgnoreCase(s1: []const u8, s2: []const u8) bool { return true; } -/// Converts the command line arguments into a slice and calls `resolveSlice`. -pub fn resolve(allocator: *Allocator, args: ...) ![]u8 { - var paths: [args.len][]const u8 = undefined; - comptime var arg_i = 0; - inline while (arg_i < args.len) : (arg_i += 1) { - paths[arg_i] = args[arg_i]; - } - return resolveSlice(allocator, paths); -} - /// On Windows, this calls `resolveWindows` and on POSIX it calls `resolvePosix`. -pub fn resolveSlice(allocator: *Allocator, paths: []const []const u8) ![]u8 { +pub fn resolve(allocator: *Allocator, paths: []const []const u8) ![]u8 { if (is_windows) { return resolveWindows(allocator, paths); } else { @@ -602,7 +655,10 @@ test "os.path.resolveWindows" { const parsed_cwd = windowsParsePath(cwd); { const result = testResolveWindows([][]const u8{ "/usr/local", "lib\\zig\\std\\array_list.zig" }); - const expected = try join(debug.global_allocator, parsed_cwd.disk_designator, "usr\\local\\lib\\zig\\std\\array_list.zig"); + const expected = try join(debug.global_allocator, [][]const u8{ + parsed_cwd.disk_designator, + "usr\\local\\lib\\zig\\std\\array_list.zig", + }); if (parsed_cwd.kind == WindowsPath.Kind.Drive) { expected[0] = asciiUpper(parsed_cwd.disk_designator[0]); } @@ -610,7 +666,10 @@ test "os.path.resolveWindows" { } { const result = testResolveWindows([][]const u8{ "usr/local", "lib\\zig" }); - const expected = try join(debug.global_allocator, cwd, "usr\\local\\lib\\zig"); + const expected = try join(debug.global_allocator, [][]const u8{ + cwd, + "usr\\local\\lib\\zig", + }); if (parsed_cwd.kind == WindowsPath.Kind.Drive) { expected[0] = asciiUpper(parsed_cwd.disk_designator[0]); } diff --git a/std/os/test.zig b/std/os/test.zig index f14cf47786..bd9148d1b1 100644 --- a/std/os/test.zig +++ b/std/os/test.zig @@ -105,3 +105,19 @@ test "AtomicFile" { try os.deleteFile(test_out_file); } + +test "thread local storage" { + if (builtin.single_threaded) return error.SkipZigTest; + const thread1 = try std.os.spawnThread({}, testTls); + const thread2 = try std.os.spawnThread({}, testTls); + testTls({}); + thread1.wait(); + thread2.wait(); +} + +threadlocal var x: i32 = 1234; +fn testTls(context: void) void { + if (x != 1234) @panic("bad start value"); + x += 1; + if (x != 1235) @panic("bad end value"); +} diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig index 3f19905835..8e9ed8b8db 100644 --- a/std/os/windows/index.zig +++ b/std/os/windows/index.zig @@ -49,7 +49,10 @@ pub const UNICODE = false; pub const WCHAR = u16; pub const WORD = u16; pub const LARGE_INTEGER = i64; -pub const LONG = c_long; +pub const ULONG = u32; +pub const LONG = i32; +pub const ULONGLONG = u64; +pub const LONGLONG = i64; pub const TRUE = 1; pub const FALSE = 0; @@ -380,3 +383,17 @@ pub const COORD = extern struct { }; pub const CREATE_UNICODE_ENVIRONMENT = 1024; + +pub const TLS_OUT_OF_INDEXES = 4294967295; +pub const IMAGE_TLS_DIRECTORY = extern struct { + StartAddressOfRawData: usize, + EndAddressOfRawData: usize, + AddressOfIndex: usize, + AddressOfCallBacks: usize, + SizeOfZeroFill: u32, + Characteristics: u32, +}; +pub const IMAGE_TLS_DIRECTORY64 = IMAGE_TLS_DIRECTORY; +pub const IMAGE_TLS_DIRECTORY32 = IMAGE_TLS_DIRECTORY; + +pub const PIMAGE_TLS_CALLBACK = ?extern fn(PVOID, DWORD, PVOID) void; diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig index 66b9552c5f..ce8443de02 100644 --- a/std/os/windows/kernel32.zig +++ b/std/os/windows/kernel32.zig @@ -164,6 +164,10 @@ pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD) void; pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) BOOL; +pub extern "kernel32" stdcallcc fn TlsAlloc() DWORD; + +pub extern "kernel32" stdcallcc fn TlsFree(dwTlsIndex: DWORD) BOOL; + pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) DWORD; pub extern "kernel32" stdcallcc fn WriteFile( diff --git a/std/os/windows/tls.zig b/std/os/windows/tls.zig new file mode 100644 index 0000000000..9e62a7c5c6 --- /dev/null +++ b/std/os/windows/tls.zig @@ -0,0 +1,36 @@ +const std = @import("../../index.zig"); + +export var _tls_index: u32 = std.os.windows.TLS_OUT_OF_INDEXES; +export var _tls_start: u8 linksection(".tls") = 0; +export var _tls_end: u8 linksection(".tls$ZZZ") = 0; +export var __xl_a: std.os.windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLA") = null; +export var __xl_z: std.os.windows.PIMAGE_TLS_CALLBACK linksection(".CRT$XLZ") = null; + +// TODO this is how I would like it to be expressed +// TODO also note, ReactOS has a +1 on StartAddressOfRawData and AddressOfCallBacks. Investigate +// why they do that. +//export const _tls_used linksection(".rdata$T") = std.os.windows.IMAGE_TLS_DIRECTORY { +// .StartAddressOfRawData = @ptrToInt(&_tls_start), +// .EndAddressOfRawData = @ptrToInt(&_tls_end), +// .AddressOfIndex = @ptrToInt(&_tls_index), +// .AddressOfCallBacks = @ptrToInt(__xl_a), +// .SizeOfZeroFill = 0, +// .Characteristics = 0, +//}; +// This is the workaround because we can't do @ptrToInt at comptime like that. +pub const IMAGE_TLS_DIRECTORY = extern struct { + StartAddressOfRawData: *c_void, + EndAddressOfRawData: *c_void, + AddressOfIndex: *c_void, + AddressOfCallBacks: *c_void, + SizeOfZeroFill: u32, + Characteristics: u32, +}; +export const _tls_used linksection(".rdata$T") = IMAGE_TLS_DIRECTORY { + .StartAddressOfRawData = &_tls_start, + .EndAddressOfRawData = &_tls_end, + .AddressOfIndex = &_tls_index, + .AddressOfCallBacks = &__xl_a, + .SizeOfZeroFill = 0, + .Characteristics = 0, +}; diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 129bde913f..6dcc90b372 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -4,6 +4,7 @@ const root = @import("@root"); const std = @import("std"); const builtin = @import("builtin"); +const assert = std.debug.assert; var argc_ptr: [*]usize = undefined; @@ -44,7 +45,9 @@ nakedcc fn _start() noreturn { extern fn WinMainCRTStartup() noreturn { @setAlignStack(16); - + if (!builtin.single_threaded) { + _ = @import("../os/windows/tls.zig"); + } std.os.windows.ExitProcess(callMain()); } @@ -61,9 +64,23 @@ fn posixCallMainAndExit() noreturn { while (envp_optional[envp_count]) |_| : (envp_count += 1) {} const envp = @ptrCast([*][*]u8, envp_optional)[0..envp_count]; if (builtin.os == builtin.Os.linux) { - const auxv = @ptrCast([*]usize, envp.ptr + envp_count + 1); - std.os.linux_elf_aux_maybe = @ptrCast([*]std.elf.Auxv, auxv); - std.debug.assert(std.os.linuxGetAuxVal(std.elf.AT_PAGESZ) == std.os.page_size); + // Scan auxiliary vector. + const auxv = @ptrCast([*]std.elf.Auxv, envp.ptr + envp_count + 1); + std.os.linux_elf_aux_maybe = auxv; + var i: usize = 0; + var at_phdr: usize = 0; + var at_phnum: usize = 0; + var at_phent: usize = 0; + while (auxv[i].a_un.a_val != 0) : (i += 1) { + switch (auxv[i].a_type) { + std.elf.AT_PAGESZ => assert(auxv[i].a_un.a_val == std.os.page_size), + std.elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val, + std.elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val, + std.elf.AT_PHENT => at_phent = auxv[i].a_un.a_val, + else => {}, + } + } + if (!builtin.single_threaded) linuxInitializeThreadLocalStorage(at_phdr, at_phnum, at_phent); } std.os.posix.exit(callMainWithArgs(argc, argv, envp)); @@ -116,3 +133,41 @@ inline fn callMain() u8 { else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"), } } + +var tls_end_addr: usize = undefined; +const main_thread_tls_align = 32; +var main_thread_tls_bytes: [64]u8 align(main_thread_tls_align) = [1]u8{0} ** 64; + +fn linuxInitializeThreadLocalStorage(at_phdr: usize, at_phnum: usize, at_phent: usize) void { + var phdr_addr = at_phdr; + var n = at_phnum; + var base: usize = 0; + while (n != 0) : ({n -= 1; phdr_addr += at_phent;}) { + const phdr = @intToPtr(*std.elf.Phdr, phdr_addr); + // TODO look for PT_DYNAMIC when we have https://github.com/ziglang/zig/issues/1917 + switch (phdr.p_type) { + std.elf.PT_PHDR => base = at_phdr - phdr.p_vaddr, + std.elf.PT_TLS => std.os.linux_tls_phdr = phdr, + else => continue, + } + } + const tls_phdr = std.os.linux_tls_phdr orelse return; + std.os.linux_tls_img_src = @intToPtr([*]const u8, base + tls_phdr.p_vaddr); + assert(main_thread_tls_bytes.len >= tls_phdr.p_memsz); // not enough preallocated Thread Local Storage + assert(main_thread_tls_align >= tls_phdr.p_align); // preallocated Thread Local Storage not aligned enough + @memcpy(&main_thread_tls_bytes, std.os.linux_tls_img_src, tls_phdr.p_filesz); + tls_end_addr = @ptrToInt(&main_thread_tls_bytes) + tls_phdr.p_memsz; + linuxSetThreadArea(@ptrToInt(&tls_end_addr)); +} + +fn linuxSetThreadArea(addr: usize) void { + switch (builtin.arch) { + builtin.Arch.x86_64 => { + const ARCH_SET_FS = 0x1002; + const rc = std.os.linux.syscall2(std.os.linux.SYS_arch_prctl, ARCH_SET_FS, addr); + // acrh_prctl is documented to never fail + assert(rc == 0); + }, + else => @compileError("Unsupported architecture"), + } +} diff --git a/test/cli.zig b/test/cli.zig index 54ef58af79..6ad97415fa 100644 --- a/test/cli.zig +++ b/test/cli.zig @@ -27,9 +27,9 @@ pub fn main() !void { std.debug.warn("Expected second argument to be cache root directory path\n"); return error.InvalidArgs; }); - const zig_exe = try os.path.resolve(a, zig_exe_rel); + const zig_exe = try os.path.resolve(a, [][]const u8{zig_exe_rel}); - const dir_path = try os.path.join(a, cache_root, "clitest"); + const dir_path = try os.path.join(a, [][]const u8{ cache_root, "clitest" }); const TestFn = fn ([]const u8, []const u8) anyerror!void; const test_fns = []TestFn{ testZigInitLib, @@ -99,8 +99,8 @@ fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void { fn testGodboltApi(zig_exe: []const u8, dir_path: []const u8) anyerror!void { if (builtin.os != builtin.Os.linux or builtin.arch != builtin.Arch.x86_64) return; - const example_zig_path = try os.path.join(a, dir_path, "example.zig"); - const example_s_path = try os.path.join(a, dir_path, "example.s"); + const example_zig_path = try os.path.join(a, [][]const u8{ dir_path, "example.zig" }); + const example_s_path = try os.path.join(a, [][]const u8{ dir_path, "example.s" }); try std.io.writeFile(example_zig_path, \\// Type your code here, or load an example. diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 30d9ca5d70..acd1eada06 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,25 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "threadlocal qualifier on const", + \\threadlocal const x: i32 = 1234; + \\export fn entry() i32 { + \\ return x; + \\} + , + ".tmp_source.zig:1:13: error: threadlocal variable cannot be constant", + ); + + cases.add( + "threadlocal qualifier on local variable", + \\export fn entry() void { + \\ threadlocal var x: i32 = 1234; + \\} + , + ".tmp_source.zig:2:5: error: function-local variable 'x' cannot be threadlocal", + ); + cases.add( "@bitCast same size but bit count mismatch", \\export fn entry(byte: u8) void { diff --git a/test/stage1/behavior/misc.zig b/test/stage1/behavior/misc.zig index 8d2555dddd..3cc8e5f31e 100644 --- a/test/stage1/behavior/misc.zig +++ b/test/stage1/behavior/misc.zig @@ -685,3 +685,11 @@ test "fn call returning scalar optional in equality expression" { fn getNull() ?*i32 { return null; } + +test "thread local variable" { + const S = struct { + threadlocal var t: i32 = 1234; + }; + S.t += 1; + assertOrPanic(S.t == 1235); +} diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 53c5d01381..c97ee0e3d6 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -1,20 +1,17 @@ const std = @import("std"); +const mem = std.mem; const assertOrPanic = std.debug.assertOrPanic; -test "implicit array to vector and vector to array" { +test "vector wrap operators" { const S = struct { fn doTheTest() void { - var v: @Vector(4, i32) = [4]i32{10, 20, 30, 40}; - const x: @Vector(4, i32) = [4]i32{1, 2, 3, 4}; - v +%= x; - const result: [4]i32 = v; - assertOrPanic(result[0] == 11); - assertOrPanic(result[1] == 22); - assertOrPanic(result[2] == 33); - assertOrPanic(result[3] == 44); + const v: @Vector(4, i32) = [4]i32{ 10, 20, 30, 40 }; + const x: @Vector(4, i32) = [4]i32{ 1, 2, 3, 4 }; + assertOrPanic(mem.eql(i32, ([4]i32)(v +% x), [4]i32{ 11, 22, 33, 44 })); + assertOrPanic(mem.eql(i32, ([4]i32)(v -% x), [4]i32{ 9, 18, 27, 36 })); + assertOrPanic(mem.eql(i32, ([4]i32)(v *% x), [4]i32{ 10, 40, 90, 160 })); } }; S.doTheTest(); comptime S.doTheTest(); } - diff --git a/test/tests.zig b/test/tests.zig index 548496fa2f..670c410509 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -194,6 +194,9 @@ pub fn addPkgTests(b: *build.Builder, test_filter: ?[]const u8, root_src: []cons if (link_libc) { these_tests.linkSystemLibrary("c"); } + if (mem.eql(u8, name, "std")) { + these_tests.overrideStdDir("std"); + } step.dependOn(&these_tests.step); } } @@ -436,7 +439,10 @@ pub const CompareOutputContext = struct { pub fn addCase(self: *CompareOutputContext, case: TestCase) void { const b = self.b; - const root_src = os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename) catch unreachable; + const root_src = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, case.sources.items[0].filename }, + ) catch unreachable; switch (case.special) { Special.Asm => { @@ -449,7 +455,10 @@ pub const CompareOutputContext = struct { exe.addAssemblyFile(root_src); for (case.sources.toSliceConst()) |src_file| { - const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const expanded_src_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, src_file.filename }, + ) catch unreachable; const write_src = b.addWriteFile(expanded_src_path, src_file.source); exe.step.dependOn(&write_src.step); } @@ -473,7 +482,10 @@ pub const CompareOutputContext = struct { } for (case.sources.toSliceConst()) |src_file| { - const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const expanded_src_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, src_file.filename }, + ) catch unreachable; const write_src = b.addWriteFile(expanded_src_path, src_file.source); exe.step.dependOn(&write_src.step); } @@ -496,7 +508,10 @@ pub const CompareOutputContext = struct { } for (case.sources.toSliceConst()) |src_file| { - const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const expanded_src_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, src_file.filename }, + ) catch unreachable; const write_src = b.addWriteFile(expanded_src_path, src_file.source); exe.step.dependOn(&write_src.step); } @@ -569,8 +584,14 @@ pub const CompileErrorContext = struct { const self = @fieldParentPtr(CompileCmpOutputStep, "step", step); const b = self.context.b; - const root_src = os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename) catch unreachable; - const obj_path = os.path.join(b.allocator, b.cache_root, "test.o") catch unreachable; + const root_src = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, self.case.sources.items[0].filename }, + ) catch unreachable; + const obj_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, "test.o" }, + ) catch unreachable; var zig_args = ArrayList([]const u8).init(b.allocator); zig_args.append(b.zig_exe) catch unreachable; @@ -718,7 +739,10 @@ pub const CompileErrorContext = struct { self.step.dependOn(&compile_and_cmp_errors.step); for (case.sources.toSliceConst()) |src_file| { - const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const expanded_src_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, src_file.filename }, + ) catch unreachable; const write_src = b.addWriteFile(expanded_src_path, src_file.source); compile_and_cmp_errors.step.dependOn(&write_src.step); } @@ -849,7 +873,10 @@ pub const TranslateCContext = struct { const self = @fieldParentPtr(TranslateCCmpOutputStep, "step", step); const b = self.context.b; - const root_src = os.path.join(b.allocator, b.cache_root, self.case.sources.items[0].filename) catch unreachable; + const root_src = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, self.case.sources.items[0].filename }, + ) catch unreachable; var zig_args = ArrayList([]const u8).init(b.allocator); zig_args.append(b.zig_exe) catch unreachable; @@ -983,7 +1010,10 @@ pub const TranslateCContext = struct { self.step.dependOn(&translate_c_and_cmp.step); for (case.sources.toSliceConst()) |src_file| { - const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const expanded_src_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, src_file.filename }, + ) catch unreachable; const write_src = b.addWriteFile(expanded_src_path, src_file.source); translate_c_and_cmp.step.dependOn(&write_src.step); } @@ -1098,7 +1128,10 @@ pub const GenHContext = struct { pub fn addCase(self: *GenHContext, case: *const TestCase) void { const b = self.b; - const root_src = os.path.join(b.allocator, b.cache_root, case.sources.items[0].filename) catch unreachable; + const root_src = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, case.sources.items[0].filename }, + ) catch unreachable; const mode = builtin.Mode.Debug; const annotated_case_name = fmt.allocPrint(self.b.allocator, "gen-h {} ({})", case.name, @tagName(mode)) catch unreachable; @@ -1110,7 +1143,10 @@ pub const GenHContext = struct { obj.setBuildMode(mode); for (case.sources.toSliceConst()) |src_file| { - const expanded_src_path = os.path.join(b.allocator, b.cache_root, src_file.filename) catch unreachable; + const expanded_src_path = os.path.join( + b.allocator, + [][]const u8{ b.cache_root, src_file.filename }, + ) catch unreachable; const write_src = b.addWriteFile(expanded_src_path, src_file.source); obj.step.dependOn(&write_src.step); }