authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-06 13:48:04-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-06 13:48:04-05:00
logb1775ca168e0bcfba6753346c5226881da49c6c4
treedc8a194940eca72dc20b692d170a9c1d2a6a4a31
parent8c6fa982cd0a02775264b616c37da9907cc603bb
signature Commit is signed but in an unrecognized format.

thread local storage working for linux x86_64


20 files changed, 306 insertions(+), 103 deletions(-)

CMakeLists.txt+1
......@@ -587,6 +587,7 @@ set(ZIG_STD_FILES
587587 "os/linux/vdso.zig"
588588 "os/linux/x86_64.zig"
589589 "os/path.zig"
590 "os/startup.zig"
590591 "os/time.zig"
591592 "os/uefi.zig"
592593 "os/windows/advapi32.zig"
src/all_types.hpp+8-5
......@@ -544,12 +544,7 @@ struct AstNodeDefer {
544544};
545545
546546struct AstNodeVariableDeclaration {
547 VisibMod visib_mod;
548547 Buf *symbol;
549 bool is_const;
550 bool is_comptime;
551 bool is_export;
552 bool is_extern;
553548 // one or both of type and expr will be non null
554549 AstNode *type;
555550 AstNode *expr;
......@@ -559,6 +554,13 @@ struct AstNodeVariableDeclaration {
559554 AstNode *align_expr;
560555 // populated if the "section(S)" is present
561556 AstNode *section_expr;
557 Token *threadlocal_tok;
558
559 VisibMod visib_mod;
560 bool is_const;
561 bool is_comptime;
562 bool is_export;
563 bool is_extern;
562564};
563565
564566struct AstNodeTestDecl {
......@@ -1873,6 +1875,7 @@ struct ZigVar {
18731875 bool shadowable;
18741876 bool src_is_const;
18751877 bool gen_is_const;
1878 bool is_thread_local;
18761879};
18771880
18781881struct ErrorTableEntry {
src/analyze.cpp+45-24
......@@ -28,28 +28,10 @@ static Error ATTRIBUTE_MUST_USE resolve_enum_zero_bits(CodeGen *g, ZigType *enum
2828static Error ATTRIBUTE_MUST_USE resolve_union_zero_bits(CodeGen *g, ZigType *union_type);
2929static void analyze_fn_body(CodeGen *g, ZigFn *fn_table_entry);
3030
31ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg) {
32 if (node->owner->c_import_node != nullptr) {
33 // if this happens, then translate_c generated code that
34 // failed semantic analysis, which isn't supposed to happen
35 ErrorMsg *err = add_node_error(g, node->owner->c_import_node,
36 buf_sprintf("compiler bug: @cImport generated invalid zig code"));
37
38 add_error_note(g, err, node, msg);
39
40 g->errors.append(err);
41 return err;
42 }
43
44 ErrorMsg *err = err_msg_create_with_line(node->owner->path, node->line, node->column,
45 node->owner->source_code, node->owner->line_offsets, msg);
46
47 g->errors.append(err);
48 return err;
49}
50
51ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg) {
52 if (node->owner->c_import_node != nullptr) {
31static ErrorMsg *add_error_note_token(CodeGen *g, ErrorMsg *parent_msg, ImportTableEntry *owner, Token *token,
32 Buf *msg)
33{
34 if (owner->c_import_node != nullptr) {
5335 // if this happens, then translate_c generated code that
5436 // failed semantic analysis, which isn't supposed to happen
5537
......@@ -64,13 +46,46 @@ ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *m
6446 return note;
6547 }
6648
67 ErrorMsg *err = err_msg_create_with_line(node->owner->path, node->line, node->column,
68 node->owner->source_code, node->owner->line_offsets, msg);
49 ErrorMsg *err = err_msg_create_with_line(owner->path, token->start_line, token->start_column,
50 owner->source_code, owner->line_offsets, msg);
6951
7052 err_msg_add_note(parent_msg, err);
7153 return err;
7254}
7355
56ErrorMsg *add_token_error(CodeGen *g, ImportTableEntry *owner, Token *token, Buf *msg) {
57 if (owner->c_import_node != nullptr) {
58 // if this happens, then translate_c generated code that
59 // failed semantic analysis, which isn't supposed to happen
60 ErrorMsg *err = add_node_error(g, owner->c_import_node,
61 buf_sprintf("compiler bug: @cImport generated invalid zig code"));
62
63 add_error_note_token(g, err, owner, token, msg);
64
65 g->errors.append(err);
66 return err;
67 }
68 ErrorMsg *err = err_msg_create_with_line(owner->path, token->start_line, token->start_column,
69 owner->source_code, owner->line_offsets, msg);
70
71 g->errors.append(err);
72 return err;
73}
74
75ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg) {
76 Token fake_token;
77 fake_token.start_line = node->line;
78 fake_token.start_column = node->column;
79 return add_token_error(g, node->owner, &fake_token, msg);
80}
81
82ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg) {
83 Token fake_token;
84 fake_token.start_line = node->line;
85 fake_token.start_column = node->column;
86 return add_error_note_token(g, parent_msg, node->owner, &fake_token, msg);
87}
88
7489ZigType *new_type_table_entry(ZigTypeId id) {
7590 ZigType *entry = allocate<ZigType>(1);
7691 entry->id = id;
......@@ -3668,6 +3683,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) {
36683683 bool is_const = var_decl->is_const;
36693684 bool is_extern = var_decl->is_extern;
36703685 bool is_export = var_decl->is_export;
3686 bool is_thread_local = var_decl->threadlocal_tok != nullptr;
36713687
36723688 ZigType *explicit_type = nullptr;
36733689 if (var_decl->type) {
......@@ -3727,6 +3743,7 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) {
37273743 tld_var->var = add_variable(g, source_node, tld_var->base.parent_scope, var_decl->symbol,
37283744 is_const, init_val, &tld_var->base, type);
37293745 tld_var->var->linkage = linkage;
3746 tld_var->var->is_thread_local = is_thread_local;
37303747
37313748 if (implicit_type != nullptr && type_is_invalid(implicit_type)) {
37323749 tld_var->var->var_type = g->builtin_types.entry_invalid;
......@@ -3747,6 +3764,10 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var) {
37473764 }
37483765 }
37493766
3767 if (is_thread_local && is_const) {
3768 add_node_error(g, source_node, buf_sprintf("threadlocal variable cannot be constant"));
3769 }
3770
37503771 g->global_vars.append(tld_var);
37513772}
37523773
src/analyze.hpp+1
......@@ -12,6 +12,7 @@
1212
1313void semantic_analyze(CodeGen *g);
1414ErrorMsg *add_node_error(CodeGen *g, AstNode *node, Buf *msg);
15ErrorMsg *add_token_error(CodeGen *g, ImportTableEntry *owner, Token *token, Buf *msg);
1516ErrorMsg *add_error_note(CodeGen *g, ErrorMsg *parent_msg, AstNode *node, Buf *msg);
1617ZigType *new_type_table_entry(ZigTypeId id);
1718ZigType *get_pointer_to_type(CodeGen *g, ZigType *child_type, bool is_const);
src/ast_render.cpp+6-1
......@@ -132,6 +132,10 @@ static const char *const_or_var_string(bool is_const) {
132132 return is_const ? "const" : "var";
133133}
134134
135static const char *thread_local_string(Token *tok) {
136 return (tok == nullptr) ? "" : "threadlocal ";
137}
138
135139const char *container_string(ContainerKind kind) {
136140 switch (kind) {
137141 case ContainerKindEnum: return "enum";
......@@ -554,8 +558,9 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) {
554558 {
555559 const char *pub_str = visib_mod_string(node->data.variable_declaration.visib_mod);
556560 const char *extern_str = extern_string(node->data.variable_declaration.is_extern);
561 const char *thread_local_str = thread_local_string(node->data.variable_declaration.threadlocal_tok);
557562 const char *const_or_var = const_or_var_string(node->data.variable_declaration.is_const);
558 fprintf(ar->f, "%s%s%s ", pub_str, extern_str, const_or_var);
563 fprintf(ar->f, "%s%s%s%s ", pub_str, extern_str, thread_local_str, const_or_var);
559564 print_symbol(ar, node->data.variable_declaration.symbol);
560565
561566 if (node->data.variable_declaration.type) {
src/codegen.cpp+7
......@@ -6445,6 +6445,9 @@ static void do_code_gen(CodeGen *g) {
64456445 maybe_import_dll(g, global_value, GlobalLinkageIdStrong);
64466446 LLVMSetAlignment(global_value, var->align_bytes);
64476447 LLVMSetGlobalConstant(global_value, var->gen_is_const);
6448 if (var->is_thread_local && !g->is_single_threaded) {
6449 LLVMSetThreadLocalMode(global_value, LLVMGeneralDynamicTLSModel);
6450 }
64486451 }
64496452 } else {
64506453 bool exported = (var->linkage == VarLinkageExport);
......@@ -6470,6 +6473,9 @@ static void do_code_gen(CodeGen *g) {
64706473 }
64716474
64726475 LLVMSetGlobalConstant(global_value, var->gen_is_const);
6476 if (var->is_thread_local && !g->is_single_threaded) {
6477 LLVMSetThreadLocalMode(global_value, LLVMGeneralDynamicTLSModel);
6478 }
64736479 }
64746480
64756481 var->value_ref = global_value;
......@@ -7520,6 +7526,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
75207526 g->compile_var_package = new_package(buf_ptr(this_dir), builtin_zig_basename);
75217527 g->root_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
75227528 g->std_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package);
7529 g->std_package->package_table.put(buf_create_from_str("std"), g->std_package);
75237530 g->compile_var_import = add_source_file(g, g->compile_var_package, builtin_zig_path, contents);
75247531 scan_import(g, g->compile_var_import);
75257532
src/ir.cpp+4
......@@ -5204,6 +5204,10 @@ static IrInstruction *ir_gen_var_decl(IrBuilder *irb, Scope *scope, AstNode *nod
52045204 add_node_error(irb->codegen, variable_declaration->section_expr,
52055205 buf_sprintf("cannot set section of local variable '%s'", buf_ptr(variable_declaration->symbol)));
52065206 }
5207 if (variable_declaration->threadlocal_tok != nullptr) {
5208 add_token_error(irb->codegen, node->owner, variable_declaration->threadlocal_tok,
5209 buf_sprintf("function-local variable '%s' cannot be threadlocal", buf_ptr(variable_declaration->symbol)));
5210 }
52075211
52085212 // Temporarily set the name of the IrExecutable to the VariableDeclaration
52095213 // so that the struct or enum from the init expression inherits the name.
src/parser.cpp+14-8
......@@ -844,12 +844,17 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc) {
844844
845845// VarDecl <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? LinkSection? (EQUAL Expr)? SEMICOLON
846846static AstNode *ast_parse_var_decl(ParseContext *pc) {
847 Token *first = eat_token_if(pc, TokenIdKeywordConst);
848 if (first == nullptr)
849 first = eat_token_if(pc, TokenIdKeywordVar);
850 if (first == nullptr)
851 return nullptr;
852
847 Token *thread_local_kw = eat_token_if(pc, TokenIdKeywordThreadLocal);
848 Token *mut_kw = eat_token_if(pc, TokenIdKeywordConst);
849 if (mut_kw == nullptr)
850 mut_kw = eat_token_if(pc, TokenIdKeywordVar);
851 if (mut_kw == nullptr) {
852 if (thread_local_kw == nullptr) {
853 return nullptr;
854 } else {
855 ast_invalid_token_error(pc, peek_token(pc));
856 }
857 }
853858 Token *identifier = expect_token(pc, TokenIdSymbol);
854859 AstNode *type_expr = nullptr;
855860 if (eat_token_if(pc, TokenIdColon) != nullptr)
......@@ -863,8 +868,9 @@ static AstNode *ast_parse_var_decl(ParseContext *pc) {
863868
864869 expect_token(pc, TokenIdSemicolon);
865870
866 AstNode *res = ast_create_node(pc, NodeTypeVariableDeclaration, first);
867 res->data.variable_declaration.is_const = first->id == TokenIdKeywordConst;
871 AstNode *res = ast_create_node(pc, NodeTypeVariableDeclaration, mut_kw);
872 res->data.variable_declaration.threadlocal_tok = thread_local_kw;
873 res->data.variable_declaration.is_const = mut_kw->id == TokenIdKeywordConst;
868874 res->data.variable_declaration.symbol = token_buf(identifier);
869875 res->data.variable_declaration.type = type_expr;
870876 res->data.variable_declaration.align_expr = align_expr;
src/tokenizer.cpp+2
......@@ -146,6 +146,7 @@ static const struct ZigKeyword zig_keywords[] = {
146146 {"suspend", TokenIdKeywordSuspend},
147147 {"switch", TokenIdKeywordSwitch},
148148 {"test", TokenIdKeywordTest},
149 {"threadlocal", TokenIdKeywordThreadLocal},
149150 {"true", TokenIdKeywordTrue},
150151 {"try", TokenIdKeywordTry},
151152 {"undefined", TokenIdKeywordUndefined},
......@@ -1586,6 +1587,7 @@ const char * token_name(TokenId id) {
15861587 case TokenIdKeywordStruct: return "struct";
15871588 case TokenIdKeywordSwitch: return "switch";
15881589 case TokenIdKeywordTest: return "test";
1590 case TokenIdKeywordThreadLocal: return "threadlocal";
15891591 case TokenIdKeywordTrue: return "true";
15901592 case TokenIdKeywordTry: return "try";
15911593 case TokenIdKeywordUndefined: return "undefined";
src/tokenizer.hpp+1
......@@ -88,6 +88,7 @@ enum TokenId {
8888 TokenIdKeywordSuspend,
8989 TokenIdKeywordSwitch,
9090 TokenIdKeywordTest,
91 TokenIdKeywordThreadLocal,
9192 TokenIdKeywordTrue,
9293 TokenIdKeywordTry,
9394 TokenIdKeywordUndefined,
std/debug/index.zig-1
......@@ -37,7 +37,6 @@ const Module = struct {
3737var stderr_file: os.File = undefined;
3838var stderr_file_out_stream: os.File.OutStream = undefined;
3939
40/// TODO multithreaded awareness
4140var stderr_stream: ?*io.OutStream(os.File.WriteError) = null;
4241var stderr_mutex = std.Mutex.init();
4342pub fn warn(comptime fmt: []const u8, args: ...) void {
std/heap.zig+2-5
......@@ -106,9 +106,7 @@ pub const DirectAllocator = struct {
106106 };
107107 const ptr = os.windows.HeapAlloc(heap_handle, 0, amt) orelse return error.OutOfMemory;
108108 const root_addr = @ptrToInt(ptr);
109 const rem = @rem(root_addr, alignment);
110 const march_forward_bytes = if (rem == 0) 0 else (alignment - rem);
111 const adjusted_addr = root_addr + march_forward_bytes;
109 const adjusted_addr = mem.alignForward(root_addr, alignment);
112110 const record_addr = adjusted_addr + n;
113111 @intToPtr(*align(1) usize, record_addr).* = root_addr;
114112 return @intToPtr([*]u8, adjusted_addr)[0..n];
......@@ -126,8 +124,7 @@ pub const DirectAllocator = struct {
126124 const base_addr = @ptrToInt(old_mem.ptr);
127125 const old_addr_end = base_addr + old_mem.len;
128126 const new_addr_end = base_addr + new_size;
129 const rem = @rem(new_addr_end, os.page_size);
130 const new_addr_end_rounded = new_addr_end + if (rem == 0) 0 else (os.page_size - rem);
127 const new_addr_end_rounded = mem.alignForward(new_addr_end, os.page_size);
131128 if (old_addr_end > new_addr_end_rounded) {
132129 _ = os.posix.munmap(new_addr_end_rounded, old_addr_end - new_addr_end_rounded);
133130 }
std/index.zig+2-1
......@@ -33,8 +33,8 @@ pub const io = @import("io.zig");
3333pub const json = @import("json.zig");
3434pub const macho = @import("macho.zig");
3535pub const math = @import("math/index.zig");
36pub const meta = @import("meta/index.zig");
3736pub const mem = @import("mem.zig");
37pub const meta = @import("meta/index.zig");
3838pub const net = @import("net.zig");
3939pub const os = @import("os/index.zig");
4040pub const pdb = @import("pdb.zig");
......@@ -45,6 +45,7 @@ pub const unicode = @import("unicode.zig");
4545pub const zig = @import("zig/index.zig");
4646
4747pub const lazyInit = @import("lazy_init.zig").lazyInit;
48pub const startup = @import("os/startup.zig");
4849
4950test "std" {
5051 // run tests from these
std/mem.zig+20
......@@ -1366,3 +1366,23 @@ test "std.mem.subArrayPtr" {
13661366 sub2[1] = 'X';
13671367 debug.assert(std.mem.eql(u8, a2, "abcXef"));
13681368}
1369
1370/// Round an address up to the nearest aligned address
1371pub fn alignForward(addr: usize, alignment: usize) usize {
1372 return (addr + alignment - 1) & ~(alignment - 1);
1373}
1374
1375test "std.mem.alignForward" {
1376 debug.assertOrPanic(alignForward(1, 1) == 1);
1377 debug.assertOrPanic(alignForward(2, 1) == 2);
1378 debug.assertOrPanic(alignForward(1, 2) == 2);
1379 debug.assertOrPanic(alignForward(2, 2) == 2);
1380 debug.assertOrPanic(alignForward(3, 2) == 4);
1381 debug.assertOrPanic(alignForward(4, 2) == 4);
1382 debug.assertOrPanic(alignForward(7, 8) == 8);
1383 debug.assertOrPanic(alignForward(8, 8) == 8);
1384 debug.assertOrPanic(alignForward(9, 8) == 16);
1385 debug.assertOrPanic(alignForward(15, 8) == 16);
1386 debug.assertOrPanic(alignForward(16, 8) == 16);
1387 debug.assertOrPanic(alignForward(17, 8) == 24);
1388}
std/os/index.zig+66-53
......@@ -8,6 +8,9 @@ const is_posix = switch (builtin.os) {
88};
99const os = @This();
1010
11// See the comment in startup.zig for why this does not use the `std` global above.
12const startup = @import("std").startup;
13
1114test "std.os" {
1215 _ = @import("child_process.zig");
1316 _ = @import("darwin.zig");
......@@ -667,14 +670,11 @@ fn posixExecveErrnoToErr(err: usize) PosixExecveError {
667670 }
668671}
669672
670pub var linux_elf_aux_maybe: ?[*]std.elf.Auxv = null;
671pub var posix_environ_raw: [][*]u8 = undefined;
672
673673/// See std.elf for the constants.
674674pub fn linuxGetAuxVal(index: usize) usize {
675675 if (builtin.link_libc) {
676676 return usize(std.c.getauxval(index));
677 } else if (linux_elf_aux_maybe) |auxv| {
677 } else if (startup.linux_elf_aux_maybe) |auxv| {
678678 var i: usize = 0;
679679 while (auxv[i].a_type != std.elf.AT_NULL) : (i += 1) {
680680 if (auxv[i].a_type == index)
......@@ -692,12 +692,7 @@ pub fn getBaseAddress() usize {
692692 return base;
693693 }
694694 const phdr = linuxGetAuxVal(std.elf.AT_PHDR);
695 const ElfHeader = switch (@sizeOf(usize)) {
696 4 => std.elf.Elf32_Ehdr,
697 8 => std.elf.Elf64_Ehdr,
698 else => @compileError("Unsupported architecture"),
699 };
700 return phdr - @sizeOf(ElfHeader);
695 return phdr - @sizeOf(std.elf.Ehdr);
701696 },
702697 builtin.Os.macosx, builtin.Os.freebsd => return @ptrToInt(&std.c._mh_execute_header),
703698 builtin.Os.windows => return @ptrToInt(windows.GetModuleHandleW(null)),
......@@ -739,7 +734,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
739734 try result.setMove(key, value);
740735 }
741736 } else {
742 for (posix_environ_raw) |ptr| {
737 for (startup.posix_environ_raw) |ptr| {
743738 var line_i: usize = 0;
744739 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
745740 const key = ptr[0..line_i];
......@@ -761,7 +756,7 @@ test "os.getEnvMap" {
761756
762757/// TODO make this go through libc when we have it
763758pub fn getEnvPosix(key: []const u8) ?[]const u8 {
764 for (posix_environ_raw) |ptr| {
759 for (startup.posix_environ_raw) |ptr| {
765760 var line_i: usize = 0;
766761 while (ptr[line_i] != 0 and ptr[line_i] != '=') : (line_i += 1) {}
767762 const this_key = ptr[0..line_i];
......@@ -1942,14 +1937,14 @@ pub const ArgIteratorPosix = struct {
19421937 pub fn init() ArgIteratorPosix {
19431938 return ArgIteratorPosix{
19441939 .index = 0,
1945 .count = raw.len,
1940 .count = startup.posix_argv_raw.len,
19461941 };
19471942 }
19481943
19491944 pub fn next(self: *ArgIteratorPosix) ?[]const u8 {
19501945 if (self.index == self.count) return null;
19511946
1952 const s = raw[self.index];
1947 const s = startup.posix_argv_raw[self.index];
19531948 self.index += 1;
19541949 return cstr.toSlice(s);
19551950 }
......@@ -1960,10 +1955,6 @@ pub const ArgIteratorPosix = struct {
19601955 self.index += 1;
19611956 return true;
19621957 }
1963
1964 /// This is marked as public but actually it's only meant to be used
1965 /// internally by zig's startup code.
1966 pub var raw: [][*]u8 = undefined;
19671958};
19681959
19691960pub const ArgIteratorWindows = struct {
......@@ -2908,14 +2899,15 @@ pub const Thread = struct {
29082899 pub const Data = if (use_pthreads)
29092900 struct {
29102901 handle: Thread.Handle,
2911 stack_addr: usize,
2912 stack_len: usize,
2902 mmap_addr: usize,
2903 mmap_len: usize,
29132904 }
29142905 else switch (builtin.os) {
29152906 builtin.Os.linux => struct {
29162907 handle: Thread.Handle,
2917 stack_addr: usize,
2918 stack_len: usize,
2908 mmap_addr: usize,
2909 mmap_len: usize,
2910 tls_end_addr: usize,
29192911 },
29202912 builtin.Os.windows => struct {
29212913 handle: Thread.Handle,
......@@ -2955,7 +2947,7 @@ pub const Thread = struct {
29552947 posix.EDEADLK => unreachable,
29562948 else => unreachable,
29572949 }
2958 assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
2950 assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0);
29592951 } else switch (builtin.os) {
29602952 builtin.Os.linux => {
29612953 while (true) {
......@@ -2969,7 +2961,7 @@ pub const Thread = struct {
29692961 else => unreachable,
29702962 }
29712963 }
2972 assert(posix.munmap(self.data.stack_addr, self.data.stack_len) == 0);
2964 assert(posix.munmap(self.data.mmap_addr, self.data.mmap_len) == 0);
29732965 },
29742966 builtin.Os.windows => {
29752967 assert(windows.WaitForSingleObject(self.data.handle, windows.INFINITE) == windows.WAIT_OBJECT_0);
......@@ -3097,42 +3089,56 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread
30973089
30983090 const MAP_GROWSDOWN = if (builtin.os == builtin.Os.linux) linux.MAP_GROWSDOWN else 0;
30993091
3100 const mmap_len = default_stack_size;
3101 const stack_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0);
3102 if (stack_addr == posix.MAP_FAILED) return error.OutOfMemory;
3103 errdefer assert(posix.munmap(stack_addr, mmap_len) == 0);
3092 var stack_end_offset: usize = undefined;
3093 var thread_start_offset: usize = undefined;
3094 var context_start_offset: usize = undefined;
3095 var tls_start_offset: usize = undefined;
3096 const mmap_len = blk: {
3097 // First in memory will be the stack, which grows downwards.
3098 var l: usize = mem.alignForward(default_stack_size, os.page_size);
3099 stack_end_offset = l;
3100 // Above the stack, so that it can be in the same mmap call, put the Thread object.
3101 l = mem.alignForward(l, @alignOf(Thread));
3102 thread_start_offset = l;
3103 l += @sizeOf(Thread);
3104 // Next, the Context object.
3105 if (@sizeOf(Context) != 0) {
3106 l = mem.alignForward(l, @alignOf(Context));
3107 context_start_offset = l;
3108 l += @sizeOf(Context);
3109 }
3110 // Finally, the Thread Local Storage, if any.
3111 if (!Thread.use_pthreads) {
3112 if (startup.linux_tls_phdr) |tls_phdr| {
3113 l = mem.alignForward(l, tls_phdr.p_align);
3114 tls_start_offset = l;
3115 l += tls_phdr.p_memsz;
3116 }
3117 }
3118 break :blk l;
3119 };
3120 const mmap_addr = posix.mmap(null, mmap_len, posix.PROT_READ | posix.PROT_WRITE, posix.MAP_PRIVATE | posix.MAP_ANONYMOUS | MAP_GROWSDOWN, -1, 0);
3121 if (mmap_addr == posix.MAP_FAILED) return error.OutOfMemory;
3122 errdefer assert(posix.munmap(mmap_addr, mmap_len) == 0);
3123
3124 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, mmap_addr + thread_start_offset));
3125 thread_ptr.data.mmap_addr = mmap_addr;
3126 thread_ptr.data.mmap_len = mmap_len;
31043127
3105 var stack_end: usize = stack_addr + mmap_len;
31063128 var arg: usize = undefined;
31073129 if (@sizeOf(Context) != 0) {
3108 stack_end -= @sizeOf(Context);
3109 stack_end -= stack_end % @alignOf(Context);
3110 assert(stack_end >= stack_addr);
3111 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, stack_end));
3130 arg = mmap_addr + context_start_offset;
3131 const context_ptr = @alignCast(@alignOf(Context), @intToPtr(*Context, arg));
31123132 context_ptr.* = context;
3113 arg = stack_end;
31143133 }
31153134
3116 stack_end -= @sizeOf(Thread);
3117 stack_end -= stack_end % @alignOf(Thread);
3118 assert(stack_end >= stack_addr);
3119 const thread_ptr = @alignCast(@alignOf(Thread), @intToPtr(*Thread, stack_end));
3120
3121 thread_ptr.data.stack_addr = stack_addr;
3122 thread_ptr.data.stack_len = mmap_len;
3123
3124 if (builtin.os == builtin.Os.windows) {
3125 // use windows API directly
3126 @compileError("TODO support spawnThread for Windows");
3127 } else if (Thread.use_pthreads) {
3135 if (Thread.use_pthreads) {
31283136 // use pthreads
31293137 var attr: c.pthread_attr_t = undefined;
31303138 if (c.pthread_attr_init(&attr) != 0) return SpawnThreadError.SystemResources;
31313139 defer assert(c.pthread_attr_destroy(&attr) == 0);
31323140
3133 // align to page
3134 stack_end -= stack_end % os.page_size;
3135 assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, stack_addr), stack_end - stack_addr) == 0);
3141 assert(c.pthread_attr_setstack(&attr, @intToPtr(*c_void, mmap_addr), stack_end_offset) == 0);
31363142
31373143 const err = c.pthread_create(&thread_ptr.data.handle, &attr, MainFuncs.posixThreadMain, @intToPtr(*c_void, arg));
31383144 switch (err) {
......@@ -3143,10 +3149,17 @@ pub fn spawnThread(context: var, comptime startFn: var) SpawnThreadError!*Thread
31433149 else => return unexpectedErrorPosix(@intCast(usize, err)),
31443150 }
31453151 } else if (builtin.os == builtin.Os.linux) {
3146 // use linux API directly. TODO use posix.CLONE_SETTLS and initialize thread local storage correctly
3147 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;
3148 const newtls: usize = 0;
3149 const rc = posix.clone(MainFuncs.linuxThreadMain, stack_end, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle);
3152 var flags: u32 = posix.CLONE_VM | posix.CLONE_FS | posix.CLONE_FILES | posix.CLONE_SIGHAND |
3153 posix.CLONE_THREAD | posix.CLONE_SYSVSEM | posix.CLONE_PARENT_SETTID | posix.CLONE_CHILD_CLEARTID |
3154 posix.CLONE_DETACHED;
3155 var newtls: usize = undefined;
3156 if (startup.linux_tls_phdr) |tls_phdr| {
3157 @memcpy(@intToPtr([*]u8, mmap_addr + tls_start_offset), startup.linux_tls_img_src, tls_phdr.p_filesz);
3158 thread_ptr.data.tls_end_addr = mmap_addr + mmap_len;
3159 newtls = @ptrToInt(&thread_ptr.data.tls_end_addr);
3160 flags |= posix.CLONE_SETTLS;
3161 }
3162 const rc = posix.clone(MainFuncs.linuxThreadMain, mmap_addr + stack_end_offset, flags, arg, &thread_ptr.data.handle, newtls, &thread_ptr.data.handle);
31503163 const err = posix.getErrno(rc);
31513164 switch (err) {
31523165 0 => return thread_ptr,
std/os/startup.zig created+26
......@@ -0,0 +1,26 @@
1// This file contains global variables that are initialized on startup from
2// std/special/bootstrap.zig. There are a few things to be aware of here.
3//
4// First, when building an object or library, and no entry point is defined
5// (such as pub fn main), std/special/bootstrap.zig is not included in the
6// compilation. And so these global variables will remain set to the values
7// you see here.
8//
9// Second, when using `zig test` to test the standard library, note that
10// `zig test` is self-hosted. This means that it uses std/special/bootstrap.zig
11// and an @import("std") from the install directory, which is distinct from
12// the standard library files that we are directly testing with `zig test`.
13// This means that these global variables would not get set. So the workaround
14// here is that references to these globals from the standard library must
15// use `@import("std").startup` rather than
16// `@import("path/to/std/index.zig").startup` (and rather than the file path of
17// this file directly). We also put "std" as a reference to itself in the
18// standard library package so that this can work.
19
20const std = @import("../index.zig");
21
22pub var linux_tls_phdr: ?*std.elf.Phdr = null;
23pub var linux_tls_img_src: [*]const u8 = undefined; // defined when linux_tls_phdr is non-null
24pub var linux_elf_aux_maybe: ?[*]std.elf.Auxv = null;
25pub var posix_environ_raw: [][*]u8 = undefined;
26pub var posix_argv_raw: [][*]u8 = undefined;
std/os/test.zig+16
......@@ -105,3 +105,19 @@ test "AtomicFile" {
105105
106106 try os.deleteFile(test_out_file);
107107}
108
109test "thread local storage" {
110 if (builtin.single_threaded) return error.SkipZigTest;
111 const thread1 = try std.os.spawnThread({}, testTls);
112 const thread2 = try std.os.spawnThread({}, testTls);
113 testTls({});
114 thread1.wait();
115 thread2.wait();
116}
117
118threadlocal var x: i32 = 1234;
119fn testTls(context: void) void {
120 if (x != 1234) @panic("bad start value");
121 x += 1;
122 if (x != 1235) @panic("bad end value");
123}
std/special/bootstrap.zig+58-5
......@@ -4,6 +4,7 @@
44const root = @import("@root");
55const std = @import("std");
66const builtin = @import("builtin");
7const assert = std.debug.assert;
78
89var argc_ptr: [*]usize = undefined;
910
......@@ -61,9 +62,23 @@ fn posixCallMainAndExit() noreturn {
6162 while (envp_optional[envp_count]) |_| : (envp_count += 1) {}
6263 const envp = @ptrCast([*][*]u8, envp_optional)[0..envp_count];
6364 if (builtin.os == builtin.Os.linux) {
64 const auxv = @ptrCast([*]usize, envp.ptr + envp_count + 1);
65 std.os.linux_elf_aux_maybe = @ptrCast([*]std.elf.Auxv, auxv);
66 std.debug.assert(std.os.linuxGetAuxVal(std.elf.AT_PAGESZ) == std.os.page_size);
65 // Scan auxiliary vector.
66 const auxv = @ptrCast([*]std.elf.Auxv, envp.ptr + envp_count + 1);
67 std.startup.linux_elf_aux_maybe = auxv;
68 var i: usize = 0;
69 var at_phdr: usize = 0;
70 var at_phnum: usize = 0;
71 var at_phent: usize = 0;
72 while (auxv[i].a_un.a_val != 0) : (i += 1) {
73 switch (auxv[i].a_type) {
74 std.elf.AT_PAGESZ => assert(auxv[i].a_un.a_val == std.os.page_size),
75 std.elf.AT_PHDR => at_phdr = auxv[i].a_un.a_val,
76 std.elf.AT_PHNUM => at_phnum = auxv[i].a_un.a_val,
77 std.elf.AT_PHENT => at_phent = auxv[i].a_un.a_val,
78 else => {},
79 }
80 }
81 if (!builtin.single_threaded) linuxInitializeThreadLocalStorage(at_phdr, at_phnum, at_phent);
6782 }
6883
6984 std.os.posix.exit(callMainWithArgs(argc, argv, envp));
......@@ -72,8 +87,8 @@ fn posixCallMainAndExit() noreturn {
7287// This is marked inline because for some reason LLVM in release mode fails to inline it,
7388// and we want fewer call frames in stack traces.
7489inline fn callMainWithArgs(argc: usize, argv: [*][*]u8, envp: [][*]u8) u8 {
75 std.os.ArgIteratorPosix.raw = argv[0..argc];
76 std.os.posix_environ_raw = envp;
90 std.startup.posix_argv_raw = argv[0..argc];
91 std.startup.posix_environ_raw = envp;
7792 return callMain();
7893}
7994
......@@ -116,3 +131,41 @@ inline fn callMain() u8 {
116131 else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '!void'"),
117132 }
118133}
134
135var tls_end_addr: usize = undefined;
136const main_thread_tls_align = 32;
137var main_thread_tls_bytes: [64]u8 align(main_thread_tls_align) = [1]u8{0} ** 64;
138
139fn linuxInitializeThreadLocalStorage(at_phdr: usize, at_phnum: usize, at_phent: usize) void {
140 var phdr_addr = at_phdr;
141 var n = at_phnum;
142 var base: usize = 0;
143 while (n != 0) : ({n -= 1; phdr_addr += at_phent;}) {
144 const phdr = @intToPtr(*std.elf.Phdr, phdr_addr);
145 // TODO look for PT_DYNAMIC when we have https://github.com/ziglang/zig/issues/1917
146 switch (phdr.p_type) {
147 std.elf.PT_PHDR => base = at_phdr - phdr.p_vaddr,
148 std.elf.PT_TLS => std.startup.linux_tls_phdr = phdr,
149 else => continue,
150 }
151 }
152 const tls_phdr = std.startup.linux_tls_phdr orelse return;
153 std.startup.linux_tls_img_src = @intToPtr([*]const u8, base + tls_phdr.p_vaddr);
154 assert(main_thread_tls_bytes.len >= tls_phdr.p_memsz); // not enough preallocated Thread Local Storage
155 assert(main_thread_tls_align >= tls_phdr.p_align); // preallocated Thread Local Storage not aligned enough
156 @memcpy(&main_thread_tls_bytes, std.startup.linux_tls_img_src, tls_phdr.p_filesz);
157 tls_end_addr = @ptrToInt(&main_thread_tls_bytes) + tls_phdr.p_memsz;
158 linuxSetThreadArea(@ptrToInt(&tls_end_addr));
159}
160
161fn linuxSetThreadArea(addr: usize) void {
162 switch (builtin.arch) {
163 builtin.Arch.x86_64 => {
164 const ARCH_SET_FS = 0x1002;
165 const rc = std.os.linux.syscall2(std.os.linux.SYS_arch_prctl, ARCH_SET_FS, addr);
166 // acrh_prctl is documented to never fail
167 assert(rc == 0);
168 },
169 else => @compileError("Unsupported architecture"),
170 }
171}
test/compile_errors.zig+19
......@@ -1,6 +1,25 @@
11const tests = @import("tests.zig");
22
33pub fn addCases(cases: *tests.CompileErrorContext) void {
4 cases.add(
5 "threadlocal qualifier on const",
6 \\threadlocal const x: i32 = 1234;
7 \\export fn entry() i32 {
8 \\ return x;
9 \\}
10 ,
11 ".tmp_source.zig:1:13: error: threadlocal variable cannot be constant",
12 );
13
14 cases.add(
15 "threadlocal qualifier on local variable",
16 \\export fn entry() void {
17 \\ threadlocal var x: i32 = 1234;
18 \\}
19 ,
20 ".tmp_source.zig:2:5: error: function-local variable 'x' cannot be threadlocal",
21 );
22
423 cases.add(
524 "@bitCast same size but bit count mismatch",
625 \\export fn entry(byte: u8) void {
test/stage1/behavior/misc.zig+8
......@@ -685,3 +685,11 @@ test "fn call returning scalar optional in equality expression" {
685685fn getNull() ?*i32 {
686686 return null;
687687}
688
689test "thread local variable" {
690 const S = struct {
691 threadlocal var t: i32 = 1234;
692 };
693 S.t += 1;
694 assertOrPanic(S.t == 1235);
695}